42954c56e61b2bb1f21f371730de0fa8e83ef00f
[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       end
87 #     @db = BDB::Hash.open(path.to_s, nil, amask, mmask)
88       @db = BDB::Hash.open(path.to_s, nil, amask)
89       at_exit {
90         close
91       }
92     end
93
94     def close
95       return if @db.nil?
96       begin
97         @db.sync
98         @db.close
99         #p ["AttributeTable: close", @name]
100       rescue => e
101         #p e
102       end
103     end
104
105     def get(k)    @db.get(k);    end
106     def put(k, v) @db.put(k, v); end
107
108     def each() @db.each {|k, v| yield(k, v) } end
109   end
110
111   module TableAccessModule
112     def reset
113       @db = nil
114       @access = 0
115     end
116
117     def sync
118       @db.close if @db
119       reset
120     end
121     alias close sync
122
123     private
124     def setup_db(writable=nil)
125       if writable
126         sync if @access & BDB::CREATE == 0
127         @access = BDB::CREATE
128       else
129         @access = BDB::RDONLY
130       end
131
132       return if @db
133
134       begin
135         @db = AttributeTable.new(@ds.location, @category, @keyvalue,
136                                  @name, @access, @ds.modemask)
137       rescue => e
138         #qp e
139         @db = nil
140       end
141       #raise if @db.nil?
142     end
143   end
144
145   class FeatureTable
146     include ChiseValue
147     include TableAccessModule
148
149     def initialize(ds, name)
150       @ds, @name = ds, name
151       @category, @keyvalue = "character", "feature"
152       reset
153     end
154
155     def get_value(cid)
156       setup_db
157       return nil if @db.nil?
158       parse_value(@db.get(format_char_id(cid)))
159     end
160
161     def set_value(cid, value)
162       setup_db(true)
163       raise "@db is nil." if @db.nil?
164       @db.put(format_char_id(cid), value)
165     end
166
167     def each
168       setup_db
169       raise "@db is nil." if @db.nil?
170       @db.each {|k, v|
171         yield(parse_c_string(k), v)
172       }
173     end
174   end
175
176   class CCSTable
177     include ChiseValue
178     include TableAccessModule
179
180     def initialize(ds, name)
181       @ds, @name = ds, name
182       @category, @keyvalue = "character", "by_feature"
183       reset
184     end
185
186     def decode(code_point)
187       setup_db
188       return nil if @db.nil?
189       parse_c_string(@db.get(code_point.to_s))
190     end
191
192     def set_decoded_char(code_point, cid)
193       setup_db(true)
194       raise "@db is nil." if @db.nil?
195       @db.put(code_point.to_s, format_char_id(cid))
196     end
197
198     def each
199       setup_db
200       raise "@db is nil." if @db.nil?
201       @db.each {|k, v|
202         yield(parse_value(k), parse_c_string(v))
203       }
204     end
205   end
206
207   module ChiseValue
208     def parse_value(v)
209       return v if v.nil?
210       #return v if v.kind_of?(Integer)
211       return v.to_i if /\A\d+\Z/ =~ v # number?
212       return $1 if /\A"(.+)"\Z/ =~ v # remove surrounding "
213       #return v.sub(/\A\?/, "") if v =~ /\A\?/ # remove ? in the head
214       #return parse_sexp(v) if v =~ /\A\(.+\)\Z/ # parse sexp # not yet
215       v
216     end
217
218     def parse_c_string(str)
219       return nil if str.nil?
220
221       i = 0
222       c = str[i]
223       i += 1
224       len = str.length
225
226       raise unless 2 <= len && c == ?\?
227
228       c = str[i]
229       i += 1
230
231       if (c == ?\\)
232         raise if (len < 3)
233         c = str[i]
234         i += 1
235         if (c == ?^)
236           raise if (len < 4)
237           c = str[i]
238           i += 1
239           if c == ?\?
240             return 0x7F
241           else
242             return c & (0x80 | 0x1F)
243           end
244         end
245         # raise # ?
246       end
247
248       if ( c < 0xC0 )
249         cid = c
250         counter = 0
251       elsif ( c < 0xE0 )
252         cid = c & 0x1f
253         counter = 1
254       elsif ( c < 0xF0 )
255         cid = c & 0x0f
256         counter = 2
257       elsif ( c < 0xF8 )
258         cid = c & 0x07
259         counter = 3
260       elsif ( c < 0xFC )
261         cid = c & 0x03
262         counter = 4
263       else
264         cid = c & 0x01
265         counter = 5
266       end
267
268       if (counter + 2 <= len)
269         (0...counter).each {|j|
270           cid = (cid << 6) | (str[j + i] & 0x3F)
271         }
272         return cid
273       end
274
275       raise
276     end
277
278     def format_char_id(cid)
279       case cid
280       when ?\t  then return "?\t"
281       when ?\n  then return "?\n"
282       when ?\r  then return "?\r"
283       when 0x1C then return "?\^\\"
284       end
285
286       if cid <= 0x1F
287         return "?\\^"+(?@+cid).chr
288       elsif (cid == ?\s) || (cid == ?\") ||
289           (cid == ?\#) || (cid == ?\') ||
290           (cid == ?\() || (cid == ?\)) ||
291           (cid == ?\,) || (cid == ?\.) ||
292           (cid == ?\;) || (cid == ?\?) ||
293           (cid == ?\[) || (cid == ?\\) ||
294           (cid == ?\]) || (cid == ?\`)
295         return "?\\"+cid.chr
296       elsif (cid <= 0x7E)
297         return("?"+cid.chr)
298       elsif (cid == 0x7F)
299         return "?\\^?"+0.chr
300       elsif (cid <= 0x9F)
301         dest = "?\\^"
302         dest += (((cid + ?@) >> 6) | 0xC0).chr
303         dest += (((cid + ?@) & 0x3F) | 0x80).chr
304         return dest
305       elsif (cid <= 0x7FF)
306         dest = "?  "
307         dest[1] = (cid >> 6) | 0xC0
308         dest[2] = (cid & 0x3F) | 0x80
309         return dest
310       elsif (cid <= 0xFFFF)
311         dest = "?   "
312         dest[1] =  (cid >> 12) | 0xE0
313         dest[2] = ((cid >>  6) & 0x3F) | 0x80
314         dest[3] =  (cid        & 0x3F) | 0x80
315         return dest
316       elsif (cid <= 0x1FFFFF)
317         dest = "?    "
318         dest[1] =  (cid >> 18) | 0xF0
319         dest[2] = ((cid >> 12) & 0x3F) | 0x80
320         dest[3] = ((cid >>  6) & 0x3F) | 0x80
321         dest[4] =  (cid        & 0x3F) | 0x80
322         return dest
323       elsif (cid <= 0x3FFFFFF)
324         dest = "?     "
325         dest[1] =  (cid >> 24) | 0xF8
326         dest[2] = ((cid >> 18) & 0x3F) | 0x80
327         dest[3] = ((cid >> 12) & 0x3F) | 0x80
328         dest[4] = ((cid >>  6) & 0x3F) | 0x80
329         dest[5] =  (cid        & 0x3F) | 0x80
330         return dest
331       else
332         dest = "?      "
333         dest[1] =  (cid >> 30) | 0xFC
334         dest[2] = ((cid >> 24) & 0x3F) | 0x80
335         dest[3] = ((cid >> 18) & 0x3F) | 0x80
336         dest[4] = ((cid >> 12) & 0x3F) | 0x80
337         dest[5] = ((cid >>  6) & 0x3F) | 0x80
338         dest[6] =  (cid        & 0x3F) | 0x80
339         return dest
340       end
341       raise
342     end
343   end
344 end