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