1644f1b95b2c75c1ef0a3e38a9f2c5824d980a02
[chise/ruby.git] / lib / chise / db.rb
1 #!/usr/bin/env ruby
2 # $Id: db.rb,v 1.2 2003-11-30 13:16:38 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       @db_dir = Config.instance.db_dir
239       @glob, @pre, @post = "#{@db_dir}/system-char-id/*", "#{@db_dir}/system-char-id/", ""
240       open_dbs()
241     end
242     def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
243       atrs = Hash.new
244       @dbs.each {|t, db|
245         v = get(t, u8)
246         atrs[t] = v if v != nil
247       }
248       return atrs
249     end
250   end
251
252   class CodesysDB < DB #----------------------------------------------------------------------
253     include Singleton
254     def initialize()
255       super
256       @glob, @pre, @post = "#{DB_DIR}/*/system-char-id", "#{DB_DIR}/", "/system-char-id"
257       open_dbs()
258     end
259     #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
260     def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
261     def get_codesys(t)
262       db = get(t)
263       return nil unless db
264       return Codesys.new(t)
265     end
266   end
267
268   class Codesys < DB #======================================================================
269     def initialize(name)
270       #       super
271       @name = name
272       @dbs = CodesysDB.instance
273     end
274     def keys() #どんなコードポイントの情報を持っているかの一覧
275       ks = @dbs.get(@name).keys
276 #      if @name =~ /jisx0208/ #特別処理
277 #       n = @dbs.get('=jis-x0208').keys 
278 #       #        p ['keys', @name, ks, n]
279 #       ks += n
280 #      end
281       ks.map! {|k| to_num(k) }
282       ks
283     end
284     def get(key)
285       v = @dbs.get(@name, key)
286       return v if v
287 #      if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
288 #       return @dbs.get('=jis-x0208', key)
289 #      end
290       return nil
291     end
292     def each()
293       return unless block_given?
294       db = @dbs.get(@name)
295       return nil unless db
296       db.each {|k, v|
297         k = to_num(k)
298         v = to_num(v)
299         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
300         vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
301         yield(k, vv)
302       }
303     end
304     def each_sort()
305       return unless block_given?
306       db = @dbs.get(@name)
307       return nil unless db
308       ar = db.to_a
309       ar.map! {|k, v| [to_num(k), to_num(v)] }
310       ar.sort.each {|k, v|
311         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
312         vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
313         yield(k, vv)
314       }
315     end
316   end
317
318   class JISX0208 #======================================================================
319     def initialize
320       db = CodesysDB.instance
321       @common = db.get_codesys('=jis-x0208')
322       @newest = db.get_codesys('japanese-jisx0208-1990')
323     end
324     def get_char(code)
325       char = @common.get(code)
326       return char unless char.nil?
327       char = @newest.get(code)
328       return char unless char.nil?
329       return nil
330     end
331   end
332
333   class DBS_Management #======================================================================ファイル管理
334     OBSOLETE_ATTRIBUTES = "
335 cns-radical
336 cns-radical?
337 kangxi-radical
338 daikanwa-radical
339 unicode-radical
340
341 cns-strokes
342 kangxi-strokes
343 daikanwa-strokes
344 shinjigen-1-radical
345 gb-original-radical
346 japanese-strokes
347 jis-strokes-a
348 jis-strokes-b
349 jisx0208-strokes
350 jis-x0213-strokes
351 jisx0213-strokes
352 unicode-strokes
353
354 totalstrokes
355 cns-total-strokes
356 jis-total-strokes-b
357
358 non-morohashi
359
360 =>ucs*
361 #=>mojikyo
362 #=mojikyo
363 ->identical
364
365 ancient-ideograph-of
366 ancient-char-of-shinjigen-1
367 original-ideograph-of
368 original-char-of-shinjigen-1
369 simplified-ideograph-of
370 vulgar-ideograph-of
371 vulgar-char-of-shinjigen-1
372 ideograph=
373 ideographic-variants
374 variant-of-shinjigen-1
375
376 iso-10646-comment
377 ".split
378     def initialize
379       @odir = DB_DIR+"/system-char-id/obsolete" #直打ちしている。
380     end
381     def move_obsolete_files # 廃止予定のbdbファイルをobsoleteディレクトリーにつっこむ
382       db = CharDB.instance
383       db.close_all
384       Dir.mkdir(@odir) unless FileTest.directory? @odir
385       OBSOLETE_ATTRIBUTES.each {|attr|
386         next if attr =~ /^#/
387         filename = db.get_filename(attr)
388         move_to_obsolete(filename)
389         move_to_obsolete(filename+".txt")
390       }
391     end
392     def move_to_obsolete(file)
393       cmd = "mv \"#{file}\" #{@odir}"
394       #      p cmd
395       system cmd
396     end
397   end
398
399 end
400
401 #----------------------------------------------------------------------終了