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