e43ee16e2e6c2bdffbc7833d3d190b1417783257
[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       dir = @location + "character/feature"
40       dir.each_entry {|f|
41         next if f.to_s == "." || f.to_s == ".."
42         f = f.unescape_win_filename
43         f = f.unescape
44         yield(f.to_s)
45       }
46     end
47
48     def load_feature(name, cid)
49       ft = get_feature(name)
50       return nil if ft.nil?
51       ft.get_value(cid)
52     end
53
54     def decode_char(ccs, code_point)
55       ct = get_ccs(ccs)
56       return nil if ct.nil?
57       ct.decode(code_point)
58     end
59   end
60
61   class FeatureTable
62     include ChiseValue
63     include TableAccessModule
64
65     def set_value(cid, value)
66       setup_db(true)
67       return nil if @db.nil?
68       @db.put(format_char_id(cid), value)
69     end
70
71     def get_value(cid)
72       setup_db
73       return nil if @db.nil?
74       @db.get(format_char_id(cid))
75     end
76
77     def each
78       setup_db
79       return nil if @db.nil?
80       @db.each {|k, v|
81         yield(parse_c_string(k), v)
82       }
83     end
84
85     private
86     def setup_db(writable=nil)
87       setup_db_exec(writable, "character", "feature")
88     end
89   end
90
91   class CCSTable
92     include ChiseValue
93     include TableAccessModule
94
95     def decode(code_point)
96       setup_db
97       return nil if @db.nil?
98       v = @db.get(code_point.to_s)
99       return nil if v.nil?
100       parse_c_string(v)
101     end
102
103     def set_decoded_char(code_point, cid)
104       setup_db(true)
105       return nil if @db.nil?
106       @db.put(code_point.to_s, format_char_id(cid))
107     end
108
109     private
110     def setup_db(writable=nil)
111       setup_db_exec(writable, "character", "by_feature")
112     end
113   end
114
115   module TableAccessModule
116     def initialize(ds, name)
117       @ds, @name = ds, name
118       @db = nil
119       @access = 0
120     end
121
122     def sync
123       @db.close if @db
124       @db = nil
125       @access = 0
126     end
127     alias close sync
128
129     def setup_db_exec(writable, cat, key)
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, cat, key,
141                                  @name, @access, @ds.modemask)
142       rescue
143         @db = nil
144       end
145       #raise if @db.nil?
146     end
147   end
148
149   class AttributeTable
150     def initialize(dir, cat, keytype, name, amask, mmask)
151       dbdir  = dir + cat + keytype
152       #FileUtils.mkdir_p(dbdir.to_s) unless dbdir.directory?
153       path = dbdir + name.path.escape.escape_win_filename
154 #     qp path, amask, mmask
155       raise unless path.exist?
156 #     @db = BDB::Hash.open(path.to_s, amask, mmask)
157       @db = BDB::Hash.open(path.to_s)
158       at_exit {
159         close
160       }
161     end
162
163     def close
164       return if @db.nil?
165       begin
166         @db.sync
167         @db.close
168       rescue
169       end
170     end
171
172     def get(k)    @db.get(k);    end
173     def put(k, v) @db.put(k, v); end
174     def each() @db.each {|k, v| yield(k, v) } end
175   end
176
177   module ChiseValue
178     def parse_c_string(str)
179       i = 0
180       c = str[i]
181       i += 1
182       len = str.length
183
184       raise unless 2 <= len && c == ?\?
185
186       c = str[i]
187       i += 1
188
189       if (c == ?\\)
190         raise if (len < 3)
191         c = str[i]
192         i += 1
193         if (c == ?^)
194           raise if (len < 4)
195           c = str[i]
196           i += 1
197           if c == ?\?
198             return 0x7F
199           else
200             return c & (0x80 | 0x1F)
201           end
202         end
203         # raise # ?
204       end
205
206       if ( c < 0xC0 )
207         cid = c
208         counter = 0
209       elsif ( c < 0xE0 )
210         cid = c & 0x1f
211         counter = 1
212       elsif ( c < 0xF0 )
213         cid = c & 0x0f
214         counter = 2
215       elsif ( c < 0xF8 )
216         cid = c & 0x07
217         counter = 3
218       elsif ( c < 0xFC )
219         cid = c & 0x03
220         counter = 4
221       else
222         cid = c & 0x01
223         counter = 5
224       end
225
226       if (counter + 2 <= len)
227         (0...counter).each {|j|
228           cid = (cid << 6) | (str[j + i] & 0x3F)
229         }
230         return cid
231       end
232
233       raise
234     end
235
236     def format_char_id(cid)
237       case cid
238       when ?\t  then return "?\t"
239       when ?\n  then return "?\n"
240       when ?\r  then return "?\r"
241       when 0x1C then return "?\^\\"
242       end
243
244       if cid <= 0x1F
245         return "?\\^"+(?@+cid).chr
246       elsif (cid == ?\s) || (cid == ?\") ||
247           (cid == ?\#) || (cid == ?\') ||
248           (cid == ?\() || (cid == ?\)) ||
249           (cid == ?\,) || (cid == ?\.) ||
250           (cid == ?\;) || (cid == ?\?) ||
251           (cid == ?\[) || (cid == ?\\) ||
252           (cid == ?\]) || (cid == ?\`)
253         return "?\\"+cid.chr
254       elsif (cid <= 0x7E)
255         return("?"+cid.chr)
256       elsif (cid == 0x7F)
257         return "?\\^?"+0.chr
258       elsif (cid <= 0x9F)
259         dest = "?\\^"
260         dest += (((cid + ?@) >> 6) | 0xC0).chr
261         dest += (((cid + ?@) & 0x3F) | 0x80).chr
262         return dest
263       elsif (cid <= 0x7FF)
264         dest = "?  "
265         dest[1] = (cid >> 6) | 0xC0
266         dest[2] = (cid & 0x3F) | 0x80
267         return dest
268       elsif (cid <= 0xFFFF)
269         dest = "?   "
270         dest[1] =  (cid >> 12) | 0xE0
271         dest[2] = ((cid >>  6) & 0x3F) | 0x80
272         dest[3] =  (cid        & 0x3F) | 0x80
273         return dest
274       elsif (cid <= 0x1FFFFF)
275         dest = "?    "
276         dest[1] =  (cid >> 18) | 0xF0
277         dest[2] = ((cid >> 12) & 0x3F) | 0x80
278         dest[3] = ((cid >>  6) & 0x3F) | 0x80
279         dest[4] =  (cid        & 0x3F) | 0x80
280         return dest
281       elsif (cid <= 0x3FFFFFF)
282         dest = "?     "
283         dest[1] =  (cid >> 24) | 0xF8
284         dest[2] = ((cid >> 18) & 0x3F) | 0x80
285         dest[3] = ((cid >> 12) & 0x3F) | 0x80
286         dest[4] = ((cid >>  6) & 0x3F) | 0x80
287         dest[5] =  (cid        & 0x3F) | 0x80
288         return dest
289       else
290         dest = "?      "
291         dest[1] =  (cid >> 30) | 0xFC
292         dest[2] = ((cid >> 24) & 0x3F) | 0x80
293         dest[3] = ((cid >> 18) & 0x3F) | 0x80
294         dest[4] = ((cid >> 12) & 0x3F) | 0x80
295         dest[5] = ((cid >>  6) & 0x3F) | 0x80
296         dest[6] =  (cid        & 0x3F) | 0x80
297         return dest
298       end
299       raise
300     end
301   end
302 end