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/util"
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
69     def get_filename(t)
70       return @pre + DB.unix_to_win(t) + @post if CHISE.windows?
71       return @pre + t + @post
72     end
73
74     def get_dirname(t)
75       File.dirname(get_filename(t))
76     end
77
78     def open_dbs()
79       @dbs = Hash.new
80       keys = find_keys()
81       keys.each {|key| open_db(key) }
82     end
83
84     def find_keys()
85       files = []
86       Dir.glob(@glob){|f|
87         next if ! File.file?(f)
88         next if f =~ /.txt$/
89         files << f
90       }
91       keys = []
92       files.each {|f|
93         t = DB.win_to_unix(f)
94         t.sub!(%r|^#{@pre}|, "")
95         t.sub!(%r|#{@post}$|, "") if @post != ""
96         keys << t
97       }
98       keys
99     end
100
101     def close_db(t)
102       db = get(t)
103       return nil if db.nil?
104       db.close
105       @dbs.delete(t)
106     end
107
108     def open_db(t)
109       return nil if get(t) #すでにopenしていたら再openはしない。
110       begin
111         bdb = ADB.open(get_filename(t), nil, 0)
112         @dbs[t] = bdb if bdb != nil
113       rescue
114         p ["open error", get_filename(t)]; return nil
115       end
116       true
117     end
118
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       true
136     end
137
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       true
155     end
156
157     def remove_db(t) #tという名前のデータベースを消去する
158       db = get(t)
159       if db
160         db.close
161         @dbs.delete(t)
162       end
163       begin
164         File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
165       rescue
166         p ["unlink error", get_filename(t)]; return nil
167       end
168       dn = get_dirname(t)
169       Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
170       true
171     end
172
173     def to_num(s)
174       return s.to_i if s =~ /^\d+$/
175       s
176     end
177
178     def dump_db(t)
179       db = get(t)
180       return nil unless db
181       file = get_filename(t)
182       open("#{file}.txt", "w"){|out|
183         #        out.binmode.sync = true
184         ar = db.to_a
185         ar.map! {|k, v| [to_num(k), to_num(v)] }
186         ar.sort.each {|k, v|
187           out.printf("%s\t%s\n", k, v)
188         }
189       }
190       true
191     end
192
193     def each_db()  @dbs.to_a.sort.each {|t, db| yield(t, db) } end
194     def dump_all()  each_db {|t, db| dump_db(t) } end
195     def close_all() each_db {|t, db| db.close   } end
196     def keys() @dbs.keys end
197
198     def each(t)
199       return unless block_given?
200       db = @dbs[t]
201       return nil unless db
202       db.each {|k, v|
203         k = to_num(k)
204         v = to_num(v)
205         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
206         vv = get(t, k)  #p ["each", t, k, v, vv]
207         yield(k, vv)
208       }
209     end
210
211     def each_sort(t)
212       return unless block_given?
213       db = @dbs[t]
214       return nil unless db
215       ar = db.to_a
216       ar.map! {|k, v| [to_num(k), to_num(v)] }
217       ar.sort.each {|k, v|
218         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
219         vv = get(t, k)  #p ["each", t, k, v, vv]
220         yield(k, vv)
221       }
222     end
223
224     def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
225       db = @dbs[t]
226       return db if key.nil?
227       return nil unless db
228       db.myget(key)
229     end
230
231     def put(t, key, v) #tというデータベースのkeyにvをいれる
232       db = @dbs[t]
233       if db == nil
234         db = make_db(t) 
235         db = open_db(t) 
236         db = @dbs[t]
237       end
238       db.myput(key, v) #putする
239     end
240   end
241
242   class CharDB < DB # An Attribute DataBase.  Key is in UTF8-MCS.
243     include Singleton
244
245     def initialize()
246       super
247       dir = Config.instance.db_dir
248       @glob, @pre, @post = "#{dir}/system-char-id/*", "#{dir}/system-char-id/", ""
249       open_dbs()
250     end
251
252     def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
253       atrs = Hash.new
254       @dbs.each {|t, db|
255         v = get(t, u8)
256         atrs[t] = v if v != nil
257       }
258       atrs
259     end
260   end
261
262   class CodesysDB < DB # A CodeSystem DataBase.
263     include Singleton
264
265     def initialize()
266       super
267       dir = Config.instance.db_dir
268       @glob, @pre, @post = "#{dir}/*/system-char-id", "#{dir}/", "/system-char-id"
269       open_dbs()
270     end
271
272     #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
273     def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
274
275     def get_codesys(t)
276       db = get(t)
277       return nil unless db
278       return Codesys.new(t)
279     end
280   end
281
282   class Codesys < DB
283     def initialize(name)
284       #super
285       @name = name
286       @dbs = CodesysDB.instance
287     end
288
289     def keys() #どんなコードポイントの情報を持っているかの一覧
290       ks = @dbs.get(@name).keys
291 #      if @name =~ /jisx0208/ #特別処理
292 #       n = @dbs.get("=jis-x0208").keys 
293 #       #        p ["keys", @name, ks, n]
294 #       ks += n
295 #      end
296       ks.map! {|k| to_num(k) }
297       ks
298     end
299
300     def get(key)
301       v = @dbs.get(@name, key)
302       return v if v
303 #      if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
304 #       return @dbs.get("=jis-x0208", key)
305 #      end
306       return nil
307     end
308
309     def each()
310       return unless block_given?
311       db = @dbs.get(@name)
312       return nil unless db
313       db.each {|k, v|
314         k = to_num(k)
315         v = to_num(v)
316         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
317         vv = @dbs.get(@name, k) #p ["each", t, k, v, vv]
318         yield(k, vv)
319       }
320     end
321
322     def each_sort()
323       return unless block_given?
324       db = @dbs.get(@name)
325       return nil unless db
326       ar = db.to_a
327       ar.map! {|k, v| [to_num(k), to_num(v)] }
328       ar.sort.each {|k, v|
329         k.sub!(/^\?/, "") if k =~ /^\?/ #冒頭の?は取り除く
330         vv = @dbs.get(@name, k) #p ["each", t, k, v, vv]
331         yield(k, vv)
332       }
333     end
334   end
335
336   class JISX0208
337     def initialize
338       db = CodesysDB.instance
339       @common = db.get_codesys("=jis-x0208")
340       @newest = db.get_codesys("japanese-jisx0208-1990")
341     end
342
343     def get_char(code)
344       char = @common.get(code)
345       return char unless char.nil?
346       char = @newest.get(code)
347       return char unless char.nil?
348       return nil
349     end
350   end
351
352 end