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