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