i
[chise/ruby.git] / chise / db.rb
1 # Copyright (C) 2002-2004 Kouichirou Eto, All rights reserved.
2
3 require "singleton"
4 require "bdb"
5 require "chise/config"
6 require "chise/util"
7 require "chise/rbchise"
8
9 module CHISE
10
11   class DBS # collection of DBs. not yet
12   end
13
14   class ADB < BDB::Hash # A DataBase.
15     def initialize(*args)
16       super
17       @filename = args[0]
18       @modified = false
19       at_exit {
20 #       p ["at_exit", @filename, @modified]
21         if @modified
22 #         p ["close", @filename, @modified]
23           self.close #これがないと、うまくデータベースがセーブされないのです。
24         end
25       }
26     end
27
28     def self.open_create(filename)
29       ADB.open(filename, nil, BDB::CREATE | BDB::EXCL) #上書きはしない
30     end
31
32     def mykey(key)
33       if key.is_a?(String)
34         if key.char_length == 1
35           return "?"+key  #Stringだったら引く前に?を足す
36         end
37       end
38       #key = key.to_s if key.is_a?(Numeric) #NumberだったらStringにする。
39       #ここで && key ! =~ /^\?/ をいれると、?自身を検索できなくなってしまう。
40       return key
41     end
42
43     def myvalue(v)
44       return v if v == nil
45       return v.to_i if v =~ /^\d+$/ #数字だったらここで変換しておく
46       return v.sub(/^\?/, "") if v =~ /^\?/ #冒頭の?は取り除く
47       return $1 if v =~ /^"(.+)"$/ #最初と最後に"がついていたら、取り除く
48       #p ["get", v, t, key, db]
49       #return parse_sexp(v) if v =~ /^\(.+\)$/ #最初と最後が()の時は、S式にparseする
50       return v #それ以外って何?
51     end
52
53     def myget(key) #keyキーを引いて返す
54       key = mykey(key)
55       v = get(key) #存在しなかったらnilを返すことになる
56       return myvalue(v)
57     end
58
59     def myput(key, v) #keyにvをいれる
60       key = mykey(key)
61       put(key, v) #putする
62       @modified = true
63     end
64   end
65
66   class DB # abstract class for DataBase
67     # translate file name for deal with Windows file system.
68     def self.unix_to_win(unix_path)
69       win = unix_path.gsub(/</, "(")
70       win = win.gsub(/>/, ")")
71       win = win.gsub(/\*/, "+")
72       win = win.gsub(/\?/, "!")
73       win
74     end
75
76     def self.win_to_unix(win_path)
77       unix = win_path.gsub(/\)/, ">")
78       unix = unix.gsub(/\(/, "<")
79       unix = unix.gsub(/\!/, "?")
80       unix = unix.gsub(/\+/, "*")
81       unix
82     end
83
84     def get_filename(t)
85       return @pre + DB.unix_to_win(t) + @post if CHISE.windows?
86       return @pre + t + @post
87     end
88
89     def get_dirname(t) File.dirname(get_filename(t)) end
90
91     def open_dbs()
92       @dbs = Hash.new
93       keys = find_keys()
94       keys.each {|key| open_db(key) }
95     end
96
97     def find_keys()
98       files = []
99       Dir.glob(@glob){|f|
100         next if ! File.file?(f)
101         next if f =~ /.txt$/
102         files << f
103       }
104       keys = []
105       files.each {|f|
106         t = DB.win_to_unix(f)
107         t.sub!(%r|^#{@pre}|, "")
108         t.sub!(%r|#{@post}$|, "") if @post != ""
109         keys << t
110       }
111       return keys
112     end
113
114     def close_db(t)
115       db = get(t)
116       return nil if db.nil?
117       db.close
118       @dbs.delete(t)
119     end
120
121     def open_db(t)
122       return nil if get(t) #すでにopenしていたら再openはしない。
123       begin
124         bdb = ADB.open(get_filename(t), nil, 0)
125         @dbs[t] = bdb if bdb != nil
126       rescue
127         p ["open error", get_filename(t)]; return nil
128       end
129       true
130     end
131
132     def make_db(t, h=nil) #tという名前でhという中身のデータベースを作る
133       return nil if get(t) #すでにある場合はreturn
134       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
135       db = nil
136       begin
137         db = ADB.open_create(get_filename(t)) #上書きはしない
138         if h != nil
139           h.each {|k, v|
140             k = "?"+k if k.is_a?(String)
141             db[k] = v
142           }
143         end
144         db.close
145       rescue
146         p ["make error", get_filename(t)]; return nil
147       end
148       true
149     end
150
151     def make_db_no_question_mark(t, h=nil) #tという名前でhという中身のデータベースを作る
152       return nil if get(t) #すでにある場合はreturn
153       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
154       db = nil
155       begin
156         db = ADB.open_create(get_filename(t)) #上書きはしない
157         if h != nil
158           h.each {|k, v|
159             #        k = "?"+k if k.is_a?(String)
160             db[k] = v
161           }
162         end
163         db.close
164       rescue
165         p ["make error", get_filename(t)]; return nil
166       end
167       true
168     end
169
170     def remove_db(t) #tという名前のデータベースを消去する
171       db = get(t)
172       if db
173         db.close
174         @dbs.delete(t)
175       end
176       begin
177         File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
178       rescue
179         p ["unlink error", get_filename(t)]; return nil
180       end
181       dn = get_dirname(t)
182       Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
183       true
184     end
185
186     def to_num(s)
187       return s.to_i if s =~ /^\d+$/
188       s
189     end
190
191     def dump_db(t)
192       db = get(t)
193       return nil unless db
194       file = get_filename(t)
195       open("#{file}.txt", "w"){|out|
196         #        out.binmode.sync = true
197         ar = db.to_a
198         ar.map! {|k, v| [to_num(k), to_num(v)] }
199         ar.sort.each {|k, v|
200           out.printf("%s\t%s\n", k, v)
201         }
202       }
203       true
204     end
205
206     def each_db()  @dbs.to_a.sort.each {|t, db| yield(t, db) } end
207     def dump_all()  each_db {|t, db| dump_db(t) } end
208     def close_all() each_db {|t, db| db.close   } end
209     def keys() @dbs.keys end
210
211     def each(t)
212       return unless block_given?
213       db = @dbs[t]
214       return nil unless db
215       db.each {|k, v|
216         k = to_num(k)
217         v = to_num(v)
218         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
219         vv = get(t, k)  #p ["each", t, k, v, vv]
220         yield(k, vv)
221       }
222     end
223
224     def each_sort(t)
225       return unless block_given?
226       db = @dbs[t]
227       return nil unless db
228       ar = db.to_a
229       ar.map! {|k, v| [to_num(k), to_num(v)] }
230       ar.sort.each {|k, v|
231         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
232         vv = get(t, k)  #p ["each", t, k, v, vv]
233         yield(k, vv)
234       }
235     end
236
237     def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
238       db = @dbs[t]
239       return db if key.nil?
240       return nil unless db
241       db.myget(key)
242     end
243
244     def put(t, key, v) #tというデータベースのkeyにvをいれる
245       db = @dbs[t]
246       if db == nil
247         db = make_db(t) 
248         db = open_db(t) 
249         db = @dbs[t]
250       end
251       db.myput(key, v) #putする
252     end
253   end
254
255   class CharDB < DB # An Attribute DataBase.  Key is in UTF8-MCS.
256     include Singleton
257
258     def initialize()
259       super
260       dir = Config.instance.db_dir
261       @glob, @pre, @post = "#{dir}/system-char-id/*", "#{dir}/system-char-id/", ""
262       open_dbs()
263     end
264
265     def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
266       atrs = Hash.new
267       @dbs.each {|t, db|
268         v = get(t, u8)
269         atrs[t] = v if v != nil
270       }
271       atrs
272     end
273   end
274
275   class CodesysDB < DB # A CodeSystem DataBase.
276     include Singleton
277
278     def initialize()
279       super
280       dir = Config.instance.db_dir
281       @glob, @pre, @post = "#{dir}/*/system-char-id", "#{dir}/", "/system-char-id"
282       open_dbs()
283     end
284
285     #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
286     def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
287
288     def get_codesys(t)
289       db = get(t)
290       return nil unless db
291       return Codesys.new(t)
292     end
293   end
294
295   class Codesys < DB
296     def initialize(name)
297       #super
298       @name = name
299       @dbs = CodesysDB.instance
300     end
301
302     def keys() #どんなコードポイントの情報を持っているかの一覧
303       ks = @dbs.get(@name).keys
304 #      if @name =~ /jisx0208/ #特別処理
305 #       n = @dbs.get("=jis-x0208").keys 
306 #       #        p ["keys", @name, ks, n]
307 #       ks += n
308 #      end
309       ks.map! {|k| to_num(k) }
310       ks
311     end
312
313     def get(key)
314       v = @dbs.get(@name, key)
315       return v if v
316 #      if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
317 #       return @dbs.get("=jis-x0208", key)
318 #      end
319       return nil
320     end
321
322     def each()
323       return unless block_given?
324       db = @dbs.get(@name)
325       return nil unless db
326       db.each {|k, v|
327         k = to_num(k)
328         v = to_num(v)
329         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
330         vv = @dbs.get(@name, k) #p ["each", t, k, v, vv]
331         yield(k, vv)
332       }
333     end
334
335     def each_sort()
336       return unless block_given?
337       db = @dbs.get(@name)
338       return nil unless db
339       ar = db.to_a
340       ar.map! {|k, v| [to_num(k), to_num(v)] }
341       ar.sort.each {|k, v|
342         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
343         vv = @dbs.get(@name, k) #p ["each", t, k, v, vv]
344         yield(k, vv)
345       }
346     end
347   end
348
349   class JISX0208
350     def initialize
351       db = CodesysDB.instance
352       @common = db.get_codesys("=jis-x0208")
353       @newest = db.get_codesys("japanese-jisx0208-1990")
354     end
355     def get_char(code)
356       char = @common.get(code)
357       return char unless char.nil?
358       char = @newest.get(code)
359       return char unless char.nil?
360       return nil
361     end
362   end
363
364   class DBS_Management # DataBase file management
365     OBSOLETE_ATTRIBUTES = "
366 cns-radical
367 cns-radical?
368 kangxi-radical
369 daikanwa-radical
370 unicode-radical
371
372 cns-strokes
373 kangxi-strokes
374 daikanwa-strokes
375 shinjigen-1-radical
376 gb-original-radical
377 japanese-strokes
378 jis-strokes-a
379 jis-strokes-b
380 jisx0208-strokes
381 jis-x0213-strokes
382 jisx0213-strokes
383 unicode-strokes
384
385 totalstrokes
386 cns-total-strokes
387 jis-total-strokes-b
388
389 non-morohashi
390
391 =>ucs*
392 #=>mojikyo
393 #=mojikyo
394 ->identical
395
396 ancient-ideograph-of
397 ancient-char-of-shinjigen-1
398 original-ideograph-of
399 original-char-of-shinjigen-1
400 simplified-ideograph-of
401 vulgar-ideograph-of
402 vulgar-char-of-shinjigen-1
403 ideograph=
404 ideographic-variants
405 variant-of-shinjigen-1
406
407 iso-10646-comment
408 ".split
409
410     def initialize
411       dir = Config.instance.db_dir
412       @odir = dir+"/system-char-id/obsolete" #直打ちしている。
413     end
414
415     def move_obsolete_files # move obsolete BDB files to obsolete directory
416       db = CharDB.instance
417       db.close_all
418       Dir.mkdir(@odir) unless FileTest.directory? @odir
419       OBSOLETE_ATTRIBUTES.each {|attr|
420         next if attr =~ /^#/
421         filename = db.get_filename(attr)
422         move_to_obsolete(filename)
423         move_to_obsolete(filename+".txt")
424       }
425     end
426
427     def move_to_obsolete(file)
428       cmd = "mv \"#{file}\" #{@odir}"
429       #      p cmd
430       system cmd
431     end
432
433   end
434 end