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