0a8cbc3b3837f63899f746c12377de35d40c1b95
[chise/ruby.git] / src / db.rb
1 #!/usr/bin/env ruby
2 #
3 # rbchise compatible ruby library by eto 2003-0317
4 # Copyright (C) 2002-2003 Kouichirou Eto
5 #     All rights reserved.
6 #     This is free software with ABSOLUTELY NO WARRANTY.
7 #
8 # You can redistribute it and/or modify it under the terms of 
9 # the GNU General Public License version 2.
10 #
11
12 require 'db'
13
14 module CHISE
15
16   class DBS #======================================================================複数のDBを集めたclass、未完成
17   end
18
19   class ADB < BDB::Hash #======================================================================一つのDB
20     def initialize(*args)
21       super
22       @modified = false
23       at_exit {
24         if @modified
25           self.close #これがないと、うまくデータベースがセーブされないのです。
26         end
27       }
28     end
29     def self.open_create(filename)
30       ADB.open(filename, nil, BDB::CREATE | BDB::EXCL) #上書きはしない
31     end
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     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     def myget(key) #keyキーを引いて返す
52       key = mykey(key)
53       v = get(key) #存在しなかったらnilを返すことになる
54       return myvalue(v)
55     end
56     def myput(key, v) #keyにvをいれる
57       key = mykey(key)
58       put(key, v) #putする
59       @modified = true
60     end
61   end
62
63   class DB #======================================================= データベース群のabstract class
64     def self.unix_to_win(unix) #Windowsファイル名制限のため、変換する
65       win = unix.gsub(/</, '(')
66       win.gsub!(/>/, ')')
67       win.gsub!(/\*/, '+')
68       win.gsub!(/\?/, '!')
69       return win
70     end
71     def self.win_to_unix(win)
72       unix = win.gsub(%r|\)|, '>')
73       unix.gsub!(%r|\(|, '<')
74       unix.gsub!(%r|!|, '?')
75       unix.gsub!(%r|\+|, '*')
76       return unix
77     end
78     def get_filename(t)
79       return @pre + DB.unix_to_win(t) + @post if windows?
80       return @pre + t + @post
81     end
82     def get_dirname(t) File.dirname(get_filename(t)) end
83     def open_dbs()
84       @dbs = Hash.new
85       keys = find_keys()
86       keys.each {|key| open_db(key) }
87     end
88     def find_keys()
89       files = []
90       Dir.glob(@glob){|f|
91         next if ! File.file?(f)
92         next if f =~ /.txt$/
93         files << f
94       }
95       keys = []
96       files.each {|f|
97         t = DB.win_to_unix(f)
98         t.sub!(%r|^#{@pre}|, '')
99         t.sub!(%r|#{@post}$|, '') if @post != ""
100         keys << t
101       }
102       return keys
103     end
104     def close_db(t)
105       db = get(t)
106       return nil if db.nil?
107       db.close
108       @dbs.delete(t)
109     end
110     def open_db(t)
111       return nil if get(t) #すでにopenしていたら再openはしない。
112       begin
113         bdb = ADB.open(get_filename(t), nil, 0)
114         @dbs[t] = bdb if bdb != nil
115       rescue
116         p ["open error", get_filename(t)]; return nil
117       end
118       return true
119     end
120     def make_db(t, h=nil) #tという名前でhという中身のデータベースを作る
121       return nil if get(t) #すでにある場合はreturn
122       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
123       db = nil
124       begin
125         db = ADB.open_create(get_filename(t)) #上書きはしない
126         if h != nil
127           h.each {|k, v|
128             k = '?'+k if k.is_a?(String)
129             db[k] = v
130           }
131         end
132         db.close
133       rescue
134         p ["make error", get_filename(t)]; return nil
135       end
136       return true
137     end
138     def make_db_no_question_mark(t, h=nil) #tという名前でhという中身のデータベースを作る
139       return nil if get(t) #すでにある場合はreturn
140       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
141       db = nil
142       begin
143         db = ADB.open_create(get_filename(t)) #上書きはしない
144         if h != nil
145           h.each {|k, v|
146             #        k = '?'+k if k.is_a?(String)
147             db[k] = v
148           }
149         end
150         db.close
151       rescue
152         p ["make error", get_filename(t)]; return nil
153       end
154       return true
155     end
156     def remove_db(t) #tという名前のデータベースを消去する
157       db = get(t)
158       if db
159         db.close
160         @dbs.delete(t)
161       end
162       begin
163         File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
164       rescue
165         p ["unlink error", get_filename(t)]; return nil
166       end
167       dn = get_dirname(t)
168       Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
169       return true
170     end
171     def to_num(s)
172       return s.to_i if s =~ /^\d+$/
173       return s
174     end
175     def dump_db(t)
176       db = get(t)
177       return nil unless db
178       file = get_filename(t)
179       open("#{file}.txt", "w"){|out|
180         #        out.binmode.sync = true
181         ar = db.to_a
182         ar.map! {|k, v| [to_num(k), to_num(v)] }
183         ar.sort.each {|k, v|
184           out.printf("%s\t%s\n", k, v)
185         }
186       }
187       return true
188     end
189     def each_db()  @dbs.to_a.sort.each {|t, db| yield(t, db) } end
190     def dump_all()  each_db {|t, db| dump_db(t) } end
191     def close_all() each_db {|t, db| db.close   } end
192     def keys() @dbs.keys end
193     def each(t)
194       return unless block_given?
195       db = @dbs[t]
196       return nil unless db
197       db.each {|k, v|
198         k = to_num(k)
199         v = to_num(v)
200         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
201         vv = get(t, k)  #p ['each', t, k, v, vv]
202         yield(k, vv)
203       }
204     end
205     def each_sort(t)
206       return unless block_given?
207       db = @dbs[t]
208       return nil unless db
209       ar = db.to_a
210       ar.map! {|k, v| [to_num(k), to_num(v)] }
211       ar.sort.each {|k, v|
212         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
213         vv = get(t, k)  #p ['each', t, k, v, vv]
214         yield(k, vv)
215       }
216     end
217     #----------------------------------------------------------------------
218     def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
219       db = @dbs[t]
220       return db if key.nil?
221       return nil unless db
222       return db.myget(key)
223     end
224     def put(t, key, v) #tというデータベースのkeyにvをいれる
225       db = @dbs[t]
226       if db == nil
227         db = make_db(t) 
228         db = open_db(t) 
229         db = @dbs[t]
230       end
231       db.myput(key, v) #putする
232     end
233   end
234
235   class CharDB < DB #------------------------------------ MCS-UTF8をキーとした属性へのデータベース
236     include Singleton
237     def initialize()
238       super
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 #----------------------------------------------------------------------終了