start Ruby/CHISE
[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 char_length() to_a.length end
17   def char_at(n) to_a()[n] end
18   def char() Character.get(to_a[0]) end
19   def method_missing(mid, *args) char.method_missing(mid, *args) end
20   def char_id() char.char_id() end
21   def get_char_attribute(a) char.get_char_attribute(a) end
22   def ucs() char.ucs() end
23   def to_utf8()
24     return to_a.map {|ch|
25       ch.char.to_utf8
26     }.join('')
27   end
28
29   #put関係、[]関係は用意しないことにした。
30   def de_er!() #EntityReferenceを取り除く
31     return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
32     er = "&"+$1+";"      
33     self.sub!(Regexp.new(Regexp.escape(er)), Character.new(er).mcs_utf8) #変換自体はCharacterにまかせる
34     return self.de_er! if self =~ Regexp.new(EntityReference::REGEXP_PART) #まだあったら再帰
35     return self
36   end
37   def de_er() return self.dup.de_er!; end
38
39   def map_char(block = Proc.new)
40     return unless block_given?
41     return self.to_a.map {|ch| (block.call(ch)).to_s }.join("")
42   end
43   def map_char!(block = Proc.new)
44     return unless block_given?
45     self.replace(self.map_char {|ch| block.call(ch)})
46   end
47   def inspect_all() map_char {|ch| ch.char.inspect_all } end
48   def inspect_x()   map_char {|ch| ch.char.inspect_x   } end
49
50   def decompose() map_char {|ch| ch.char.decompose } end
51   def decompose!() self.replace(self.decompose); self; end
52   def decompose_all(level=nil)
53     level = 0 if level.nil?
54     if 10 < level
55       p ['too many recursive', self] 
56       exit
57     end
58     de = self.decompose
59     return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
60     de #もうこれ以上変化は無さそうだぞと。
61   end
62   def decompose_all!() self.replace(self.decompose_all); self; end
63
64   def find() #"日雲"→"曇"とかいう感じの操作
65     ar = []
66     length = char_length()
67     each_char {|ch|
68       char = ch.char
69       ar << char.ids_contained #その文字を含んでいる漢字のリスト
70     }
71     h = Hash.new(0)
72     ar.each {|list|
73       next if list.nil?
74       list.each_char {|ch|
75         h[ch] += 1
76       }
77     }
78     str = ""
79     h.each {|k, v|
80 #      p [k, v]
81       if length == v #全部に顔を出していたら
82         str += k
83       end
84     }
85 #    p str
86     str
87   end
88   def compose()
89     db = CodesysDB.instance
90     composed = db.get('ids', self)
91     return "" if composed.nil? #なかったよと。
92     return "" if composed.char_length == 0 #なにごと?
93     return composed if composed.char_length == 1
94     composed.each_char {|ch|
95       char = ch.char
96       return ch if char.has_attribute? #とりあえず最初にみつかったものを返すというヌルい仕様
97     }
98   end
99   def nu_compose_sorted()
100     db = CodesysDB.instance
101     composed = db.get('ids', self)
102     return "" if composed.nil? #なかったよと。
103     return "" if composed.char_length == 0 #なにごと?
104     return composed if composed.char_length == 1
105     ar = []
106     composed.each_char {|ch|
107       char = ch.char
108       ar << ch if char.has_attribute?
109     }
110     ar2 = []
111     ar.each {|ch|
112       char = ch.char
113       if char.char_id < 0xfffff #Unicodeっぽい?
114         ar2 << ch
115         ar.delete(ch)
116       end
117     }
118     EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
119       ar.each {|ch|
120         char = ch.char
121         v = char[codesys]
122         if v #EntityReferenceの順番に準拠する。
123           ar2 << ch
124           ar.delete(ch)
125         end
126       }
127     }
128     if 0 < ar.length
129       p ['yokuwakaran character', ar, ar[0].inspect_all]
130     end
131     return ar2.join("")
132   end
133   def nu_compose_ar()
134     ar = []
135     CharDB.instance.each_sort('ids'){|k, v| #文字, IDS
136 #      if v =~ self
137       if v == self
138         ar << k
139       end
140     }
141     ar
142   end
143   def aggregate()
144 #selfである文字列をIDSだと仮定し、それを完全にcomposeしきらないで、
145 #その部分集合だけをとりだして、compose可能であればできるだけcomposeする。
146     tree = IDS_Tree.new(self)
147     return self if tree.depth <= 1 #sub_nodesが無い場合はここでさよなら
148     tree.sub_nodes.each {|node|
149       c = node.compose
150       next if c.nil? || c == ""
151       n = self.gsub(node, c)
152       return n.aggregate
153     }
154     return self #おきかえられるものがまったくなかったら、自分をかえす。
155   end
156 end
157
158 module CHISE
159   def windows?()
160     (RUBY_PLATFORM =~ /win/ || RUBY_PLATFORM =~ /mingw/)
161   end
162   module_function :windows?
163   if windows?()
164     DB_DIR = 'd:/work/chise/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
165   else
166     DB_DIR = '/usr/local/lib/xemacs-21.4.10/i686-pc-linux/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
167   end
168
169   class EntityReference #======================================================================
170     #状況によってどのERに変換するかが異なる可能性があるので、普通のclassとして実装したほうがいい?
171     CODESYS_TABLE = [
172       %w( chinese-big5-cdp      CDP- 4 X),
173       %w( ideograph-daikanwa    M-   5 d),
174       %w( ideograph-cbeta       CB   5 d),
175       %w( ideograph-gt          GT-  5 d),
176       %w( ideograph-gt-k        GT-K 5 d),
177       %w( japanese-jisx0208-1990 J90- 4 X),
178       %w( japanese-jisx0208     J83- 4 X),
179       %w( japanese-jisx0213-1   JX1- 4 X),
180       %w( japanese-jisx0213-2   JX2- 4 X),
181       %w( japanese-jisx0212     JSP- 4 X),
182       %w( japanese-jisx0208-1978 J78- 4 X),
183       %w( chinese-cns11643-1    C1-  4 X),
184       %w( chinese-cns11643-2    C2-  4 X),
185       %w( chinese-cns11643-3    C3-  4 X),
186       %w( chinese-cns11643-4    C4-  4 X),
187       %w( chinese-cns11643-5    C5-  4 X),
188       %w( chinese-cns11643-6    C6-  4 X),
189       %w( chinese-cns11643-7    C7-  4 X),
190       %w( korean-ksc5601        K0- 4 X),
191     ]
192     CODESYS_ORDER = %w(japanese chinese korean ideograph)
193     REGEXP_PART = "&([-+0-9A-Za-z]+);"
194     REGEXP_ALL = "^#{REGEXP_PART}$"
195
196     def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
197
198     def self.parse(er) #char_idをFIXNUMで返す
199       return "" unless er =~ Regexp.new(REGEXP_ALL) #なんか、間違ってる?
200       er = $1 #ついでに中身の部分を取り出す
201       return $1.hex if er =~ /^MCS-([0-9A-Fa-f]+)/ #MCS
202       return $1.hex if er =~ /^U[-+]([0-9A-Fa-f]+)/ #Unicode直打ち
203
204       er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
205       each_codesys {|codesys, er_prefix, keta, numtype| #p [codesys, er_prefix, keta, numtype]
206         numtyperegex = '\d' #if numtype == 'd'
207         numtyperegex = '[0-9A-Fa-f]' if numtype == 'X'
208         regexpstr = "^#{er_prefix}(#{numtyperegex}{#{keta},#{keta}})$"  #p regexpstr
209         if er =~ Regexp.new(regexpstr)
210           codestr = $1
211           code = codestr.to_i #if numtype == 'd'
212           code = codestr.hex if numtype == 'X'
213           char_id_u8 = EntityReference.get_database(codesys, code)
214           char_id_num = Character.parse_char_id(char_id_u8)
215           return char_id_num
216         end
217       }
218       return ""
219     end
220     def self.each_codesys()
221       CODESYS_ORDER.each {|lang|
222         CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype| #普通こういう書き方はしない。ループ一個にする。
223           next unless codesys =~ lang
224           yield(codesys, er_prefix, keta, numtype)
225         }
226       }
227     end
228     def self.get_database(codesys, code)
229       c = CodesysDB.instance.get(codesys, code)
230       return c if c != nil
231       if codesys =~ /-jisx0208/
232         #return self.get_database("=jis-x0208", code) #再帰でどうだ?
233         c = CodesysDB.instance.get("=jis-x0208", code)
234         return c
235       end
236       return nil
237     end
238   end
239
240   class CharacterFactory #============================================文字オブジェクトの生成、cache
241     include Singleton
242     MAX = 10000
243     def initialize
244       @max = MAX
245       reset()
246     end
247     def get(char_id)
248       check_max()
249       n = Character.parse_char_id(char_id)
250       c = @chars[n]
251       @chars[n] = Character.new(n) if @chars[n] == nil
252       return @chars[n]
253     end
254     def reset()
255       @chars = nil
256       @chars = Hash.new
257       GC.start #ガーベージコレクション
258     end
259     def length() @chars.length; end
260     def check_max()
261       reset if @max < @chars.length #MAXを超えたらresetしてしまう。乱暴じゃがcacheなのでこれでいいのだ。
262     end
263   end
264
265   class Character #=============================================================== 文字オブジェクト
266     def initialize(char_id=nil)
267       @char_id = Character.parse_char_id(char_id)
268       @attributes = Hash.new
269       @check_all_database = false
270     end
271     attr_reader :char_id
272     def mcs_utf8() Character.u4itou8(@char_id) end
273
274     #----------------------------------------------------------------------
275     def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
276
277     #----------------------------------------------------------------------
278     def get_char_attribute(a) # XEmacs UTF-2000互換API群
279       a.gsub!(/_/, '-') #underlineは-に置換
280       atr = @attributes[a]
281       return atr if atr != nil
282       atr = check_database(a)
283       @attributes[a] = atr if atr != nil
284       return get_char_attribute("=jis-x0208") if a =~ /jisx0208/
285       return @attributes[a]
286     end
287     def put_char_attribute(a,v)
288       a.gsub!(/_/, '-') #underlineは-に置換
289       @attributes[a] = v;
290       CharDB.instance.put(a, mcs_utf8(), v)
291     end
292     def char_attribute_alist() check_all_database(); @attributes; end
293     def char_attribute_list()  check_all_database(); @attributes.keys; end
294     alias [] get_char_attribute  #その略称
295     alias []= put_char_attribute
296     alias alist char_attribute_alist
297     alias list  char_attribute_list
298
299     def method_missing(mid, *args) #参考:ostruct.rb
300       mname = mid.id2name
301       return get_char_attribute(mname) if args.length == 0
302       put_char_attribute(mname.chop, args[0]) if mname =~ /=$/ #代入
303     end
304
305     def has_attribute?() #意味のあるattributeを持ってますか?
306       keys = list
307       keys.delete_if {|k|
308         k =~ /ids/
309       }
310       return (keys.length != 0)
311     end
312
313     #----------------------------------------------------------------------
314     def ==(ch)
315       return false if ch == nil
316       return false unless ch.is_a? Character
317       self.char_id == ch.char_id
318     end
319
320     #----------------------------------------------------------------------
321     def self.parse_char_id(char_id) #FIXNUMを返す
322       return nil if char_id == nil
323       if char_id.is_a?(Numeric) #p [char_id]
324         char_id = 0x80000000 + char_id if char_id < 0  #補数表現
325         return char_id.to_i
326       elsif char_id.is_a?(String)
327         return char_id.to_i if char_id =~ /^\d+$/ #文字列による数字だったら数値化してreturn
328         return EntityReference.parse(char_id) if char_id =~ Regexp.new(EntityReference::REGEXP_ALL) #実体参照?
329         char_id.sub!(/^\?/, '') if char_id =~ /^\?/ #もし先頭に?がついていたら削除
330         #このへん本当はもっとちゃんとチェックするべし
331         u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
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 "" if num == nil
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       #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
381       ar.each {|a|      #p [a]
382          u = get_char_attribute(a)
383          return u if u != nil
384        }
385        return nil
386      end
387
388      #----------------------------------------------------------------------CCS関係
389      def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
390      #alias to_s to_utf8
391      alias to_s mcs_utf8
392
393      #----------------------------------------------------------------------
394      def to_er(codesys=nil) #実体参照を返す、希望するcodesysが引数(未実装)
395        return "" if @char_id == nil
396        return sprintf("&U+%04X;", @char_id) if @char_id <= 0xffff
397        return sprintf("&U-%05X;", @char_id) if @char_id <= 0xfffff
398        EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
399          code = self[codesys]
400          next if code == nil
401          return sprintf("&#{er_prefix}%0#{keta}#{numtype};", code)
402        }
403        return sprintf("&MCS-%08X;", @char_id) #本当はこれは無しにしたい
404      end
405      def to_er_list()
406        ar = []
407        EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
408          er = to_er(codesys)
409          ar << er if er != nil
410        }
411        ar
412      end
413
414      def inspect_x()
415        return "<>" if @char_id == nil
416        ar = [to_utf8(), to_er().sub(/^&/,'').chop]
417        "<"+ar.join(',')+">"
418      end
419      alias inspect inspect_x
420      def inspect_all_codesys()
421        #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
422      end
423      def inspect_all()
424        ar = [inspect.chop]
425        alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
426        return ar.join(',')+">"
427      end
428      def get_attributes()
429        str = ""
430        alist.to_a.sort.each {|a, v|
431          str += "#{a}: #{v}\n"
432        }
433        str
434      end
435
436      #----------------------------------------------------------------------IDS関係
437      def decompose
438        k = self.to_s
439 #       idss = self['ids']
440 #       return idss if idss
441        idss = self['ids-aggregated']
442        return idss if idss != nil && 0 < idss.length && k != idss
443        idss = self['ids']
444        return idss if idss != nil && 0 < idss.length && k != idss
445        return k
446 #       return k if idss.nil? || idss.length == 0 || k == idss
447 #       if idss.char_length == 2
448 #       p ['What???', k, idss, k.inspect_all]
449 #        #return idssx[1] #二個目だけ返すとか?
450 #        return k #IDSに展開する方法が無いと。
451 #       end
452 #       return k if k == idss
453 #       if idss.include?(k) #<C5-4C4D><C6-4A37>この二文字のBUG対策
454 #        #return idss.sub(k, '')
455 #        return k #IDSに展開する方法が無いと。
456 #       end
457 #       return idss
458      end
459      def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
460      def ids_operator_argc()
461        return 0 unless is_ids?
462        return 3 if @char_id == 0x2ff2 || @char_id == 0x2ff3
463        return 2
464      end
465    end
466
467    class DBS #======================================================================複数のDBを集めたclass
468    end
469
470    class ADB < BDB::Hash #======================================================================一つのDB
471      def initialize(*args)
472        super
473        @modified = false
474        at_exit {
475          if @modified
476            self.close #これがないと、うまくデータベースがセーブされないのです。
477          end
478        }
479      end
480      def self.open_create(filename)
481        ADB.open(filename, nil, BDB::CREATE | BDB::EXCL) #上書きはしない
482      end
483      def mykey(key)
484        if key.is_a?(String)
485          if key.char_length == 1
486            return '?'+key  #Stringだったら引く前に?を足す
487          end
488        end
489        #key = key.to_s if key.is_a?(Numeric) #NumberだったらStringにする。
490        #ここで && key ! =~ /^\?/ をいれると、?自身を検索できなくなってしまう。
491        return key
492      end
493      def myvalue(v)
494        return v if v == nil
495        return v.to_i if v =~ /^\d+$/ #数字だったらここで変換しておく
496        return v.sub(/^\?/, '') if v =~ /^\?/ #冒頭の?は取り除く
497        return $1 if v =~ /^"(.+)"$/ #最初と最後に"がついていたら、取り除く
498        #p ['get', v, t, key, db]
499        #return parse_sexp(v) if v =~ /^\(.+\)$/ #最初と最後が()の時は、S式にparseする
500        return v #それ以外って何?
501      end
502      def myget(key) #keyキーを引いて返す
503        key = mykey(key)
504        v = get(key) #存在しなかったらnilを返すことになる
505        return myvalue(v)
506      end
507      def myput(key, v) #keyにvをいれる
508        key = mykey(key)
509        put(key, v) #putする
510        @modified = true
511      end
512    end
513
514    class DB #======================================================= データベース群のabstract class
515      def self.unix_to_win(unix) #Windowsファイル名制限のため、変換する
516        win = unix.gsub(/</, '(')
517        win.gsub!(/>/, ')')
518        win.gsub!(/\*/, '+')
519        win.gsub!(/\?/, '!')
520        return win
521      end
522      def self.win_to_unix(win)
523        unix = win.gsub(%r|\)|, '>')
524        unix.gsub!(%r|\(|, '<')
525        unix.gsub!(%r|!|, '?')
526        unix.gsub!(%r|\+|, '*')
527        return unix
528      end
529 #     def windows?() DB.windows?() end
530      def get_filename(t)
531        return @pre + DB.unix_to_win(t) + @post if windows?
532        return @pre + t + @post
533      end
534      def get_dirname(t) File.dirname(get_filename(t)) end
535      def open_dbs()
536        @dbs = Hash.new
537        keys = find_keys()
538        keys.each {|key| open_db(key) }
539      end
540      def find_keys()
541        files = []
542        Dir.glob(@glob){|f|
543          next if ! File.file?(f)
544          next if f =~ /.txt$/
545          files << f
546        }
547        keys = []
548        files.each {|f|
549          t = DB.win_to_unix(f)
550          t.sub!(%r|^#{@pre}|, '')
551          t.sub!(%r|#{@post}$|, '') if @post != ""
552          keys << t
553        }
554        return keys
555        #return keys.sort
556      end
557      def close_db(t)
558        db = get(t)
559        return nil if db.nil?
560        db.close
561        @dbs.delete(t)
562      end
563      def open_db(t)
564        return nil if get(t) #すでにopenしていたら再openはしない。
565        begin
566          bdb = ADB.open(get_filename(t), nil, 0)
567          @dbs[t] = bdb if bdb != nil
568        rescue
569          p ["open error", get_filename(t)]; return nil
570        end
571        return true
572      end
573      def make_db(t, h=nil) #tという名前でhという中身のデータベースを作る
574        return nil if get(t) #すでにある場合はreturn
575        Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
576        db = nil
577        begin
578          db = ADB.open_create(get_filename(t)) #上書きはしない
579          if h != nil
580            h.each {|k, v|
581              k = '?'+k if k.is_a?(String)
582              db[k] = v
583            }
584          end
585          db.close
586        rescue
587          p ["make error", get_filename(t)]; return nil
588        end
589        return true
590      end
591      def make_db_no_question_mark(t, h=nil) #tという名前でhという中身のデータベースを作る
592        return nil if get(t) #すでにある場合はreturn
593        Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
594        db = nil
595        begin
596          db = ADB.open_create(get_filename(t)) #上書きはしない
597          if h != nil
598            h.each {|k, v|
599 #            k = '?'+k if k.is_a?(String)
600              db[k] = v
601            }
602          end
603          db.close
604        rescue
605          p ["make error", get_filename(t)]; return nil
606        end
607        return true
608      end
609      def remove_db(t) #tという名前のデータベースを消去する
610        db = get(t)
611        if db
612          db.close
613          @dbs.delete(t)
614        end
615        begin
616          File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
617        rescue
618          p ["unlink error", get_filename(t)]; return nil
619        end
620        dn = get_dirname(t)
621        Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
622        return true
623      end
624      def to_num(s)
625        return s.to_i if s =~ /^\d+$/
626        return s
627      end
628      def dump_db(t)
629        db = get(t)
630        return nil unless db
631        file = get_filename(t)
632        open("#{file}.txt", "w"){|out|
633 #        out.binmode.sync = true
634          ar = db.to_a
635          ar.map! {|k, v| [to_num(k), to_num(v)] }
636          ar.sort.each {|k, v|
637            out.printf("%s\t%s\n", k, v)
638          }
639        }
640        return true
641      end
642      def each_db()  @dbs.to_a.sort.each {|t, db| yield(t, db) } end
643      def dump_all()  each_db {|t, db| dump_db(t) } end
644      def close_all() each_db {|t, db| db.close   } end
645      def keys() @dbs.keys end
646      def each(t)
647        return unless block_given?
648        db = @dbs[t]
649        return nil unless db
650        db.each {|k, v|
651          k = to_num(k)
652          v = to_num(v)
653          k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
654          vv = get(t, k) #p ['each', t, k, v, vv]
655          yield(k, vv)
656        }
657      end
658      def each_sort(t)
659        return unless block_given?
660        db = @dbs[t]
661        return nil unless db
662        ar = db.to_a
663        ar.map! {|k, v| [to_num(k), to_num(v)] }
664        ar.sort.each {|k, v|
665          k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
666          vv = get(t, k) #p ['each', t, k, v, vv]
667          yield(k, vv)
668        }
669      end
670      #----------------------------------------------------------------------
671      def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
672        db = @dbs[t]
673        return db if key.nil?
674        return nil unless db
675        return db.myget(key)
676      end
677      def put(t, key, v) #tというデータベースのkeyにvをいれる
678        db = @dbs[t]
679        if db == nil
680          db = make_db(t) 
681          db = open_db(t) 
682          db = @dbs[t]
683        end
684        db.myput(key, v) #putする
685      end
686    end
687
688    class CharDB < DB #------------------------------------ MCS-UTF8をキーとした属性へのデータベース
689      include Singleton
690      def initialize()
691        super
692        @glob, @pre, @post = "#{DB_DIR}/system-char-id/*", "#{DB_DIR}/system-char-id/", ""
693        open_dbs()
694      end
695      def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
696        atrs = Hash.new
697        @dbs.each {|t, db|
698          v = get(t, u8)
699          atrs[t] = v if v != nil
700        }
701        return atrs
702      end
703    end
704
705    class CodesysDB < DB #----------------------------------------------------------------------
706      include Singleton
707      def initialize()
708        super
709        @glob, @pre, @post = "#{DB_DIR}/*/system-char-id", "#{DB_DIR}/", "/system-char-id"
710        open_dbs()
711      end
712      #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
713      def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
714      def get_codesys(t)
715        db = get(t)
716        return nil unless db
717        return Codesys.new(t)
718      end
719    end
720
721    class Codesys < DB #======================================================================
722      def initialize(name)
723 #       super
724        @name = name
725        @dbs = CodesysDB.instance
726      end
727      def keys() #どんなコードポイントの情報を持っているかの一覧
728        ks = @dbs.get(@name).keys
729        if @name =~ /jisx0208/ #特別処理
730          n = @dbs.get('=jis-x0208').keys 
731          #       p ['keys', @name, ks, n]
732          ks += n
733        end
734        ks.map! {|k| to_num(k) }
735        ks
736      end
737      def get(key)
738        v = @dbs.get(@name, key)
739        return v if v
740        if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
741          return @dbs.get('=jis-x0208', key)
742        end
743        return nil
744      end
745      def each()
746        return unless block_given?
747        db = @dbs.get(@name)
748        return nil unless db
749        db.each {|k, v|
750          k = to_num(k)
751          v = to_num(v)
752          k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
753          vv = @dbs.get(@name, k)        #p ['each', t, k, v, vv]
754          yield(k, vv)
755        }
756      end
757      def each_sort()
758        return unless block_given?
759        db = @dbs.get(@name)
760        return nil unless db
761        ar = db.to_a
762        ar.map! {|k, v| [to_num(k), to_num(v)] }
763        ar.sort.each {|k, v|
764          k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
765          vv = @dbs.get(@name, k)        #p ['each', t, k, v, vv]
766          yield(k, vv)
767        }
768      end
769    end
770
771   class IDS_TEXT_DB < DB #======================================================================
772     include Singleton
773     if CHISE.windows?()
774       IDS_DB_DIR = 'd:/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
775     else
776       IDS_DB_DIR = '/home/eto/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
777     end
778     IDS_LIST = "
779 IDS-JIS-X0208-1990.txt
780 IDS-CBETA.txt
781 IDS-Daikanwa-01.txt
782 IDS-Daikanwa-02.txt
783 IDS-Daikanwa-03.txt
784 IDS-Daikanwa-04.txt
785 IDS-Daikanwa-05.txt
786 IDS-Daikanwa-06.txt
787 IDS-Daikanwa-07.txt
788 IDS-Daikanwa-08.txt
789 IDS-Daikanwa-09.txt
790 IDS-Daikanwa-10.txt
791 IDS-Daikanwa-11.txt
792 IDS-Daikanwa-12.txt
793 IDS-Daikanwa-dx.txt
794 IDS-Daikanwa-ho.txt
795 IDS-UCS-Basic.txt
796 #IDS-UCS-Compat-Supplement.txt
797 #IDS-UCS-Compat.txt
798 IDS-UCS-Ext-A.txt
799 IDS-UCS-Ext-B-1.txt
800 IDS-UCS-Ext-B-2.txt
801 IDS-UCS-Ext-B-3.txt
802 IDS-UCS-Ext-B-4.txt
803 IDS-UCS-Ext-B-5.txt
804 IDS-UCS-Ext-B-6.txt
805 ".split
806     def initialize()
807       super
808       @ids_list = IDS_LIST
809       @chars = []
810       @glob, @pre, @post = "#{IDS_DB_DIR}/db/*", "#{IDS_DB_DIR}/db/", ""
811       dir = File.dirname(@pre)
812       Dir.mkdir(dir) unless FileTest.exist?(dir)
813       open_dbs()
814     end
815     def each_file()
816       return unless block_given?
817       @ids_list.each {|file|
818         next if file =~ /^#/
819         yield(IDS_DB_DIR+file)
820       }
821     end
822     def each_line(file)
823       open(file){|f|
824         while line = f.gets
825           next if line =~ /^;/ #コメントはとばす
826           line.chomp!
827           code, char, ids = line.split
828           yield(code, char, ids)
829         end
830       }
831     end
832     def dump_text_all
833       each_file {|file|
834         dir = File.dirname(file) + '/../ids-new/'
835         Dir.mkdir(dir) if ! FileTest.directory?(dir)
836         newfile = dir + File.basename(file)
837         p [file, newfile]
838         open(newfile, "w"){|out|
839           out.binmode.sync = true
840           each_line(file){|code, ch, ids|
841             char = Character.get(ch)
842             ids = char.decompose
843             out.print "#{code}  #{ch}   #{ids}\n"
844           }
845         }
846       }
847     end
848     def make_ids_error
849       each_file {|file|
850         dir = File.dirname(file) + '/../ids-error'
851         Dir.mkdir(dir) unless FileTest.exist?(dir)
852         errfile = dir + '/' + File.basename(file)
853 #       p [file, errfile]
854         open(errfile, "w"){|out|
855           out.binmode.sync = true
856           each_line(file){|code, ch, ids|
857             char = Character.get(ch)
858             ids_error = char['ids-error']
859             next if ids_error.nil?
860             out.print "#{code}  #{ch}   #{ids}  #{ids_error}\n"
861           }
862         }
863       }
864     end
865   end
866
867   class IDS_DB < DB #======================================================================BDB化したIDS DBを扱う
868     include Singleton
869     def initialize
870       @dbs = CharDB.instance
871     end
872     def make_ids_db
873       db = IDS_TEXT_DB.instance
874       db.each_file {|file|
875         db.each_line(file){|code, ch, ids|
876           char = Character.get(ch) #実体参照である
877           ids = "" if ids == nil
878           ids.de_er! #実体参照を解除する
879           char['ids-text'] = ids
880         }
881         p [file, CharacterFactory.instance.length]
882         CharacterFactory.instance.reset()
883       }
884       @dbs.dump_db('ids-text') #テキスト化する
885     end
886     def make_ids_error_check
887       @dbs.each('ids-text') {|k, ids|
888         next if k.nil? || k == "" || ids.nil? || ids == "" #無視します
889         next if k == ids #問題無しなので
890         char = k.char
891         idstree = IDS_Tree.new(ids)
892         c = idstree.check_integrity
893         c = "contains self" if ids.include?(k)
894         c = "no attribute"  if !char.has_attribute? #isolated characterはまぎれこませない。
895         if c
896           char['ids-error'] = c
897         else
898           char['ids'] = ids
899         end
900 #         print c,"\t", k.char.to_er,"\t", k,"\t", v,"\n" 
901       }
902       @dbs.dump_db('ids-error') #テキスト化する
903       @dbs.dump_db('ids') #テキスト化する
904     end
905     def make_ids_reverse
906       h = Hash.new
907       @dbs.each('ids') {|k, v|
908         char = k.char
909         ids = char.decompose
910         h[ids] = "" if h[ids].nil?
911         h[ids] += k #追加する
912       }
913       h.each {|k, v|
914         h[k] = char_sort(v) #文字の順番を、よく使うっぽいものからの順番にする
915       }
916       h.delete_if {|k, v| #h[k]が""になる可能性もあるが、それはkeyとして入れないことにする。
917         v == ""
918       }
919       p ['length', h.length]
920       cdb = CodesysDB.instance
921       cdb.make_db_no_question_mark('ids', h)
922       cdb.dump_db('ids')
923     end
924     def char_sort(composed)
925       return composed if composed.char_length == 1
926       ar = composed.to_a
927       arorg = ar.dup
928       ar2 = []
929       ar.dup.each {|ch|
930         char = ch.char
931         if char.char_id < 0xfffff #Unicodeっぽい?
932           ar2 << ch
933           ar.delete(ch)
934         end
935       }
936       if 0 < ar.length
937         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
938           ar.each {|ch|
939             char = ch.char
940             v = char[codesys]
941 #           p [codesys, v] if v
942             if v #EntityReferenceの順番に準拠する。
943               ar2 << ch
944               ar.delete(ch)
945             end
946           }
947         }
948       end
949       if 0 < ar.length
950 #       p ['yokuwakaran character', ar, ar[0].inspect_all, arorg]
951         EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
952           ar.dup.each {|ch|
953             char = ch.char
954             v = char[codesys]
955 #           p [codesys, v] if v
956           }
957         }
958       end
959       return ar2.join("")
960     end
961     def dump_ids_duplicated
962       open('ids-duplicated.txt', 'w'){|out|
963         #out.binmode
964         CodesysDB.instance.each('ids') {|k, v|
965           if v.nil?
966             out.print "nil      #{k}    #{v}\n"
967             next
968           end
969           n = v.char_length
970           next if n == 1
971           out.print "#{n}       #{k}    #{v}"
972           v.each_char {|ch|
973             char = ch.char
974             out.print " #{char.inspect}"
975           }
976           out.print "\n"
977         }
978       }
979     end
980     def make_ids_aggregated
981       @dbs.each('ids') {|k, v|
982         char = k.char
983         ids = char.decompose
984         ag = ids.aggregate
985         char['ids-aggregated'] = ag
986       }
987       @dbs.dump_db('ids-aggregated')
988     end
989     def dump_ids_aggregated
990       open('ids-aggregated.txt', 'w'){|out|
991         #out.binmode
992         @dbs.each('ids') {|k, v|
993           char = k.char
994           ids = char['ids']
995           ag  = char['ids-aggregated']
996           out.print "#{char.to_s}       #{ag}   #{ids}\n" if ids != ag
997         }
998       }
999     end
1000     def make_ids_parts
1001       @dbs.each('ids') {|k, v|
1002         char = k.char
1003         pids = char.to_s
1004         ar = []
1005         counter = 0
1006         loop {
1007           ids = pids.decompose
1008           break if ids == pids #これ以上分割できないようだったら終了〜。
1009           ar += ids.to_a
1010           counter += 1
1011           p [char.to_s, pids, ids, ar] if 10 < counter #これは何かおかしいぞと
1012           pids = ids
1013         }
1014         ar.sort!
1015         ar.uniq!
1016 #やっぱりIDS文字も加えることにする. by eto 2003-02-05
1017 #       ar.delete_if {|ch|
1018 #         ch.char.is_ids? #IDS文字はまぎれこませない。
1019 #       }
1020         str = ar.join('')
1021         char['ids-parts'] = str
1022       }
1023       @dbs.dump_db('ids-parts')
1024     end
1025     def make_ids_contained
1026       h = Hash.new
1027       @dbs.each('ids-parts') {|k, v|
1028         char = k.char
1029         parts = char.ids_parts
1030         parts.each_char {|ch|
1031 #         part = ch.char
1032           h[ch] = [] if h[ch].nil?
1033           h[ch] << k
1034 #         h[ch] += k
1035 #         part['ids-contained'] = "" if part['ids-contained'].nil?
1036 #         part['ids-contained'] += k
1037         }
1038       }
1039       h.each {|k, v|
1040         char = k.char
1041         v.sort!
1042         char['ids-contained'] = v.join('')
1043         
1044       }
1045       @dbs.dump_db('ids-contained')
1046     end
1047     def make_ids_decomposed
1048       @dbs.each('ids') {|k, v|
1049         char = k.char
1050         de= char.decompose_all
1051         char['ids-decomposed'] = de
1052       }
1053       @dbs.dump_db('ids-decomposed')
1054     end
1055   end
1056
1057   class Node < Array #=======================================================木構造の中の一つの枝
1058     def initialize(nodeleaf=nil, nodenum=nil)
1059       super()
1060       @nodeleaf = nodeleaf
1061       @nodenum = nodenum
1062       if @nodeleaf
1063         original_add(@nodeleaf)
1064       end
1065     end
1066     attr_reader :nodenum
1067     alias original_add <<
1068     private :original_add
1069     def <<(obj)
1070       original_add(obj)
1071       @nodenum -= 1 if @nodenum
1072     end
1073     def nodes
1074       ar = []
1075       ar << self.to_s
1076       self.each {|n|
1077         ar += n.nodes if n.is_a? Node
1078       }
1079       return ar
1080     end
1081   end
1082
1083   class Tree #======================================================================木構造を扱う
1084     def initialize()
1085       @root = Node.new()
1086       @stack = [@root]
1087       @leafnum = 0
1088       @depth = 1 #stackの深さが最大になったところの値、木構造が無いときは1となる
1089     end
1090     def depth() @depth - 1 end
1091     def add_node(nodeleaf=nil, nodenum=nil) #枝を追加
1092       new_node = Node.new(nodeleaf, nodenum)
1093       @stack.last << new_node
1094       @stack << new_node
1095       if @depth < @stack.length
1096         @depth = @stack.length
1097       end
1098       self
1099     end
1100     def end_node() #この枝は終り
1101       @stack.pop
1102       self
1103     end
1104     def add_leaf(a) #葉を追加
1105       @stack.last << a
1106       end_check()
1107       self
1108     end
1109     def end_check()
1110       n = @stack.last.nodenum
1111       if n && n == 0
1112         end_node()
1113         end_check() #再帰
1114       end
1115     end
1116     def check_integrity
1117       n = @stack.last.nodenum
1118       return nil if @root.length == 0 #no tree is good tree
1119       return "unmatch leaves" if n && n != 0
1120       return "extra nodes" if @root.first.is_a?(Node) && @root.length != 1
1121       return "extra leaves" if @root.length != 1
1122       return nil
1123     end
1124     def nodes
1125       r = @root.nodes
1126       r.shift
1127       r
1128     end
1129     def sub_nodes
1130       r = nodes
1131       r.shift
1132       r
1133     end
1134     def to_s()    @root.to_s    end
1135     def inspect() @root.inspect end
1136   end
1137
1138   class IDS_Tree < Tree #======================================================================
1139     def initialize(str)
1140       @str = str
1141       super()
1142       parse()
1143     end
1144     def parse()
1145       @str.each_char {|ch|
1146         char = Character.new(ch)
1147         if is_ids?(char)
1148           add_node(char, ids_operator_argc(char))
1149         else
1150           add_leaf(char)
1151         end
1152       }
1153     end
1154     def is_ids?(obj)
1155       return true if "+*".include?(obj.to_s) #テスト用ですかね
1156       return true if obj.is_ids?
1157       return false
1158     end
1159     def ids_operator_argc(obj)
1160       return obj.ids_operator_argc if 0 < obj.ids_operator_argc
1161       return 2 #テスト用ってことで
1162     end
1163     def check_integrity
1164       r = super
1165       return r if r #不完全がすでにわかっているならreturn
1166       return "contains ques" if @str =~ /\?/ #?が含まれている?
1167       return nil
1168     end
1169
1170   end
1171
1172   class IDS #======================================================================IDSそのものを扱うclass
1173     def initialize(str) #IDS文字列をうけとる。
1174       @str = str
1175     end
1176     def parse
1177     end
1178     def parse_x #柔軟型のParse. IDSキャラクターが前にきてなくてもよい。などなど。
1179     end
1180   end
1181
1182   class Counter #======================================================================
1183     #使い方
1184     #counter = Counter.new(50) { exit }
1185     #counter.count
1186     def initialize(max)
1187       @max = max
1188       @count = 0
1189       @proc = proc
1190     end
1191     def count
1192       @count += 1
1193       if @max <= @count
1194         @proc.call
1195       end
1196     end
1197   end
1198
1199 end
1200
1201 #----------------------------------------------------------------------終了