update.
[chise/ruby.git] / chise / libchise_r.rb
1 # Copyright (C) 2002-2004 Kouichirou Eto, All rights reserved.
2 # libchise extension compatible library.
3
4 require "bdb"
5 require "pathname"
6 require "fileutils"
7 require "chise/config"
8 require "chise/path"
9
10 module CHISE
11   module ChiseValue; end
12   module TableAccessModule; end
13
14   module EachEntryModule
15     def each_entry(subdir)
16       #dir = @location + subdir
17       dir = DataSource::DB_DIR.path + subdir
18       dir.each_entry {|f|
19         next if f.to_s == "." || f.to_s == ".."
20         next if f.to_s =~ /\.txt\Z/
21         yield(f.unescape_win_filename.unescape.to_s)
22       }
23     end
24   end
25
26   class DataSource_R
27     NONE = 0
28     Berkeley_DB = 1
29     #DB_DIR = "/cygdrive/c/chise/chise-db"
30     DB_DIR = CHISE::DB_DIR
31
32     def initialize(type=Berkeley_DB, loc=DB_DIR, subtype=0, modemask=0755)
33       @type = type
34       #loc = Config.instance.db_dir if loc.nil?
35       @location = loc.path
36       @subtype = subtype
37       @modemask = modemask
38       @fdb = {}
39       @cdb = {}
40     end
41     attr_reader :type, :location, :subtype, :modemask
42
43     def close() end
44
45     def get_feature(f)
46       @fdb[f] = Feature.new(self, f) if @fdb[f].nil?
47       @fdb[f]
48     end
49
50     def get_ccs(ccs)
51       @cdb[ccs] = CCS.new(self, ccs) if @cdb[ccs].nil?
52       @cdb[ccs]
53     end
54
55     def each_feature_name
56       each_entry("character/feature") {|f| yield f }
57     end
58
59     def load_feature(cid, name)
60       feature = get_feature(name)
61       return nil if feature.nil?
62       feature.get_value(cid)
63     end
64
65     def decode_char(ccs, code_point)
66       ccst = get_ccs(ccs)
67       return nil if ccst.nil?
68       ccst.decode(code_point)
69     end
70
71     private
72     include EachEntryModule
73   end
74
75   class AttributeTable
76     def initialize(dir, cat, keytype, name, amask, mmask)
77       @dir, @cat, @keytype, @name, @amask, @mmask = dir, cat, keytype, name, amask, mmask
78       @db = nil
79       setup_db
80       at_exit {
81         close
82       }
83     end
84
85     def close
86       return if @db.nil?
87       begin
88         @db.sync
89         @db.close
90         # @db = nil
91       rescue => e
92         p e
93       end
94     end
95
96     def get(k)    setup_db; @db.get(k);    end
97     def put(k, v) setup_db; @db.put(k, v); end
98
99     def each() setup_db; @db.each {|k, v| yield(k, v) } end
100
101     private
102     def setup_db
103       return if @db
104       dbdir  = @dir + @cat + @keytype
105       path = dbdir + @name.path.escape.escape_win_filename
106       #TODO: should make dir.
107       if @amask == BDB::RDONLY
108         raise unless FileTest.exist?(path.to_s)
109       end
110       #qp path.to_s
111       @db = BDB::Hash.open(path.to_s, nil, @amask)
112     end
113   end
114
115   module TableAccessModule
116     def reset
117       @db = nil
118       @access = 0
119     end
120
121     def sync
122       @db.close if @db
123       reset
124       true
125     end
126     alias close sync
127
128     def setup_db(writable=nil)
129       if writable
130         sync if @access & BDB::CREATE == 0
131         access = BDB::CREATE
132       else
133         access = BDB::RDONLY
134       end
135
136       return true if @db
137
138       begin
139         db_dir = @ds.location
140         modemask = @ds.modemask
141       rescue
142         db_dir = CHISE::DataSource::DB_DIR.path
143         modemask = 0755
144       end
145
146       #qp db_dir, @category, @keyvalue, @name, @access, modemask
147       begin
148         @db = AttributeTable.new(db_dir, @category, @keyvalue,
149                                  @name, access, modemask)
150         return false if @db.nil?
151         @access = access
152       rescue => e
153         #puts $!, $@
154         @db = nil
155         return false
156       end
157       true
158     end
159   end
160
161   class Feature_R
162     include ChiseValue
163     include TableAccessModule
164
165     def initialize(ds, name)
166       @ds, @name = ds, name
167       @category, @keyvalue = "character", "feature"
168       reset
169     end
170
171     def get_value(cid)
172       setup_db
173       return nil if @db.nil?
174       @db.get(format_char_id(cid))
175     end
176
177     def set_value(cid, value)
178       setup_db(true)
179       raise "@db is nil." if @db.nil?
180       @db.put(format_char_id(cid), value)
181       true
182     end
183
184     def each_char
185       setup_db
186       raise "@db is nil." if @db.nil?
187       @db.each {|k, v|
188         yield(parse_c_string(k), v)
189       }
190     end
191   end
192
193   class CCS_R
194     include ChiseValue
195     include TableAccessModule
196
197     def initialize(ds, name)
198       @ds, @name = ds, name
199       @category, @keyvalue = "character", "by_feature"
200       reset
201     end
202
203     def set(code_point, cid)
204       setup_db(true)
205       raise "@db is nil." if @db.nil?
206       parse_c_string(@db.get(code_point.to_s))
207       @db.put(code_point.to_s, format_char_id(cid))
208       true
209     end
210
211     def decode(code_point)
212       setup_db
213       return nil if @db.nil?
214       parse_c_string(@db.get(code_point.to_s))
215     end
216
217     def set_decoded_char(code_point, cid)
218       setup_db(true)
219       raise "@db is nil." if @db.nil?
220       @db.put(code_point.to_s, format_char_id(cid))
221     end
222
223     def each_char
224       setup_db
225       if @db.nil?
226         #raise "@db is nil."+@name
227         p "@db is nil."+@name
228         return nil
229       end
230       @db.each {|code_point, cid|
231         yield(code_point, parse_c_string(cid))
232       }
233     end
234   end
235
236   module ChiseValue
237     def parse_c_string(str)
238       return nil if str.nil?
239
240       i = 0
241       c = str[i]
242       i += 1
243       len = str.length
244
245       raise unless 2 <= len && c == ?\?
246
247       c = str[i]
248       i += 1
249
250       if (c == ?\\)
251         raise if (len < 3)
252         c = str[i]
253         i += 1
254         if (c == ?^)
255           raise if (len < 4)
256           c = str[i]
257           i += 1
258           if c == ?\?
259             return 0x7F
260           else
261             return c & (0x80 | 0x1F)
262           end
263         end
264         # raise # ?
265       end
266
267       if ( c < 0xC0 )
268         cid = c
269         counter = 0
270       elsif ( c < 0xE0 )
271         cid = c & 0x1f
272         counter = 1
273       elsif ( c < 0xF0 )
274         cid = c & 0x0f
275         counter = 2
276       elsif ( c < 0xF8 )
277         cid = c & 0x07
278         counter = 3
279       elsif ( c < 0xFC )
280         cid = c & 0x03
281         counter = 4
282       else
283         cid = c & 0x01
284         counter = 5
285       end
286
287       if (counter + 2 <= len)
288         (0...counter).each {|j|
289           cid = (cid << 6) | (str[j + i] & 0x3F)
290         }
291         return cid
292       end
293
294       raise
295     end
296
297     def format_char_id(cid)
298       case cid
299       when ?\t  then return "?\t"
300       when ?\n  then return "?\n"
301       when ?\r  then return "?\r"
302       when 0x1C then return "?\^\\"
303       end
304
305       if cid <= 0x1F
306         return "?\\^"+(?@+cid).chr
307       elsif (cid == ?\s) || (cid == ?\") ||
308           (cid == ?\#) || (cid == ?\') ||
309           (cid == ?\() || (cid == ?\)) ||
310           (cid == ?\,) || (cid == ?\.) ||
311           (cid == ?\;) || (cid == ?\?) ||
312           (cid == ?\[) || (cid == ?\\) ||
313           (cid == ?\]) || (cid == ?\`)
314         return "?\\"+cid.chr
315       elsif (cid <= 0x7E)
316         return("?"+cid.chr)
317       elsif (cid == 0x7F)
318         return "?\\^?"+0.chr
319       elsif (cid <= 0x9F)
320         dest = "?\\^"
321         dest += (((cid + ?@) >> 6) | 0xC0).chr
322         dest += (((cid + ?@) & 0x3F) | 0x80).chr
323         return dest
324       elsif (cid <= 0x7FF)
325         dest = "?  "
326         dest[1] = (cid >> 6) | 0xC0
327         dest[2] = (cid & 0x3F) | 0x80
328         return dest
329       elsif (cid <= 0xFFFF)
330         dest = "?   "
331         dest[1] =  (cid >> 12) | 0xE0
332         dest[2] = ((cid >>  6) & 0x3F) | 0x80
333         dest[3] =  (cid        & 0x3F) | 0x80
334         return dest
335       elsif (cid <= 0x1FFFFF)
336         dest = "?    "
337         dest[1] =  (cid >> 18) | 0xF0
338         dest[2] = ((cid >> 12) & 0x3F) | 0x80
339         dest[3] = ((cid >>  6) & 0x3F) | 0x80
340         dest[4] =  (cid        & 0x3F) | 0x80
341         return dest
342       elsif (cid <= 0x3FFFFFF)
343         dest = "?     "
344         dest[1] =  (cid >> 24) | 0xF8
345         dest[2] = ((cid >> 18) & 0x3F) | 0x80
346         dest[3] = ((cid >> 12) & 0x3F) | 0x80
347         dest[4] = ((cid >>  6) & 0x3F) | 0x80
348         dest[5] =  (cid        & 0x3F) | 0x80
349         return dest
350       else
351         dest = "?      "
352         dest[1] =  (cid >> 30) | 0xFC
353         dest[2] = ((cid >> 24) & 0x3F) | 0x80
354         dest[3] = ((cid >> 18) & 0x3F) | 0x80
355         dest[4] = ((cid >> 12) & 0x3F) | 0x80
356         dest[5] = ((cid >>  6) & 0x3F) | 0x80
357         dest[6] =  (cid        & 0x3F) | 0x80
358         return dest
359       end
360       raise
361     end
362   end
363 end