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