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         @db = nil
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       true
121     end
122     alias close sync
123
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 true if @db
133
134       begin
135         db_dir = @ds.location
136         modemask = @ds.modemask
137       rescue
138         db_dir = CHISE::DataSource::DB_DIR.path
139         modemask = 0755
140       end
141
142       #qp db_dir, @category, @keyvalue, @name, @access, modemask
143       begin
144         @db = AttributeTable.new(db_dir, @category, @keyvalue,
145                                  @name, access, modemask)
146         return false if @db.nil?
147         @access = access
148       rescue => e
149         #puts $!, $@
150         @db = nil
151         return false
152       end
153       true
154     end
155   end
156
157   class Feature_R
158     include ChiseValue
159     include TableAccessModule
160
161     def initialize(ds, name)
162       @ds, @name = ds, name
163       @category, @keyvalue = "character", "feature"
164       reset
165     end
166
167     def get_value(cid)
168       setup_db
169       return nil if @db.nil?
170       @db.get(format_char_id(cid))
171     end
172
173     def set_value(cid, value)
174       setup_db(true)
175       raise "@db is nil." if @db.nil?
176       @db.put(format_char_id(cid), value)
177       true
178     end
179
180     def each_char
181       setup_db
182       raise "@db is nil." if @db.nil?
183       @db.each {|k, v|
184         yield(parse_c_string(k), v)
185       }
186     end
187   end
188
189   class CCS_R
190     include ChiseValue
191     include TableAccessModule
192
193     def initialize(ds, name)
194       @ds, @name = ds, name
195       @category, @keyvalue = "character", "by_feature"
196       reset
197     end
198
199     def set(code_point, cid)
200       setup_db(true)
201       raise "@db is nil." if @db.nil?
202       parse_c_string(@db.get(code_point.to_s))
203       @db.put(code_point.to_s, format_char_id(cid))
204       true
205     end
206
207     def decode(code_point)
208       setup_db
209       return nil if @db.nil?
210       parse_c_string(@db.get(code_point.to_s))
211     end
212
213     def set_decoded_char(code_point, cid)
214       setup_db(true)
215       raise "@db is nil." if @db.nil?
216       @db.put(code_point.to_s, format_char_id(cid))
217     end
218
219     def each_char
220       setup_db
221       if @db.nil?
222         #raise "@db is nil."+@name
223         p "@db is nil."+@name
224         return nil
225       end
226       @db.each {|code_point, cid|
227         yield(code_point, parse_c_string(cid))
228       }
229     end
230   end
231
232   module ChiseValue
233     def parse_c_string(str)
234       return nil if str.nil?
235
236       i = 0
237       c = str[i]
238       i += 1
239       len = str.length
240
241       raise unless 2 <= len && c == ?\?
242
243       c = str[i]
244       i += 1
245
246       if (c == ?\\)
247         raise if (len < 3)
248         c = str[i]
249         i += 1
250         if (c == ?^)
251           raise if (len < 4)
252           c = str[i]
253           i += 1
254           if c == ?\?
255             return 0x7F
256           else
257             return c & (0x80 | 0x1F)
258           end
259         end
260         # raise # ?
261       end
262
263       if ( c < 0xC0 )
264         cid = c
265         counter = 0
266       elsif ( c < 0xE0 )
267         cid = c & 0x1f
268         counter = 1
269       elsif ( c < 0xF0 )
270         cid = c & 0x0f
271         counter = 2
272       elsif ( c < 0xF8 )
273         cid = c & 0x07
274         counter = 3
275       elsif ( c < 0xFC )
276         cid = c & 0x03
277         counter = 4
278       else
279         cid = c & 0x01
280         counter = 5
281       end
282
283       if (counter + 2 <= len)
284         (0...counter).each {|j|
285           cid = (cid << 6) | (str[j + i] & 0x3F)
286         }
287         return cid
288       end
289
290       raise
291     end
292
293     def format_char_id(cid)
294       case cid
295       when ?\t  then return "?\t"
296       when ?\n  then return "?\n"
297       when ?\r  then return "?\r"
298       when 0x1C then return "?\^\\"
299       end
300
301       if cid <= 0x1F
302         return "?\\^"+(?@+cid).chr
303       elsif (cid == ?\s) || (cid == ?\") ||
304           (cid == ?\#) || (cid == ?\') ||
305           (cid == ?\() || (cid == ?\)) ||
306           (cid == ?\,) || (cid == ?\.) ||
307           (cid == ?\;) || (cid == ?\?) ||
308           (cid == ?\[) || (cid == ?\\) ||
309           (cid == ?\]) || (cid == ?\`)
310         return "?\\"+cid.chr
311       elsif (cid <= 0x7E)
312         return("?"+cid.chr)
313       elsif (cid == 0x7F)
314         return "?\\^?"+0.chr
315       elsif (cid <= 0x9F)
316         dest = "?\\^"
317         dest += (((cid + ?@) >> 6) | 0xC0).chr
318         dest += (((cid + ?@) & 0x3F) | 0x80).chr
319         return dest
320       elsif (cid <= 0x7FF)
321         dest = "?  "
322         dest[1] = (cid >> 6) | 0xC0
323         dest[2] = (cid & 0x3F) | 0x80
324         return dest
325       elsif (cid <= 0xFFFF)
326         dest = "?   "
327         dest[1] =  (cid >> 12) | 0xE0
328         dest[2] = ((cid >>  6) & 0x3F) | 0x80
329         dest[3] =  (cid        & 0x3F) | 0x80
330         return dest
331       elsif (cid <= 0x1FFFFF)
332         dest = "?    "
333         dest[1] =  (cid >> 18) | 0xF0
334         dest[2] = ((cid >> 12) & 0x3F) | 0x80
335         dest[3] = ((cid >>  6) & 0x3F) | 0x80
336         dest[4] =  (cid        & 0x3F) | 0x80
337         return dest
338       elsif (cid <= 0x3FFFFFF)
339         dest = "?     "
340         dest[1] =  (cid >> 24) | 0xF8
341         dest[2] = ((cid >> 18) & 0x3F) | 0x80
342         dest[3] = ((cid >> 12) & 0x3F) | 0x80
343         dest[4] = ((cid >>  6) & 0x3F) | 0x80
344         dest[5] =  (cid        & 0x3F) | 0x80
345         return dest
346       else
347         dest = "?      "
348         dest[1] =  (cid >> 30) | 0xFC
349         dest[2] = ((cid >> 24) & 0x3F) | 0x80
350         dest[3] = ((cid >> 18) & 0x3F) | 0x80
351         dest[4] = ((cid >> 12) & 0x3F) | 0x80
352         dest[5] = ((cid >>  6) & 0x3F) | 0x80
353         dest[6] =  (cid        & 0x3F) | 0x80
354         return dest
355       end
356       raise
357     end
358   end
359 end