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