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