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