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