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