i
[chise/ruby.git] / chise / ids.rb
1 # Copyright (C) 2002-2004 Kouichirou Eto, All rights reserved.
2
3 $LOAD_PATH << "../../lib" if $0 == __FILE__
4 require "chise/db"
5
6 module CHISE
7 #  IDC_LEFT_TO_RIGHT = "⿰"
8 #  IDC_ABOVE_TO_BELOW = "⿱"
9 #  IDC_LEFT_TO_MIDDLE_AND_RIGHT = "⿲"
10 #  IDC_ABOVE_TO_MIDDLE_AND_BELOW = "⿳"
11 #  IDC_FULL_SURROUND = "⿴"
12 #  IDC_SURROUND_FROM_ABOVE = "⿵"
13 #  IDC_SURROUND_FROM_BELOW = "⿶"
14 #  IDC_SURROUND_FROM_LEFT = "⿷"
15 #  IDC_SURROUND_FROM_UPPER_LEFT = "⿸"
16 #  IDC_SURROUND_FROM_UPPER_RIGHT = "⿹"
17 #  IDC_SURROUND_FROM_LOWER_LEFT = "⿺"
18 #  IDC_OVERLAID = "⿻"
19
20   IDC_LEFT_TO_RIGHT = "\342\277\260" #2FF0
21   IDC_ABOVE_TO_BELOW = "\342\277\261"
22   IDC_LEFT_TO_MIDDLE_AND_RIGHT = "\342\277\262"
23   IDC_ABOVE_TO_MIDDLE_AND_BELOW = "\342\277\263"
24   IDC_FULL_SURROUND = "\342\277\264" #2FF4
25   IDC_SURROUND_FROM_ABOVE = "\342\277\265"
26   IDC_SURROUND_FROM_BELOW = "\342\277\266"
27   IDC_SURROUND_FROM_LEFT = "\342\277\267"
28   IDC_SURROUND_FROM_UPPER_LEFT = "\342\277\270"
29   IDC_SURROUND_FROM_UPPER_RIGHT = "\342\277\271"
30   IDC_SURROUND_FROM_LOWER_LEFT = "\342\277\272"
31   IDC_OVERLAID = "\342\277\273"
32
33   IDC_LR  = IDC_LEFT_TO_RIGHT
34   IDC_AB  = IDC_ABOVE_TO_BELOW
35   IDC_LM  = IDC_LEFT_TO_MIDDLE_AND_RIGHT
36   IDC_AM  = IDC_ABOVE_TO_MIDDLE_AND_BELOW
37   IDC_FS  = IDC_FULL_SURROUND
38   IDC_FA  = IDC_SURROUND_FROM_ABOVE
39   IDC_FB  = IDC_SURROUND_FROM_BELOW
40   IDC_FL  = IDC_SURROUND_FROM_LEFT
41   IDC_UL  = IDC_SURROUND_FROM_UPPER_LEFT
42   IDC_UR  = IDC_SURROUND_FROM_UPPER_RIGHT
43   IDC_LL  = IDC_SURROUND_FROM_LOWER_LEFT
44   IDC_OV  = IDC_OVERLAID
45
46   IDC_LMR = IDC_LM
47   IDC_AMB = IDC_AM
48   IDC_FUL = IDC_UL
49   IDC_FUR = IDC_UR
50   IDC_FLL = IDC_LL
51   IDC_O   = IDC_OV
52
53   class IDS_TEXT_DB < DB #======================================================================
54     include Singleton
55     IDS_LIST = "
56 IDS-UCS-Basic.txt
57 #IDS-UCS-Compat-Supplement.txt
58 #IDS-UCS-Compat.txt
59 IDS-UCS-Ext-A.txt
60 IDS-UCS-Ext-B-1.txt
61 IDS-UCS-Ext-B-2.txt
62 IDS-UCS-Ext-B-3.txt
63 IDS-UCS-Ext-B-4.txt
64 IDS-UCS-Ext-B-5.txt
65 IDS-UCS-Ext-B-6.txt
66 IDS-JIS-X0208-1990.txt
67 IDS-Daikanwa-01.txt
68 IDS-Daikanwa-02.txt
69 IDS-Daikanwa-03.txt
70 IDS-Daikanwa-04.txt
71 IDS-Daikanwa-05.txt
72 IDS-Daikanwa-06.txt
73 IDS-Daikanwa-07.txt
74 IDS-Daikanwa-08.txt
75 IDS-Daikanwa-09.txt
76 IDS-Daikanwa-10.txt
77 IDS-Daikanwa-11.txt
78 IDS-Daikanwa-12.txt
79 IDS-Daikanwa-dx.txt
80 IDS-Daikanwa-ho.txt
81 IDS-CBETA.txt
82 ".split
83     def initialize()
84       super
85       @ids_list = IDS_LIST
86       @chars = []
87
88       @dir = Config.instance.ids_dir
89       
90       @glob, @pre, @post = "#{@dir}/db/*", "#{@dir}/db/", ""
91       dir = File.dirname(@pre)
92       Dir.mkdir(dir) unless FileTest.exist?(dir)
93       open_dbs()
94     end
95
96     def each_file()
97       return unless block_given?
98       @ids_list.each {|file|
99         next if file =~ /^#/
100         yield(@dir+file)
101       }
102     end
103
104     def each_line(file)
105       open(file){|f|
106         while line = f.gets
107           next if line =~ /^;/ #コメントはとばす
108           line.chomp!
109           code, char, ids = line.split
110           yield(code, char, ids)
111         end
112       }
113     end
114     def dump_text_all
115       each_file {|file|
116         dir = File.dirname(file) + "/../ids-new/"
117         Dir.mkdir(dir) if ! FileTest.directory?(dir)
118         newfile = dir + File.basename(file)
119         p [file, newfile]
120         open(newfile, "w"){|out|
121           out.binmode.sync = true
122           each_line(file){|code, ch, ids|
123             char = Character.get(ch)
124             ids = char.decompose
125             out.print "#{code}  #{ch}   #{ids}\n"
126           }
127         }
128       }
129     end
130     def make_ids_error
131       each_file {|file|
132         dir = File.dirname(file) + "/../ids-error"
133         Dir.mkdir(dir) unless FileTest.exist?(dir)
134         errfile = dir + "/" + File.basename(file)
135         #       p [file, errfile]
136         open(errfile, "w"){|out|
137           out.binmode.sync = true
138           each_line(file){|code, ch, ids|
139             char = Character.get(ch)
140             ids_error = char["ids-error"]
141             next if ids_error.nil?
142             out.print "#{code}  #{ch}   #{ids}  #{ids_error}\n"
143           }
144         }
145       }
146     end
147   end
148
149   class IDS_DB < DB # BDB化したIDS DBを扱う
150     include Singleton
151     def initialize
152       @dbs = CharDB.instance
153     end
154     def make_ids_db
155       db = IDS_TEXT_DB.instance
156       db.each_file {|file|
157         @char_counter = 0
158         @same_ids_counter = 0
159         @good_ids_counter = 0
160         @conflict_ids_counter = 0
161         db.each_line(file){|code, ch, ids|
162           @char_counter += 1
163
164           ids = "" if ids == nil
165           next if ids == "" #IDSが定義されていない場合は、さっくりと無視するべしよ。
166
167           charimg = Character.get(ch) #実体参照である可能性がある
168
169           next if code =~ /'$/ || code =~ /"$/ #大漢和番号のダッシュ付きは無視する
170           char = Character.get("&"+code+";") #code表記を元に実体参照を作って解釈する
171           if char.nil? || char.to_s == "" #うまく文字にならなかった
172             print "char == null #{char.inspect} #{code} #{ch}   #{ids}\n" unless code =~ /^M-/ || code =~ /^CB/
173             #大漢和、CBETA以外の場合は、エラーメッセージ。
174             next
175           end
176           if char != charimg #code表記と文字が一致していない?
177             unless code =~ /^M-/ || code =~ /^MH-/ || code =~ /^CB/ #食い違っていて当然であるので何もしない
178               print "unknown char       #{char.inspect} #{code} #{ch}   #{ids}\n"
179               next #それ以外の場合はエラーメッセージをだして、次へ。
180             end
181           end
182           #next if !char.has_attribute? #isolated characterはまぎれこませない。
183
184           ids.de_er! #実体参照を解除する
185           next if ids == char.to_s #もし文字とまったく一緒なら、意味が無いので情報を持たない
186           next if ids.char_length == 1
187
188           idstree = IDS_Tree.new(ids)
189           c = idstree.check_integrity
190           c = "contains self" if ids.include?(char.to_s)
191           if c #ちょっとでもエラーがある場合は、
192             char["ids-error"] = c #エラーを記録して、データとしては保持しない
193             next
194           end
195
196           if char["ids"].nil? || char["ids"] == "" #元々IDSが無かった場合は、
197             char["ids"] = ids #普通に代入すればそれでいいです。
198             @good_ids_counter += 1
199           else #しかしいままでにすでにIDSが定義されていた場合は?
200             if char["ids"] == ids #新しいIDSと古いIDSが完全に一致するなら無視しましょう。
201               @same_ids_counter += 1
202             else #しかしいままでのIDSと新しいIDSが食い違った場合は?
203               @conflict_ids_counter += 1
204               #       print "conflict   #{char.inspect} #{code} #{ids}  #{char["ids"]}\n"
205             end
206           end
207         }
208         print "#{file}  #{@char_counter}        #{@same_ids_counter}    #{@conflict_ids_counter}        #{@good_ids_counter}\n"
209         CharacterFactory.instance.reset()
210       }
211       @dbs.dump_db("ids-error") #テキスト化する
212       @dbs.dump_db("ids") #テキスト化する
213     end
214     def make_ids_reverse
215       h = Hash.new
216       @dbs.each("ids") {|k, v|
217         char = k.char
218         ids = char.decompose
219         h[ids] = "" if h[ids].nil?
220         h[ids] += k #追加する
221       }
222       h.each {|k, v|
223         h[k] = char_sort(v) #文字の順番を、よく使うっぽいものからの順番にする
224       }
225       h.delete_if {|k, v| #h[k]が""になる可能性もあるが、それはkeyとして入れないことにする。
226         v == ""
227       }
228       print "length     #{h.length}\n"
229       cdb = CodesysDB.instance
230       cdb.make_db_no_question_mark("ids", h)
231       cdb.open_db("ids") #これが無いと、dump_dbされません。
232       cdb.dump_db("ids")
233     end
234     def char_sort(composed)
235       return composed if composed.char_length == 1
236       ar = composed.to_a
237       arorg = ar.dup
238       ar2 = []
239       ar.dup.each {|ch|
240         char = ch.char
241         if char.char_id < 0xfffff #Unicodeっぽい?
242           ar2 << ch
243           ar.delete(ch)
244         end
245       }
246       if 0 < ar.length
247         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
248           ar.each {|ch|
249             char = ch.char
250             v = char[codesys]
251             #       p [codesys, v] if v
252             if v #EntityReferenceの順番に準拠する。
253               ar2 << ch
254               ar.delete(ch)
255             end
256           }
257         }
258       end
259       if 0 < ar.length
260         #       p ["yokuwakaran character", ar, ar[0].inspect_all, arorg]
261         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
262           ar.dup.each {|ch|
263             char = ch.char
264             v = char[codesys]
265             #       p [codesys, v] if v
266           }
267         }
268       end
269       return ar2.join("")
270     end
271     def dump_ids_duplicated
272       open("ids-duplicated.txt", "w"){|out|
273         #out.binmode
274         CodesysDB.instance.each("ids") {|k, v|
275           if v.nil?
276             out.print "nil      #{k}    #{v}\n"
277             next
278           end
279           n = v.char_length
280           next if n == 1
281           out.print "#{n}       #{k}    #{v}"
282           v.each_char {|ch|
283             char = ch.char
284             out.print " #{char.inspect}"
285           }
286           out.print "\n"
287         }
288       }
289     end
290     def make_ids_aggregated
291       @dbs.each("ids") {|k, v|
292         char = k.char
293         ids = char.decompose
294         ag = ids.aggregate
295         char["ids-aggregated"] = ag
296       }
297       @dbs.dump_db("ids-aggregated")
298     end
299     def dump_ids_aggregated
300       open("ids-aggregated.txt", "w"){|out|
301         #out.binmode
302         @dbs.each("ids") {|k, v|
303           char = k.char
304           ids = char["ids"]
305           ag  = char["ids-aggregated"]
306           out.print "#{char.to_s}       #{ag}   #{ids}\n" if ids != ag
307         }
308       }
309     end
310     def make_ids_parts
311       @dbs.each("ids") {|k, v|
312         char = k.char
313         pids = char.to_s
314         ar = []
315         counter = 0
316         loop {
317           ids = pids.decompose
318           break if ids == pids #これ以上分割できないようだったら終了〜。
319           ar += ids.to_a
320           counter += 1
321           p [char.to_s, pids, ids, ar] if 10 < counter #これは何かおかしいぞと
322           pids = ids
323         }
324         ar.sort!
325         ar.uniq!
326         #やっぱりIDS文字も加えることにする. by eto 2003-02-05
327         #       ar.delete_if {|ch|
328         #         ch.char.is_ids? #IDS文字はまぎれこませない。
329         #       }
330         str = ar.join("")
331         char["ids-parts"] = str
332       }
333       @dbs.dump_db("ids-parts")
334     end
335     def make_ids_contained
336       h = Hash.new
337       @dbs.each("ids-parts") {|k, v|
338         char = k.char
339         parts = char.ids_parts
340         parts.each_char {|ch|
341           #       part = ch.char
342           h[ch] = [] if h[ch].nil?
343           h[ch] << k
344           #       h[ch] += k
345           #       part["ids-contained"] = "" if part["ids-contained"].nil?
346           #       part["ids-contained"] += k
347         }
348       }
349       h.each {|k, v|
350         char = k.char
351         v.sort!
352         char["ids-contained"] = v.join("")
353         
354       }
355       @dbs.dump_db("ids-contained")
356     end
357     def make_ids_decomposed
358       @dbs.each("ids") {|k, v|
359         char = k.char
360         de= char.decompose_all
361         char["ids-decomposed"] = de
362       }
363       @dbs.dump_db("ids-decomposed")
364     end
365   end
366
367   class Node < Array #==================================木構造の中の一つの枝
368     def initialize(nodeleaf=nil, nodenum=nil)
369       super()
370       @nodeleaf = nodeleaf
371       @nodenum = nodenum
372       if @nodeleaf
373         original_add(@nodeleaf)
374       end
375     end
376     attr_reader :nodenum
377     alias original_add <<
378       private :original_add
379     def <<(obj)
380       original_add(obj)
381       @nodenum -= 1 if @nodenum
382     end
383     def nodes
384       ar = []
385       ar << self.to_s
386       self.each {|n|
387         ar += n.nodes if n.is_a? Node
388       }
389       return ar
390     end
391   end
392
393   class Tree #==================================================木構造を扱う
394     def initialize()
395       @root = Node.new()
396       @stack = [@root]
397       @leafnum = 0
398       @depth = 1 #stackの深さが最大になったところの値、木構造が無いときは1となる
399     end
400     def depth() @depth - 1 end
401     def add_node(nodeleaf=nil, nodenum=nil) #枝を追加
402       new_node = Node.new(nodeleaf, nodenum)
403       @stack.last << new_node
404       @stack << new_node
405       if @depth < @stack.length
406         @depth = @stack.length
407       end
408       self
409     end
410     def end_node() #この枝は終り
411       @stack.pop
412       self
413     end
414     def add_leaf(a) #葉を追加
415       @stack.last << a
416       end_check()
417       self
418     end
419     def end_check()
420       n = @stack.last.nodenum
421       if n && n == 0
422         end_node()
423         end_check() #再帰
424       end
425     end
426     def check_integrity
427       n = @stack.last.nodenum
428       return nil if @root.length == 0 #no tree is good tree
429       return "unmatch leaves" if n && n != 0
430       return "extra nodes" if @root.first.is_a?(Node) && @root.length != 1
431       return "extra leaves" if @root.length != 1
432       return nil
433     end
434     def nodes
435       r = @root.nodes
436       r.shift
437       r
438     end
439     def sub_nodes
440       r = nodes
441       r.shift
442       r
443     end
444     def to_s()    @root.to_s    end
445     def inspect() @root.inspect end
446   end
447
448   class IDS_Tree < Tree
449     def initialize(str)
450       @str = str
451       super()
452       parse()
453     end
454     def parse()
455       @str.each_char {|ch|
456         char = Character.new(ch)
457         if is_ids?(char)
458           add_node(char, ids_operator_argc(char))
459         else
460           add_leaf(char)
461         end
462       }
463     end
464     def is_ids?(obj)
465       return true if "+*".include?(obj.to_s) #テスト用ですかね
466       return true if obj.is_ids?
467       return false
468     end
469     def ids_operator_argc(obj)
470       return obj.ids_operator_argc if 0 < obj.ids_operator_argc
471       return 2 #テスト用ってことで
472     end
473     def check_integrity
474       r = super
475       return r if r #不完全がすでにわかっているならreturn
476       return "contains ques" if @str =~ /\?/ #?が含まれている?
477       return nil
478     end
479   end
480
481   class IDS #=========================================IDSそのものを扱うclass
482     def initialize(str) #IDS文字列をうけとる。
483       @str = str
484     end
485     def parse
486     end
487     def parse_x #柔軟型のParse. IDSキャラクターが前にきてなくてもよい。などなど。
488     end
489   end
490
491   class Counter
492     #使い方
493     #counter = Counter.new(50) { exit }
494     #counter.count
495     def initialize(max)
496       @max = max
497       @count = 0
498       @proc = proc
499     end
500     def count
501       @count += 1
502       if @max <= @count
503         @proc.call
504       end
505     end
506   end
507
508 end