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