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