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