084a24ede2edae850012b106537086c09b5a77cb
[chise/ruby.git] / src / chise.rb
1 #!/usr/bin/env ruby
2 # Ruby/CHISE module by eto 2002-1114
3
4 require 'bdb'
5 require 'uconv'
6 require 'singleton'
7
8 $KCODE = 'u' #今のところこれ以外では動かない。String.splitが影響大。inspectも影響。
9 $debug = true #これはテスト用
10 #$stdout.binmode if $debug
11 $stdout.sync = true if $debug
12
13 class String #======================================================================
14   def to_a() self.split(//) end #$KCODEが設定されているので、UTF-8的な一文字づつがchにはいる
15   def each_char() to_a.each {|ch| yield ch } end
16   def each_character() to_a.each {|ch| yield ch.char } end
17   def char_length() to_a.length end
18   def char_at(n) to_a()[n] end
19   def char() Character.get(to_a[0]) end
20   #alias to_c char #悩み中
21   def char_id() char.char_id() end
22   def get_char_attribute(a) char.get_char_attribute(a) end
23   #def ucs() char.ucs() end
24   def to_utf8()
25     return to_a.map {|ch|
26       ch.char.to_utf8
27     }.join('')
28   end
29
30   def method_missing(mid, *args)
31     if char_length == 1 #省略形が有効なのは、一文字の時だけ
32       char.method_missing(mid, *args)
33     else
34       raise NameError, "undefined method `#{mid.id2name}'", caller(1)
35     end
36   end
37
38   def map_utf8() map_char {|ch| ch.char.map_utf8 } end
39   alias map_ucs map_utf8
40   def map_ucs_er() map_char {|ch| ch.char.map_ucs_er } end
41   def to_er() map_char {|ch| ch.char.to_er } end
42
43   #put関係、[]関係は用意しないことにした。
44   def de_er!() #EntityReferenceを取り除く
45     return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
46     er = "&"+$1+";"      
47     self.sub!(Regexp.new(Regexp.escape(er)), Character.new(er).mcs_utf8) #変換自体はCharacterにまかせる
48     return self.de_er! if self =~ Regexp.new(EntityReference::REGEXP_PART) #まだあったら再帰
49     return self
50   end
51   def de_er() return self.dup.de_er!; end
52
53   def map_char(block = Proc.new)
54     return unless block_given?
55     return self.to_a.map {|ch| (block.call(ch)).to_s }.join("")
56   end
57   def map_char!(block = Proc.new)
58     return unless block_given?
59     self.replace(self.map_char {|ch| block.call(ch)})
60   end
61   def inspect_all() map_char {|ch| ch.char.inspect_all } end
62   def inspect_x()   map_char {|ch| ch.char.inspect_x   } end
63
64   def to_euc()   map_char {|ch| ch.char.to_euc   } end
65   def map_euc()  map_char {|ch| ch.char.map_euc  } end
66   def to_sjis()  map_char {|ch| ch.char.to_sjis  } end
67   def map_sjis() map_char {|ch| ch.char.map_sjis } end
68
69   def decompose() map_char {|ch| ch.char.decompose } end
70   def decompose!() self.replace(self.decompose); self; end
71   def decompose_all_nu(level=nil)
72     level = 0 if level.nil?
73     if 10 < level
74       p ['too many recursive', self] 
75       exit
76     end
77     de = self.decompose
78     return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
79     de #もうこれ以上変化は無さそうだぞと。
80   end
81   def decompose_all() map_char {|ch| ch.char.decompose_all } end
82   def decompose_all!() self.replace(self.decompose_all); self; end
83
84   def find() #"日雲"→"曇"とかいう感じの操作
85     ar = []
86     length = char_length()
87     each_char {|ch|
88       char = ch.char
89       ar << char.ids_contained #その文字を含んでいる漢字のリスト
90     }
91     h = Hash.new(0)
92     ar.each {|list|
93       next if list.nil?
94       list.each_char {|ch|
95         h[ch] += 1
96       }
97     }
98     str = ""
99     h.each {|k, v|
100       #      p [k, v]
101       if length == v #全部に顔を出していたら
102         str += k
103       end
104     }
105     #    p str
106     str
107   end
108   def compose()
109     db = CodesysDB.instance
110     composed = db.get('ids', self)
111     return "" if composed.nil? #なかったよと。
112     return "" if composed.char_length == 0 #なにごと?
113     return composed if composed.char_length == 1
114     composed.each_char {|ch|
115       char = ch.char
116       return ch if char.has_attribute? #とりあえず最初にみつかったものを返すというヌルい仕様
117     }
118     return "" #attributeを持つものが一つも無かったら、""にする
119   end
120   def aggregate()
121     #selfである文字列をIDSだと仮定し、それを完全にcomposeしきらないで、
122     #その部分集合だけをとりだして、compose可能であればできるだけcomposeする。
123     tree = IDS_Tree.new(self)
124     return self if tree.depth <= 1 #sub_nodesが無い場合はここでさよなら
125     tree.sub_nodes.each {|node|
126       c = node.compose
127       next if c.nil? || c == ""
128       #      print "#{self}     #{node} #{c}\n"
129       #      p [self, node, c]
130       n = self.gsub(node, c)
131       return n.aggregate
132     }
133     return self #おきかえられるものがまったくなかったら、自分をかえす。
134   end
135 end
136
137 module CHISE #======================================================================
138   def windows?()
139     (RUBY_PLATFORM =~ /cygwin/ || RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/)
140   end
141   module_function :windows?
142   if windows?()
143     DB_DIR = 'd:/work/chise/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
144   else
145     DB_DIR = '/usr/local/lib/xemacs-21.4.10/i686-pc-linux/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
146   end
147
148   class EntityReference #======================================================================
149     #状況によってどのERに変換するかが異なる可能性があるので、普通のclassとして実装したほうがいい?
150     CODESYS_TABLE = [
151       %w( chinese-big5-cdp      CDP- 4 X),
152       %w( ideograph-daikanwa    M-   5 d),
153       %w( ideograph-cbeta       CB   5 d),
154       %w( ideograph-gt          GT-  5 d),
155       %w( ideograph-gt-k        GT-K 5 d),
156       %w( japanese-jisx0208-1990 J90- 4 X),
157       %w( japanese-jisx0208     J83- 4 X),
158       %w( japanese-jisx0213-1   JX1- 4 X),
159       %w( japanese-jisx0213-2   JX2- 4 X),
160       %w( japanese-jisx0212     JSP- 4 X),
161       %w( japanese-jisx0208-1978 J78- 4 X),
162       %w( chinese-cns11643-1    C1-  4 X),
163       %w( chinese-cns11643-2    C2-  4 X),
164       %w( chinese-cns11643-3    C3-  4 X),
165       %w( chinese-cns11643-4    C4-  4 X),
166       %w( chinese-cns11643-5    C5-  4 X),
167       %w( chinese-cns11643-6    C6-  4 X),
168       %w( chinese-cns11643-7    C7-  4 X),
169       %w( korean-ksc5601        K0- 4 X),
170     ]
171     CODESYS_ORDER = %w(japanese chinese korean ideograph)
172     REGEXP_PART = "&([-+0-9A-Za-z#]+);"
173     REGEXP_ALL = "^#{REGEXP_PART}$"
174
175     def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
176
177     def self.parse(er) #char_idをFIXNUMで返す
178       return "" unless er =~ Regexp.new(REGEXP_ALL) #なんか、間違ってる?
179       er = $1 #ついでに中身の部分を取り出す
180       return $1.hex if er =~ /^MCS-([0-9A-Fa-f]+)/ #MCS
181       #      if er =~ /^U[-+]?([0-9A-Fa-f]+)/ #Unicode直打ち
182       if er =~ /^U[-+]?([0-9A-Fa-f]+)/ || er =~ /^#x([0-9A-Fa-f]+)/ #Unicode直打ち
183         return $1.hex 
184       end
185
186       er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
187       each_codesys {|codesys, er_prefix, keta, numtype| #p [codesys, er_prefix, keta, numtype]
188         numtyperegex = '\d' #if numtype == 'd'
189         numtyperegex = '[0-9A-Fa-f]' if numtype == 'X'
190         regexpstr = "^#{er_prefix}(#{numtyperegex}{#{keta},#{keta}})$"  #p regexpstr
191         if er =~ Regexp.new(regexpstr)
192           codestr = $1
193           code = codestr.to_i #if numtype == 'd'
194           code = codestr.hex if numtype == 'X'
195           char_id_u8 = EntityReference.get_database(codesys, code)
196           char_id_num = Character.parse_char_id(char_id_u8)
197           return char_id_num
198         end
199       }
200       return ""
201     end
202
203     def self.each_codesys()
204       CODESYS_ORDER.each {|lang|
205         CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype| #普通こういう書き方はしない。ループ一個にする。
206           next unless codesys =~ lang
207           yield(codesys, er_prefix, keta, numtype)
208         }
209       }
210     end
211     def self.get_database(codesys, code)
212       c = CodesysDB.instance.get(codesys, code)
213       return c if c != nil
214       if codesys =~ /-jisx0208/
215         #return self.get_database("=jis-x0208", code) #再帰でどうだ?
216         c = CodesysDB.instance.get("=jis-x0208", code)
217         return c
218       end
219       return nil
220     end
221   end
222
223   class CharacterFactory #============================================文字オブジェクトの生成、cache
224     include Singleton
225     MAX = 10000
226     def initialize
227       @max = MAX
228       reset()
229     end
230     def get(char_id)
231       check_max()
232       n = Character.parse_char_id(char_id)
233       c = @chars[n]
234       @chars[n] = Character.new(n) if @chars[n] == nil
235       return @chars[n]
236     end
237     def reset()
238       @chars = nil
239       @chars = Hash.new
240       GC.start #ガーベージコレクション
241     end
242     def length() @chars.length; end
243     def check_max()
244       reset if @max < @chars.length #MAXを超えたらresetしてしまう。乱暴じゃがcacheなのでこれでいいのだ。
245     end
246   end
247
248   class Character #=============================================================== 文字オブジェクト
249     BASIC_KANJI = "人子女母父王口耳手足力目首毛心犬牛鳥貝角羽虫馬魚羊肉皮米竹木麦豆山川雨風水土石金田穴日月火音糸刀舟門戸衣矢弓車皿一二三四五六七八九十百千万寸尺上中下本玉立回食行止交向歩考入示走生出来書言大小白青多少高長"
250     def is_basic_kanji?
251       BASIC_KANJI.include?(self.to_s)
252     end
253
254     def initialize(char_id=nil)
255       @char_id = Character.parse_char_id(char_id)
256       @attributes = Hash.new
257       @check_all_database = false
258     end
259     attr_reader :char_id
260     def to_i() @char_id end
261     def mcs_utf8() Character.u4itou8(@char_id) end
262     def mcs_hex() sprintf("%x", @char_id) end
263
264     #----------------------------------------------------------------------
265     def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
266
267     #----------------------------------------------------------------------
268     def normalize_attribute_name(b)
269       a = b.dup
270       a.gsub!(/_/, '-') #underlineは-に置換
271       a.sub!(/^map-/,  '=>')
272       a.sub!(/^to-/,   '->')
273       a.sub!(/^from-/, '<-')
274       a
275     end
276     def get_char_attribute(b) # XEmacs UTF-2000互換API群
277       a = normalize_attribute_name(b)
278       #p [a, b]
279       atr = @attributes[a]
280       return atr if atr != nil
281       atr = check_database(a)
282       @attributes[a] = atr if atr != nil
283       return get_char_attribute("=jis-x0208") if a =~ /jisx0208/ #ここだけ特殊形
284       return @attributes[a]
285     end
286     def put_char_attribute(b,v)
287       a = normalize_attribute_name(b)
288       @attributes[a] = v;
289       CharDB.instance.put(a, mcs_utf8(), v)
290     end
291     def char_attribute_alist() check_all_database(); @attributes; end
292     def char_attribute_list()  check_all_database(); @attributes.keys; end
293     alias [] get_char_attribute  #その略称
294     alias []= put_char_attribute
295     alias alist char_attribute_alist
296     alias list  char_attribute_list
297
298     def method_missing(mid, *args) #参考:ostruct.rb
299       mname = mid.id2name
300       return get_char_attribute(mname) if args.length == 0
301       put_char_attribute(mname.chop, args[0]) if mname =~ /=$/ #代入
302     end
303
304     def has_attribute?() #意味のあるattributeを持ってますか?
305       keys = list
306       keys.delete_if {|k|
307         k =~ /ids/
308       }
309       return (keys.length != 0)
310     end
311
312     #----------------------------------------------------------------------
313     def ==(ch)
314       return false if ch == nil
315       return false unless ch.is_a? Character
316       self.char_id == ch.char_id
317     end
318
319     #----------------------------------------------------------------------
320     def self.parse_char_id(char_id) #FIXNUMを返す
321       return nil if char_id == nil
322       if char_id.is_a?(Numeric) #p [char_id]
323         char_id = 0x80000000 + char_id if char_id < 0  #補数表現
324         return char_id.to_i
325       elsif char_id.is_a?(String)
326         return char_id.to_i if char_id =~ /^\d+$/ && 1 < char_id.length #文字列による数字だったら数値化してreturn
327         return EntityReference.parse(char_id) if char_id =~ Regexp.new(EntityReference::REGEXP_ALL) #実体参照?
328         char_id.sub!(/^\?/, '') if char_id =~ /^\?/ #もし先頭に?がついていたら削除
329         #このへん本当はもっとちゃんとチェックするべし
330         begin
331           u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
332         rescue
333           p $!
334           p char_id
335           return 0
336         end
337         return Character.u4tou4i(u4) #UCS-4数値にしてreturn
338       else
339         raise ArgumentError, "unknown object for char_id", caller(1)
340       end
341     end
342     def self.u4tou4i(u4)
343       return 0 if u4 == nil || u4 == ""
344       return (u4[3] << 24 | u4[2] << 16 | u4[1] << 8 | u4[0]) #UCS-4数値にしてreturn
345     end
346     def self.u4itou4(num)
347       return "" unless num.is_a?(Integer)
348       return sprintf("%c%c%c%c", num&0xff, (num >> 8)&0xff, (num >> 16)&0xff, (num >> 24)&0xff) #UCS-4数値を文字列にしてreturn
349     end
350     def self.u4itou8(char_id) #ucsの数値を受けとり、UTF-8の文字一文字を返す
351       begin
352         u4 = Character.u4itou4(char_id)
353         u8 = Uconv.u4tou8(u4)
354         return u8
355       rescue
356         #raise ArgumentError, "invalid char_id (#{char_id})", caller(1)
357         #print "error\n"
358         return ""
359       end
360     end
361
362     #----------------------------------------------------------------------
363     def check_database(a)
364       db = CharDB.instance
365       u8 = mcs_utf8()
366       v = db.get(a, u8) #u8で表される文字のaアトリビュートを調べる。
367       return v
368     end
369     def check_all_database() #現在の@char_idから、文字データベースを参照する
370       return if @check_all_database
371       return if @char_id == nil
372       db = CharDB.instance
373       u8 = mcs_utf8()
374       atrs = db.get_all(u8) #u8で表される文字のアトリビュートを全部持ってこい
375       atrs.each {|a,v|
376         @attributes[a] = v #とかいう感じで代入するのでええかな?
377       }
378       @check_all_database = true #重い処理なので一応checkする
379     end
380
381     #----------------------------------------------------------------------
382     def ucs()      #p 'ucs'
383       #ar = %w{ucs ucs-big5 ucs-cdp ucs-cns ucs-jis ucs-ks =>ucs =>ucs* =>ucs-jis}
384       #ar = %w{ucs ucs-jis ucs-big5 ucs-cdp ucs-cns ucs-ks =>ucs =>ucs* =>ucs-jis}
385       ar = %w{ucs-jis ucs =>ucs-jis}
386       #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
387       ar.each {|a|      #p [a]
388         u = get_char_attribute(a)
389         return u if u != nil
390       }
391       return nil
392     end
393
394     #----------------------------------------------------------------------CCS関係
395     def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
396     #alias to_s to_utf8
397     alias to_s mcs_utf8
398     def map_utf8()
399       u = ucs()
400       if u.nil? || 0xffff < u
401         return to_er()
402       else
403         return to_utf8()
404       end
405     end
406     alias map_ucs map_utf8
407     def map_ucs_er()
408       u = ucs()
409       if u.nil? || 0xffff < u
410         return to_er()
411       else
412         return Character.get(u).to_er()
413       end
414     end
415     def to_euc()
416       u = ucs()
417       return "" if u.nil? || 0xffff < u
418       Uconv.u16toeuc(Uconv.u4tou16(Character.u4itou4(ucs())))
419     end
420     def map_euc()
421       e = to_euc()
422       return e if e != ""
423       return to_er()
424     end
425     def to_sjis()
426       u = ucs()
427       return "" if u.nil? || 0xffff < u
428       Uconv.u16tosjis(Uconv.u4tou16(Character.u4itou4(ucs())))
429     end
430     def map_sjis()
431       e = to_sjis()
432       return e if e != ""
433       return to_er()
434     end
435
436     #----------------------------------------------------------------------
437     def to_er(codesys=nil) #実体参照を返す、希望するcodesysが引数(未実装)
438       return "" if @char_id == nil
439       return sprintf("&#x%04x;", @char_id) if @char_id <= 0xffff
440       return sprintf("&#x%05x;", @char_id) if @char_id <= 0xfffff
441       EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
442         code = self[codesys]
443         next if code == nil
444         return sprintf("&#{er_prefix}%0#{keta}#{numtype};", code)
445       }
446       return sprintf("&MCS-%08X;", @char_id) #本当はこれは無しにしたい
447     end
448     def to_er_list()
449       ar = []
450       EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
451         er = to_er(codesys)
452         ar << er if er != nil
453       }
454       ar
455     end
456
457     def inspect_x()
458       return "<>" if @char_id == nil
459       ar = [to_utf8(), to_er().sub(/^&/,'').chop]
460       "<"+ar.join(',')+">"
461     end
462     alias inspect inspect_x
463     def inspect_all_codesys() #未完成
464       #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
465     end
466     def inspect_all()
467       ar = [inspect.chop]
468       alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
469       return ar.join(',')+">"
470     end
471     def get_attributes()
472       str = ""
473       alist.to_a.sort.each {|a, v|
474         str += "#{a}: #{v}\n"
475       }
476       str
477     end
478
479     def inspect_ids(hex_flag=false)
480       ids = decompose
481       ar = []
482       ar << (hex_flag ? "x"+mcs_hex : to_utf8)
483       if to_s != ids #idsが部品そのものだったら部品追加はしない
484         ids.each_char {|ch|
485           char = ch.char
486           next if char.is_ids?
487           if hex_flag then
488             ar << "x"+char.mcs_hex
489           else
490             u = char.to_utf8
491             if u != ""
492               ar << u
493             else
494               ar << char.to_er
495             end
496           end
497         }
498       end
499       return "("+ar.join("\t")+")"
500     end
501
502     #----------------------------------------------------------------------IDS関係
503     def decompose
504       k = self.to_s
505       #       idss = self['ids']
506       #       return idss if idss
507       #       return k if self.is_basic_kanji? #基本漢字はstop kanjiとするぞと。
508       return self['ids-represent'] if self['ids-represent'] #ids_representを持っている場合はその値とする。
509       return self['ids-element'] if self['ids-element'] #ids_elementを持っている場合はその値とする。
510
511       idss = self['ids-meaning']
512       return idss if idss != nil && 0 < idss.length && k != idss
513       idss = self['ids-aggregated']
514       return idss if idss != nil && 0 < idss.length && k != idss
515       idss = self['ids']
516       return idss if idss != nil && 0 < idss.length && k != idss
517       return k
518       #       return k if idss.nil? || idss.length == 0 || k == idss
519       #       if idss.char_length == 2
520       # p ['What???', k, idss, k.inspect_all]
521       #  #return idssx[1] #二個目だけ返すとか?
522       #  return k #IDSに展開する方法が無いと。
523       #       end
524       #       return k if k == idss
525       #       if idss.include?(k) #<C5-4C4D><C6-4A37>この二文字のBUG対策
526       #  #return idss.sub(k, '')
527       #  return k #IDSに展開する方法が無いと。
528       #       end
529       #       return idss
530     end
531     def decompose_all
532       pde = ""
533       de = self.decompose #出発点
534       level = 0
535       while true
536         pde = de
537         de = pde.decompose #もう一度分解をしてみる。
538         break if pde == de #ループを抜けだす
539         exit if 10 < level #p ['too many recursive', self] 
540         level += 1
541       end
542       return de
543     end
544     def decompose_all_nu(level=nil)
545       level = 0 if level.nil?
546       if 10 < level
547         p ['too many recursive', self] 
548         exit
549       end
550       de = self.decompose
551       return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
552       return de #もうこれ以上変化は無さそうだぞと。
553     end
554     def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
555     def ids_operator_argc()
556       return 0 unless is_ids?
557       return 3 if @char_id == 0x2ff2 || @char_id == 0x2ff3
558       return 2
559     end
560   end
561
562   class DBS #======================================================================複数のDBを集めたclass、未完成
563   end
564
565   class ADB < BDB::Hash #======================================================================一つのDB
566     def initialize(*args)
567       super
568       @modified = false
569       at_exit {
570         if @modified
571           self.close #これがないと、うまくデータベースがセーブされないのです。
572         end
573       }
574     end
575     def self.open_create(filename)
576       ADB.open(filename, nil, BDB::CREATE | BDB::EXCL) #上書きはしない
577     end
578     def mykey(key)
579       if key.is_a?(String)
580         if key.char_length == 1
581           return '?'+key  #Stringだったら引く前に?を足す
582         end
583       end
584       #key = key.to_s if key.is_a?(Numeric) #NumberだったらStringにする。
585       #ここで && key ! =~ /^\?/ をいれると、?自身を検索できなくなってしまう。
586       return key
587     end
588     def myvalue(v)
589       return v if v == nil
590       return v.to_i if v =~ /^\d+$/ #数字だったらここで変換しておく
591       return v.sub(/^\?/, '') if v =~ /^\?/ #冒頭の?は取り除く
592       return $1 if v =~ /^"(.+)"$/ #最初と最後に"がついていたら、取り除く
593       #p ['get', v, t, key, db]
594       #return parse_sexp(v) if v =~ /^\(.+\)$/ #最初と最後が()の時は、S式にparseする
595       return v #それ以外って何?
596     end
597     def myget(key) #keyキーを引いて返す
598       key = mykey(key)
599       v = get(key) #存在しなかったらnilを返すことになる
600       return myvalue(v)
601     end
602     def myput(key, v) #keyにvをいれる
603       key = mykey(key)
604       put(key, v) #putする
605       @modified = true
606     end
607   end
608
609   class DB #======================================================= データベース群のabstract class
610     def self.unix_to_win(unix) #Windowsファイル名制限のため、変換する
611       win = unix.gsub(/</, '(')
612       win.gsub!(/>/, ')')
613       win.gsub!(/\*/, '+')
614       win.gsub!(/\?/, '!')
615       return win
616     end
617     def self.win_to_unix(win)
618       unix = win.gsub(%r|\)|, '>')
619       unix.gsub!(%r|\(|, '<')
620       unix.gsub!(%r|!|, '?')
621       unix.gsub!(%r|\+|, '*')
622       return unix
623     end
624     def get_filename(t)
625       return @pre + DB.unix_to_win(t) + @post if windows?
626       return @pre + t + @post
627     end
628     def get_dirname(t) File.dirname(get_filename(t)) end
629     def open_dbs()
630       @dbs = Hash.new
631       keys = find_keys()
632       keys.each {|key| open_db(key) }
633     end
634     def find_keys()
635       files = []
636       Dir.glob(@glob){|f|
637         next if ! File.file?(f)
638         next if f =~ /.txt$/
639         files << f
640       }
641       keys = []
642       files.each {|f|
643         t = DB.win_to_unix(f)
644         t.sub!(%r|^#{@pre}|, '')
645         t.sub!(%r|#{@post}$|, '') if @post != ""
646         keys << t
647       }
648       return keys
649     end
650     def close_db(t)
651       db = get(t)
652       return nil if db.nil?
653       db.close
654       @dbs.delete(t)
655     end
656     def open_db(t)
657       return nil if get(t) #すでにopenしていたら再openはしない。
658       begin
659         bdb = ADB.open(get_filename(t), nil, 0)
660         @dbs[t] = bdb if bdb != nil
661       rescue
662         p ["open error", get_filename(t)]; return nil
663       end
664       return true
665     end
666     def make_db(t, h=nil) #tという名前でhという中身のデータベースを作る
667       return nil if get(t) #すでにある場合はreturn
668       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
669       db = nil
670       begin
671         db = ADB.open_create(get_filename(t)) #上書きはしない
672         if h != nil
673           h.each {|k, v|
674             k = '?'+k if k.is_a?(String)
675             db[k] = v
676           }
677         end
678         db.close
679       rescue
680         p ["make error", get_filename(t)]; return nil
681       end
682       return true
683     end
684     def make_db_no_question_mark(t, h=nil) #tという名前でhという中身のデータベースを作る
685       return nil if get(t) #すでにある場合はreturn
686       Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
687       db = nil
688       begin
689         db = ADB.open_create(get_filename(t)) #上書きはしない
690         if h != nil
691           h.each {|k, v|
692             #        k = '?'+k if k.is_a?(String)
693             db[k] = v
694           }
695         end
696         db.close
697       rescue
698         p ["make error", get_filename(t)]; return nil
699       end
700       return true
701     end
702     def remove_db(t) #tという名前のデータベースを消去する
703       db = get(t)
704       if db
705         db.close
706         @dbs.delete(t)
707       end
708       begin
709         File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
710       rescue
711         p ["unlink error", get_filename(t)]; return nil
712       end
713       dn = get_dirname(t)
714       Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
715       return true
716     end
717     def to_num(s)
718       return s.to_i if s =~ /^\d+$/
719       return s
720     end
721     def dump_db(t)
722       db = get(t)
723       return nil unless db
724       file = get_filename(t)
725       open("#{file}.txt", "w"){|out|
726         #        out.binmode.sync = true
727         ar = db.to_a
728         ar.map! {|k, v| [to_num(k), to_num(v)] }
729         ar.sort.each {|k, v|
730           out.printf("%s\t%s\n", k, v)
731         }
732       }
733       return true
734     end
735     def each_db()  @dbs.to_a.sort.each {|t, db| yield(t, db) } end
736     def dump_all()  each_db {|t, db| dump_db(t) } end
737     def close_all() each_db {|t, db| db.close   } end
738     def keys() @dbs.keys end
739     def each(t)
740       return unless block_given?
741       db = @dbs[t]
742       return nil unless db
743       db.each {|k, v|
744         k = to_num(k)
745         v = to_num(v)
746         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
747         vv = get(t, k)  #p ['each', t, k, v, vv]
748         yield(k, vv)
749       }
750     end
751     def each_sort(t)
752       return unless block_given?
753       db = @dbs[t]
754       return nil unless db
755       ar = db.to_a
756       ar.map! {|k, v| [to_num(k), to_num(v)] }
757       ar.sort.each {|k, v|
758         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
759         vv = get(t, k)  #p ['each', t, k, v, vv]
760         yield(k, vv)
761       }
762     end
763     #----------------------------------------------------------------------
764     def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
765       db = @dbs[t]
766       return db if key.nil?
767       return nil unless db
768       return db.myget(key)
769     end
770     def put(t, key, v) #tというデータベースのkeyにvをいれる
771       db = @dbs[t]
772       if db == nil
773         db = make_db(t) 
774         db = open_db(t) 
775         db = @dbs[t]
776       end
777       db.myput(key, v) #putする
778     end
779   end
780
781   class CharDB < DB #------------------------------------ MCS-UTF8をキーとした属性へのデータベース
782     include Singleton
783     def initialize()
784       super
785       @glob, @pre, @post = "#{DB_DIR}/system-char-id/*", "#{DB_DIR}/system-char-id/", ""
786       open_dbs()
787     end
788     def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
789       atrs = Hash.new
790       @dbs.each {|t, db|
791         v = get(t, u8)
792         atrs[t] = v if v != nil
793       }
794       return atrs
795     end
796   end
797
798   class CodesysDB < DB #----------------------------------------------------------------------
799     include Singleton
800     def initialize()
801       super
802       @glob, @pre, @post = "#{DB_DIR}/*/system-char-id", "#{DB_DIR}/", "/system-char-id"
803       open_dbs()
804     end
805     #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
806     def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
807     def get_codesys(t)
808       db = get(t)
809       return nil unless db
810       return Codesys.new(t)
811     end
812   end
813
814   class Codesys < DB #======================================================================
815     def initialize(name)
816       #       super
817       @name = name
818       @dbs = CodesysDB.instance
819     end
820     def keys() #どんなコードポイントの情報を持っているかの一覧
821       ks = @dbs.get(@name).keys
822       if @name =~ /jisx0208/ #特別処理
823         n = @dbs.get('=jis-x0208').keys 
824         #        p ['keys', @name, ks, n]
825         ks += n
826       end
827       ks.map! {|k| to_num(k) }
828       ks
829     end
830     def get(key)
831       v = @dbs.get(@name, key)
832       return v if v
833       if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
834         return @dbs.get('=jis-x0208', key)
835       end
836       return nil
837     end
838     def each()
839       return unless block_given?
840       db = @dbs.get(@name)
841       return nil unless db
842       db.each {|k, v|
843         k = to_num(k)
844         v = to_num(v)
845         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
846         vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
847         yield(k, vv)
848       }
849     end
850     def each_sort()
851       return unless block_given?
852       db = @dbs.get(@name)
853       return nil unless db
854       ar = db.to_a
855       ar.map! {|k, v| [to_num(k), to_num(v)] }
856       ar.sort.each {|k, v|
857         k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
858         vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
859         yield(k, vv)
860       }
861     end
862   end
863
864   class JISX0208
865     def initialize
866       db = CodesysDB.instance
867       @common = db.get_codesys('=jis-x0208')
868       @newest = db.get_codesys('japanese-jisx0208-1990')
869     end
870     def get_char(code)
871       char = @common.get(code)
872       return char unless char.nil?
873       char = @newest.get(code)
874       return char unless char.nil?
875       return nil
876     end
877   end
878
879   class IDS_TEXT_DB < DB #======================================================================
880     include Singleton
881     if CHISE.windows?()
882       IDS_DB_DIR = 'd:/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
883     else
884       IDS_DB_DIR = '/home/eto/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
885     end
886     IDS_LIST = "
887 IDS-UCS-Basic.txt
888 #IDS-UCS-Compat-Supplement.txt
889 #IDS-UCS-Compat.txt
890 IDS-UCS-Ext-A.txt
891 IDS-UCS-Ext-B-1.txt
892 IDS-UCS-Ext-B-2.txt
893 IDS-UCS-Ext-B-3.txt
894 IDS-UCS-Ext-B-4.txt
895 IDS-UCS-Ext-B-5.txt
896 IDS-UCS-Ext-B-6.txt
897 IDS-JIS-X0208-1990.txt
898 IDS-Daikanwa-01.txt
899 IDS-Daikanwa-02.txt
900 IDS-Daikanwa-03.txt
901 IDS-Daikanwa-04.txt
902 IDS-Daikanwa-05.txt
903 IDS-Daikanwa-06.txt
904 IDS-Daikanwa-07.txt
905 IDS-Daikanwa-08.txt
906 IDS-Daikanwa-09.txt
907 IDS-Daikanwa-10.txt
908 IDS-Daikanwa-11.txt
909 IDS-Daikanwa-12.txt
910 IDS-Daikanwa-dx.txt
911 IDS-Daikanwa-ho.txt
912 IDS-CBETA.txt
913 ".split
914     def initialize()
915       super
916       @ids_list = IDS_LIST
917       @chars = []
918       @glob, @pre, @post = "#{IDS_DB_DIR}/db/*", "#{IDS_DB_DIR}/db/", ""
919       dir = File.dirname(@pre)
920       Dir.mkdir(dir) unless FileTest.exist?(dir)
921       open_dbs()
922     end
923     def each_file()
924       return unless block_given?
925       @ids_list.each {|file|
926         next if file =~ /^#/
927         yield(IDS_DB_DIR+file)
928       }
929     end
930     def each_line(file)
931       open(file){|f|
932         while line = f.gets
933           next if line =~ /^;/ #コメントはとばす
934           line.chomp!
935           code, char, ids = line.split
936           yield(code, char, ids)
937         end
938       }
939     end
940     def dump_text_all
941       each_file {|file|
942         dir = File.dirname(file) + '/../ids-new/'
943         Dir.mkdir(dir) if ! FileTest.directory?(dir)
944         newfile = dir + File.basename(file)
945         p [file, newfile]
946         open(newfile, "w"){|out|
947           out.binmode.sync = true
948           each_line(file){|code, ch, ids|
949             char = Character.get(ch)
950             ids = char.decompose
951             out.print "#{code}  #{ch}   #{ids}\n"
952           }
953         }
954       }
955     end
956     def make_ids_error
957       each_file {|file|
958         dir = File.dirname(file) + '/../ids-error'
959         Dir.mkdir(dir) unless FileTest.exist?(dir)
960         errfile = dir + '/' + File.basename(file)
961         #       p [file, errfile]
962         open(errfile, "w"){|out|
963           out.binmode.sync = true
964           each_line(file){|code, ch, ids|
965             char = Character.get(ch)
966             ids_error = char['ids-error']
967             next if ids_error.nil?
968             out.print "#{code}  #{ch}   #{ids}  #{ids_error}\n"
969           }
970         }
971       }
972     end
973   end
974
975   class IDS_DB < DB #======================================================================BDB化したIDS DBを扱う
976     include Singleton
977     def initialize
978       @dbs = CharDB.instance
979     end
980     def make_ids_db
981       db = IDS_TEXT_DB.instance
982       db.each_file {|file|
983         @char_counter = 0
984         @same_ids_counter = 0
985         @good_ids_counter = 0
986         @conflict_ids_counter = 0
987         db.each_line(file){|code, ch, ids|
988           @char_counter += 1
989
990           ids = "" if ids == nil
991           next if ids == "" #IDSが定義されていない場合は、さっくりと無視するべしよ。
992
993           charimg = Character.get(ch) #実体参照である可能性がある
994
995           next if code =~ /'$/ || code =~ /"$/ #大漢和番号のダッシュ付きは無視する
996           char = Character.get("&"+code+";") #code表記を元に実体参照を作って解釈する
997           if char.nil? || char.to_s == "" #うまく文字にならなかった
998             print "char == null #{char.inspect} #{code} #{ch}   #{ids}\n" unless code =~ /^M-/ || code =~ /^CB/
999             #大漢和、CBETA以外の場合は、エラーメッセージ。
1000             next
1001           end
1002           if char != charimg #code表記と文字が一致していない?
1003             unless code =~ /^M-/ || code =~ /^MH-/ || code =~ /^CB/ #食い違っていて当然であるので何もしない
1004               print "unknown char       #{char.inspect} #{code} #{ch}   #{ids}\n"
1005               next #それ以外の場合はエラーメッセージをだして、次へ。
1006             end
1007           end
1008           #next if !char.has_attribute? #isolated characterはまぎれこませない。
1009
1010           ids.de_er! #実体参照を解除する
1011           next if ids == char.to_s #もし文字とまったく一緒なら、意味が無いので情報を持たない
1012           next if ids.char_length == 1
1013
1014           idstree = IDS_Tree.new(ids)
1015           c = idstree.check_integrity
1016           c = "contains self" if ids.include?(char.to_s)
1017           if c #ちょっとでもエラーがある場合は、
1018             char['ids-error'] = c #エラーを記録して、データとしては保持しない
1019             next
1020           end
1021
1022           if char['ids'].nil? || char['ids'] == "" #元々IDSが無かった場合は、
1023             char['ids'] = ids #普通に代入すればそれでいいです。
1024             @good_ids_counter += 1
1025           else #しかしいままでにすでにIDSが定義されていた場合は?
1026             if char['ids'] == ids #新しいIDSと古いIDSが完全に一致するなら無視しましょう。
1027               @same_ids_counter += 1
1028             else #しかしいままでのIDSと新しいIDSが食い違った場合は?
1029               @conflict_ids_counter += 1
1030               #       print "conflict   #{char.inspect} #{code} #{ids}  #{char['ids']}\n"
1031             end
1032           end
1033         }
1034         print "#{file}  #{@char_counter}        #{@same_ids_counter}    #{@conflict_ids_counter}        #{@good_ids_counter}\n"
1035         CharacterFactory.instance.reset()
1036       }
1037       @dbs.dump_db('ids-error') #テキスト化する
1038       @dbs.dump_db('ids') #テキスト化する
1039     end
1040     def make_ids_reverse
1041       h = Hash.new
1042       @dbs.each('ids') {|k, v|
1043         char = k.char
1044         ids = char.decompose
1045         h[ids] = "" if h[ids].nil?
1046         h[ids] += k #追加する
1047       }
1048       h.each {|k, v|
1049         h[k] = char_sort(v) #文字の順番を、よく使うっぽいものからの順番にする
1050       }
1051       h.delete_if {|k, v| #h[k]が""になる可能性もあるが、それはkeyとして入れないことにする。
1052         v == ""
1053       }
1054       print "length     #{h.length}\n"
1055       cdb = CodesysDB.instance
1056       cdb.make_db_no_question_mark('ids', h)
1057       cdb.open_db('ids') #これが無いと、dump_dbされません。
1058       cdb.dump_db('ids')
1059     end
1060     def char_sort(composed)
1061       return composed if composed.char_length == 1
1062       ar = composed.to_a
1063       arorg = ar.dup
1064       ar2 = []
1065       ar.dup.each {|ch|
1066         char = ch.char
1067         if char.char_id < 0xfffff #Unicodeっぽい?
1068           ar2 << ch
1069           ar.delete(ch)
1070         end
1071       }
1072       if 0 < ar.length
1073         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
1074           ar.each {|ch|
1075             char = ch.char
1076             v = char[codesys]
1077             #       p [codesys, v] if v
1078             if v #EntityReferenceの順番に準拠する。
1079               ar2 << ch
1080               ar.delete(ch)
1081             end
1082           }
1083         }
1084       end
1085       if 0 < ar.length
1086         #       p ['yokuwakaran character', ar, ar[0].inspect_all, arorg]
1087         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
1088           ar.dup.each {|ch|
1089             char = ch.char
1090             v = char[codesys]
1091             #       p [codesys, v] if v
1092           }
1093         }
1094       end
1095       return ar2.join("")
1096     end
1097     def dump_ids_duplicated
1098       open('ids-duplicated.txt', 'w'){|out|
1099         #out.binmode
1100         CodesysDB.instance.each('ids') {|k, v|
1101           if v.nil?
1102             out.print "nil      #{k}    #{v}\n"
1103             next
1104           end
1105           n = v.char_length
1106           next if n == 1
1107           out.print "#{n}       #{k}    #{v}"
1108           v.each_char {|ch|
1109             char = ch.char
1110             out.print " #{char.inspect}"
1111           }
1112           out.print "\n"
1113         }
1114       }
1115     end
1116     def make_ids_aggregated
1117       @dbs.each('ids') {|k, v|
1118         char = k.char
1119         ids = char.decompose
1120         ag = ids.aggregate
1121         char['ids-aggregated'] = ag
1122       }
1123       @dbs.dump_db('ids-aggregated')
1124     end
1125     def dump_ids_aggregated
1126       open('ids-aggregated.txt', 'w'){|out|
1127         #out.binmode
1128         @dbs.each('ids') {|k, v|
1129           char = k.char
1130           ids = char['ids']
1131           ag  = char['ids-aggregated']
1132           out.print "#{char.to_s}       #{ag}   #{ids}\n" if ids != ag
1133         }
1134       }
1135     end
1136     def make_ids_parts
1137       @dbs.each('ids') {|k, v|
1138         char = k.char
1139         pids = char.to_s
1140         ar = []
1141         counter = 0
1142         loop {
1143           ids = pids.decompose
1144           break if ids == pids #これ以上分割できないようだったら終了〜。
1145           ar += ids.to_a
1146           counter += 1
1147           p [char.to_s, pids, ids, ar] if 10 < counter #これは何かおかしいぞと
1148           pids = ids
1149         }
1150         ar.sort!
1151         ar.uniq!
1152         #やっぱりIDS文字も加えることにする. by eto 2003-02-05
1153         #       ar.delete_if {|ch|
1154         #         ch.char.is_ids? #IDS文字はまぎれこませない。
1155         #       }
1156         str = ar.join('')
1157         char['ids-parts'] = str
1158       }
1159       @dbs.dump_db('ids-parts')
1160     end
1161     def make_ids_contained
1162       h = Hash.new
1163       @dbs.each('ids-parts') {|k, v|
1164         char = k.char
1165         parts = char.ids_parts
1166         parts.each_char {|ch|
1167           #       part = ch.char
1168           h[ch] = [] if h[ch].nil?
1169           h[ch] << k
1170           #       h[ch] += k
1171           #       part['ids-contained'] = "" if part['ids-contained'].nil?
1172           #       part['ids-contained'] += k
1173         }
1174       }
1175       h.each {|k, v|
1176         char = k.char
1177         v.sort!
1178         char['ids-contained'] = v.join('')
1179         
1180       }
1181       @dbs.dump_db('ids-contained')
1182     end
1183     def make_ids_decomposed
1184       @dbs.each('ids') {|k, v|
1185         char = k.char
1186         de= char.decompose_all
1187         char['ids-decomposed'] = de
1188       }
1189       @dbs.dump_db('ids-decomposed')
1190     end
1191   end
1192
1193   class Node < Array #=======================================================木構造の中の一つの枝
1194     def initialize(nodeleaf=nil, nodenum=nil)
1195       super()
1196       @nodeleaf = nodeleaf
1197       @nodenum = nodenum
1198       if @nodeleaf
1199         original_add(@nodeleaf)
1200       end
1201     end
1202     attr_reader :nodenum
1203     alias original_add <<
1204       private :original_add
1205     def <<(obj)
1206       original_add(obj)
1207       @nodenum -= 1 if @nodenum
1208     end
1209     def nodes
1210       ar = []
1211       ar << self.to_s
1212       self.each {|n|
1213         ar += n.nodes if n.is_a? Node
1214       }
1215       return ar
1216     end
1217   end
1218
1219   class Tree #======================================================================木構造を扱う
1220     def initialize()
1221       @root = Node.new()
1222       @stack = [@root]
1223       @leafnum = 0
1224       @depth = 1 #stackの深さが最大になったところの値、木構造が無いときは1となる
1225     end
1226     def depth() @depth - 1 end
1227     def add_node(nodeleaf=nil, nodenum=nil) #枝を追加
1228       new_node = Node.new(nodeleaf, nodenum)
1229       @stack.last << new_node
1230       @stack << new_node
1231       if @depth < @stack.length
1232         @depth = @stack.length
1233       end
1234       self
1235     end
1236     def end_node() #この枝は終り
1237       @stack.pop
1238       self
1239     end
1240     def add_leaf(a) #葉を追加
1241       @stack.last << a
1242       end_check()
1243       self
1244     end
1245     def end_check()
1246       n = @stack.last.nodenum
1247       if n && n == 0
1248         end_node()
1249         end_check() #再帰
1250       end
1251     end
1252     def check_integrity
1253       n = @stack.last.nodenum
1254       return nil if @root.length == 0 #no tree is good tree
1255       return "unmatch leaves" if n && n != 0
1256       return "extra nodes" if @root.first.is_a?(Node) && @root.length != 1
1257       return "extra leaves" if @root.length != 1
1258       return nil
1259     end
1260     def nodes
1261       r = @root.nodes
1262       r.shift
1263       r
1264     end
1265     def sub_nodes
1266       r = nodes
1267       r.shift
1268       r
1269     end
1270     def to_s()    @root.to_s    end
1271     def inspect() @root.inspect end
1272   end
1273
1274   class IDS_Tree < Tree #======================================================================
1275     def initialize(str)
1276       @str = str
1277       super()
1278       parse()
1279     end
1280     def parse()
1281       @str.each_char {|ch|
1282         char = Character.new(ch)
1283         if is_ids?(char)
1284           add_node(char, ids_operator_argc(char))
1285         else
1286           add_leaf(char)
1287         end
1288       }
1289     end
1290     def is_ids?(obj)
1291       return true if "+*".include?(obj.to_s) #テスト用ですかね
1292       return true if obj.is_ids?
1293       return false
1294     end
1295     def ids_operator_argc(obj)
1296       return obj.ids_operator_argc if 0 < obj.ids_operator_argc
1297       return 2 #テスト用ってことで
1298     end
1299     def check_integrity
1300       r = super
1301       return r if r #不完全がすでにわかっているならreturn
1302       return "contains ques" if @str =~ /\?/ #?が含まれている?
1303       return nil
1304     end
1305
1306   end
1307
1308   class IDS #======================================================================IDSそのものを扱うclass
1309     def initialize(str) #IDS文字列をうけとる。
1310       @str = str
1311     end
1312     def parse
1313     end
1314     def parse_x #柔軟型のParse. IDSキャラクターが前にきてなくてもよい。などなど。
1315     end
1316   end
1317
1318   class Counter #======================================================================
1319     #使い方
1320     #counter = Counter.new(50) { exit }
1321     #counter.count
1322     def initialize(max)
1323       @max = max
1324       @count = 0
1325       @proc = proc
1326     end
1327     def count
1328       @count += 1
1329       if @max <= @count
1330         @proc.call
1331       end
1332     end
1333   end
1334
1335   class DBS_Management #======================================================================ファイル管理
1336     OBSOLETE_ATTRIBUTES = "
1337 cns-radical
1338 cns-radical?
1339 kangxi-radical
1340 daikanwa-radical
1341 unicode-radical
1342
1343 cns-strokes
1344 kangxi-strokes
1345 daikanwa-strokes
1346 shinjigen-1-radical
1347 gb-original-radical
1348 japanese-strokes
1349 jis-strokes-a
1350 jis-strokes-b
1351 jisx0208-strokes
1352 jis-x0213-strokes
1353 jisx0213-strokes
1354 unicode-strokes
1355
1356 totalstrokes
1357 cns-total-strokes
1358 jis-total-strokes-b
1359
1360 non-morohashi
1361
1362 =>ucs*
1363 #=>mojikyo
1364 #=mojikyo
1365 ->identical
1366
1367 ancient-ideograph-of
1368 ancient-char-of-shinjigen-1
1369 original-ideograph-of
1370 original-char-of-shinjigen-1
1371 simplified-ideograph-of
1372 vulgar-ideograph-of
1373 vulgar-char-of-shinjigen-1
1374 ideograph=
1375 ideographic-variants
1376 variant-of-shinjigen-1
1377
1378 iso-10646-comment
1379 ".split
1380     def initialize
1381       @odir = DB_DIR+"/system-char-id/obsolete" #直打ちしている。
1382     end
1383     def move_obsolete_files # 廃止予定のbdbファイルをobsoleteディレクトリーにつっこむ
1384       db = CharDB.instance
1385       db.close_all
1386       Dir.mkdir(@odir) unless FileTest.directory? @odir
1387       OBSOLETE_ATTRIBUTES.each {|attr|
1388         next if attr =~ /^#/
1389         filename = db.get_filename(attr)
1390         move_to_obsolete(filename)
1391         move_to_obsolete(filename+".txt")
1392       }
1393     end
1394     def move_to_obsolete(file)
1395       cmd = "mv #{file} #{@odir}"
1396       #      p cmd
1397       system cmd
1398     end
1399   end
1400
1401   class JoyoList #======================================================================
1402     include Singleton
1403     #JP_JOYO_FILE = DB_DIR+"/../jp-joyo.txt" #EUC-jisx0213
1404     JP_JOYO_FILE = DB_DIR+"/../joyo-ucs.txt" #UCS
1405     COMPOSIT_KANJI = "鳴名加品古知問間聞取兄見切分粉貧林森校東明住位好岩砂里男畑習休短空坂島倉美孝赤看光初努協解新歌語話張強忘悲答晴現正字安守灰秋秒困国医包同合舌居右左受友反道返迷花菜集机主太氷州点店庫仕帳幼防引配早直班筆重番北化比死夏後進酒福私家世内谷半原前寺思電雲気布旅衆泣"
1406     #    COMPOSIT_KANJI = "鳴名加品古"
1407     def initialize
1408       @nchars = []
1409       read_file
1410     end
1411     attr_reader :nchars
1412     def read_file
1413       open(JP_JOYO_FILE) {|f|
1414         while line = f.gets
1415           next if line =~ /^;/ #コメントはとばす
1416           line.chomp!
1417           #stroke, nchar, ochar = line.split #new char, old char, old charはnilが多い
1418           stroke, nchar = line.split
1419           @nchars << nchar
1420         end
1421       }
1422     end
1423     def dump_ids(ar)
1424       ar.each {|ch|
1425         char = ch.char
1426         print char.inspect_ids(true), "\t;", char.inspect_ids(false), "\n"
1427       }
1428     end
1429   end
1430
1431 end
1432
1433 #----------------------------------------------------------------------終了