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