+#!c:/ruby/bin/ruby.exe
+# Ruby/UTF-2000 module by eto 2002-1114
+
+require 'bdb'
+require 'uconv'
+require 'singleton'
+
+$KCODE = 'u' #今のところこれ以外では動かない。String.splitが影響大。inspectも影響。
+$debug = true #これはテスト用
+#$stdout.binmode if $debug
+$stdout.sync = true if $debug
+
+class String
+ def to_a() self.split(//) end #$KCODEが設定されているので、UTF-8的な一文字づつがchにはいる
+ def each_char() to_a.each {|ch| yield ch } end
+ def char_length() to_a.length end
+ def char_at(n) to_a()[n] end
+ def char() Character.get(to_a[0]) end
+ def method_missing(mid, *args) char.method_missing(mid, *args) end
+ def char_id() char.char_id() end
+ def get_char_attribute(a) char.get_char_attribute(a) end
+ def ucs() char.ucs() end
+ def to_utf8()
+ return to_a.map {|ch|
+ ch.char.to_utf8
+ }.join('')
+ end
+
+ #put関係、[]関係は用意しないことにした。
+ def de_er!() #EntityReferenceを取り除く
+ return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
+ er = "&"+$1+";"
+ self.sub!(Regexp.new(Regexp.escape(er)), Character.new(er).mcs_utf8) #変換自体はCharacterにまかせる
+ return self.de_er! if self =~ Regexp.new(EntityReference::REGEXP_PART) #まだあったら再帰
+ return self
+ end
+ def de_er() return self.dup.de_er!; end
+
+ def map_char(block = Proc.new)
+ return unless block_given?
+ return self.to_a.map {|ch| (block.call(ch)).to_s }.join("")
+ end
+ def map_char!(block = Proc.new)
+ return unless block_given?
+ self.replace(self.map_char {|ch| block.call(ch)})
+ end
+ def inspect_all() map_char {|ch| ch.char.inspect_all } end
+ def inspect_x() map_char {|ch| ch.char.inspect_x } end
+
+ def decompose() map_char {|ch| ch.char.decompose } end
+ def decompose!() self.replace(self.decompose); self; end
+ def decompose_all(level=nil)
+ level = 0 if level.nil?
+ if 10 < level
+ p ['too many recursive', self]
+ exit
+ end
+ de = self.decompose
+ return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
+ de #もうこれ以上変化は無さそうだぞと。
+ end
+ def decompose_all!() self.replace(self.decompose_all); self; end
+
+ def find() #"日雲"→"曇"とかいう感じの操作
+ ar = []
+ length = char_length()
+ each_char {|ch|
+ char = ch.char
+ ar << char.ids_contained #その文字を含んでいる漢字のリスト
+ }
+ h = Hash.new(0)
+ ar.each {|list|
+ next if list.nil?
+ list.each_char {|ch|
+ h[ch] += 1
+ }
+ }
+ str = ""
+ h.each {|k, v|
+# p [k, v]
+ if length == v #全部に顔を出していたら
+ str += k
+ end
+ }
+# p str
+ str
+ end
+ def compose()
+ db = CodesysDB.instance
+ composed = db.get('ids', self)
+ return "" if composed.nil? #なかったよと。
+ return "" if composed.char_length == 0 #なにごと?
+ return composed if composed.char_length == 1
+ composed.each_char {|ch|
+ char = ch.char
+ return ch if char.has_attribute? #とりあえず最初にみつかったものを返すというヌルい仕様
+ }
+ end
+ def nu_compose_sorted()
+ db = CodesysDB.instance
+ composed = db.get('ids', self)
+ return "" if composed.nil? #なかったよと。
+ return "" if composed.char_length == 0 #なにごと?
+ return composed if composed.char_length == 1
+ ar = []
+ composed.each_char {|ch|
+ char = ch.char
+ ar << ch if char.has_attribute?
+ }
+ ar2 = []
+ ar.each {|ch|
+ char = ch.char
+ if char.char_id < 0xfffff #Unicodeっぽい?
+ ar2 << ch
+ ar.delete(ch)
+ end
+ }
+ EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
+ ar.each {|ch|
+ char = ch.char
+ v = char[codesys]
+ if v #EntityReferenceの順番に準拠する。
+ ar2 << ch
+ ar.delete(ch)
+ end
+ }
+ }
+ if 0 < ar.length
+ p ['yokuwakaran character', ar, ar[0].inspect_all]
+ end
+ return ar2.join("")
+ end
+ def nu_compose_ar()
+ ar = []
+ CharDB.instance.each_sort('ids'){|k, v| #文字, IDS
+# if v =~ self
+ if v == self
+ ar << k
+ end
+ }
+ ar
+ end
+ def aggregate()
+#selfである文字列をIDSだと仮定し、それを完全にcomposeしきらないで、
+#その部分集合だけをとりだして、compose可能であればできるだけcomposeする。
+ tree = IDS_Tree.new(self)
+ return self if tree.depth <= 1 #sub_nodesが無い場合はここでさよなら
+ tree.sub_nodes.each {|node|
+ c = node.compose
+ next if c.nil? || c == ""
+ n = self.gsub(node, c)
+ return n.aggregate
+ }
+ return self #おきかえられるものがまったくなかったら、自分をかえす。
+ end
+end
+
+module CHISE
+ def windows?()
+ (RUBY_PLATFORM =~ /win/ || RUBY_PLATFORM =~ /mingw/)
+ end
+ module_function :windows?
+ if windows?()
+ DB_DIR = 'd:/work/chise/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
+ else
+ DB_DIR = '/usr/local/lib/xemacs-21.4.10/i686-pc-linux/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
+ end
+
+ class EntityReference #======================================================================
+ #状況によってどのERに変換するかが異なる可能性があるので、普通のclassとして実装したほうがいい?
+ CODESYS_TABLE = [
+ %w( chinese-big5-cdp CDP- 4 X),
+ %w( ideograph-daikanwa M- 5 d),
+ %w( ideograph-cbeta CB 5 d),
+ %w( ideograph-gt GT- 5 d),
+ %w( ideograph-gt-k GT-K 5 d),
+ %w( japanese-jisx0208-1990 J90- 4 X),
+ %w( japanese-jisx0208 J83- 4 X),
+ %w( japanese-jisx0213-1 JX1- 4 X),
+ %w( japanese-jisx0213-2 JX2- 4 X),
+ %w( japanese-jisx0212 JSP- 4 X),
+ %w( japanese-jisx0208-1978 J78- 4 X),
+ %w( chinese-cns11643-1 C1- 4 X),
+ %w( chinese-cns11643-2 C2- 4 X),
+ %w( chinese-cns11643-3 C3- 4 X),
+ %w( chinese-cns11643-4 C4- 4 X),
+ %w( chinese-cns11643-5 C5- 4 X),
+ %w( chinese-cns11643-6 C6- 4 X),
+ %w( chinese-cns11643-7 C7- 4 X),
+ %w( korean-ksc5601 K0- 4 X),
+ ]
+ CODESYS_ORDER = %w(japanese chinese korean ideograph)
+ REGEXP_PART = "&([-+0-9A-Za-z]+);"
+ REGEXP_ALL = "^#{REGEXP_PART}$"
+
+ def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
+
+ def self.parse(er) #char_idをFIXNUMで返す
+ return "" unless er =~ Regexp.new(REGEXP_ALL) #なんか、間違ってる?
+ er = $1 #ついでに中身の部分を取り出す
+ return $1.hex if er =~ /^MCS-([0-9A-Fa-f]+)/ #MCS
+ return $1.hex if er =~ /^U[-+]([0-9A-Fa-f]+)/ #Unicode直打ち
+
+ er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
+ each_codesys {|codesys, er_prefix, keta, numtype| #p [codesys, er_prefix, keta, numtype]
+ numtyperegex = '\d' #if numtype == 'd'
+ numtyperegex = '[0-9A-Fa-f]' if numtype == 'X'
+ regexpstr = "^#{er_prefix}(#{numtyperegex}{#{keta},#{keta}})$" #p regexpstr
+ if er =~ Regexp.new(regexpstr)
+ codestr = $1
+ code = codestr.to_i #if numtype == 'd'
+ code = codestr.hex if numtype == 'X'
+ char_id_u8 = EntityReference.get_database(codesys, code)
+ char_id_num = Character.parse_char_id(char_id_u8)
+ return char_id_num
+ end
+ }
+ return ""
+ end
+ def self.each_codesys()
+ CODESYS_ORDER.each {|lang|
+ CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype| #普通こういう書き方はしない。ループ一個にする。
+ next unless codesys =~ lang
+ yield(codesys, er_prefix, keta, numtype)
+ }
+ }
+ end
+ def self.get_database(codesys, code)
+ c = CodesysDB.instance.get(codesys, code)
+ return c if c != nil
+ if codesys =~ /-jisx0208/
+ #return self.get_database("=jis-x0208", code) #再帰でどうだ?
+ c = CodesysDB.instance.get("=jis-x0208", code)
+ return c
+ end
+ return nil
+ end
+ end
+
+ class CharacterFactory #============================================文字オブジェクトの生成、cache
+ include Singleton
+ MAX = 10000
+ def initialize
+ @max = MAX
+ reset()
+ end
+ def get(char_id)
+ check_max()
+ n = Character.parse_char_id(char_id)
+ c = @chars[n]
+ @chars[n] = Character.new(n) if @chars[n] == nil
+ return @chars[n]
+ end
+ def reset()
+ @chars = nil
+ @chars = Hash.new
+ GC.start #ガーベージコレクション
+ end
+ def length() @chars.length; end
+ def check_max()
+ reset if @max < @chars.length #MAXを超えたらresetしてしまう。乱暴じゃがcacheなのでこれでいいのだ。
+ end
+ end
+
+ class Character #=============================================================== 文字オブジェクト
+ def initialize(char_id=nil)
+ @char_id = Character.parse_char_id(char_id)
+ @attributes = Hash.new
+ @check_all_database = false
+ end
+ attr_reader :char_id
+ def mcs_utf8() Character.u4itou8(@char_id) end
+
+ #----------------------------------------------------------------------
+ def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
+
+ #----------------------------------------------------------------------
+ def get_char_attribute(a) # XEmacs UTF-2000互換API群
+ a.gsub!(/_/, '-') #underlineは-に置換
+ atr = @attributes[a]
+ return atr if atr != nil
+ atr = check_database(a)
+ @attributes[a] = atr if atr != nil
+ return get_char_attribute("=jis-x0208") if a =~ /jisx0208/
+ return @attributes[a]
+ end
+ def put_char_attribute(a,v)
+ a.gsub!(/_/, '-') #underlineは-に置換
+ @attributes[a] = v;
+ CharDB.instance.put(a, mcs_utf8(), v)
+ end
+ def char_attribute_alist() check_all_database(); @attributes; end
+ def char_attribute_list() check_all_database(); @attributes.keys; end
+ alias [] get_char_attribute #その略称
+ alias []= put_char_attribute
+ alias alist char_attribute_alist
+ alias list char_attribute_list
+
+ def method_missing(mid, *args) #参考:ostruct.rb
+ mname = mid.id2name
+ return get_char_attribute(mname) if args.length == 0
+ put_char_attribute(mname.chop, args[0]) if mname =~ /=$/ #代入
+ end
+
+ def has_attribute?() #意味のあるattributeを持ってますか?
+ keys = list
+ keys.delete_if {|k|
+ k =~ /ids/
+ }
+ return (keys.length != 0)
+ end
+
+ #----------------------------------------------------------------------
+ def ==(ch)
+ return false if ch == nil
+ return false unless ch.is_a? Character
+ self.char_id == ch.char_id
+ end
+
+ #----------------------------------------------------------------------
+ def self.parse_char_id(char_id) #FIXNUMを返す
+ return nil if char_id == nil
+ if char_id.is_a?(Numeric) #p [char_id]
+ char_id = 0x80000000 + char_id if char_id < 0 #補数表現
+ return char_id.to_i
+ elsif char_id.is_a?(String)
+ return char_id.to_i if char_id =~ /^\d+$/ #文字列による数字だったら数値化してreturn
+ return EntityReference.parse(char_id) if char_id =~ Regexp.new(EntityReference::REGEXP_ALL) #実体参照?
+ char_id.sub!(/^\?/, '') if char_id =~ /^\?/ #もし先頭に?がついていたら削除
+ #このへん本当はもっとちゃんとチェックするべし
+ u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
+ return Character.u4tou4i(u4) #UCS-4数値にしてreturn
+ else
+ raise ArgumentError, "unknown object for char_id", caller(1)
+ end
+ end
+ def self.u4tou4i(u4)
+ return 0 if u4 == nil || u4 == ""
+ return (u4[3] << 24 | u4[2] << 16 | u4[1] << 8 | u4[0]) #UCS-4数値にしてreturn
+ end
+ def self.u4itou4(num)
+ return "" if num == nil
+ return sprintf("%c%c%c%c", num&0xff, (num >> 8)&0xff, (num >> 16)&0xff, (num >> 24)&0xff) #UCS-4数値を文字列にしてreturn
+ end
+ def self.u4itou8(char_id) #ucsの数値を受けとり、UTF-8の文字一文字を返す
+ begin
+ u4 = Character.u4itou4(char_id)
+ u8 = Uconv.u4tou8(u4)
+ return u8
+ rescue
+ #raise ArgumentError, "invalid char_id (#{char_id})", caller(1)
+ #print "error\n"
+ return ""
+ end
+ end
+
+ #----------------------------------------------------------------------
+ def check_database(a)
+ db = CharDB.instance
+ u8 = mcs_utf8()
+ v = db.get(a, u8) #u8で表される文字のaアトリビュートを調べる。
+ return v
+ end
+ def check_all_database() #現在の@char_idから、文字データベースを参照する
+ return if @check_all_database
+ return if @char_id == nil
+ db = CharDB.instance
+ u8 = mcs_utf8()
+ atrs = db.get_all(u8) #u8で表される文字のアトリビュートを全部持ってこい
+ atrs.each {|a,v|
+ @attributes[a] = v #とかいう感じで代入するのでええかな?
+ }
+ @check_all_database = true #重い処理なので一応checkする
+ end
+
+ #----------------------------------------------------------------------
+ def ucs() #p 'ucs'
+ #ar = %w{ucs ucs-big5 ucs-cdp ucs-cns ucs-jis ucs-ks =>ucs =>ucs* =>ucs-jis}
+ ar = %w{ucs ucs-jis ucs-big5 ucs-cdp ucs-cns ucs-ks =>ucs =>ucs* =>ucs-jis}
+ #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
+ ar.each {|a| #p [a]
+ u = get_char_attribute(a)
+ return u if u != nil
+ }
+ return nil
+ end
+
+ #----------------------------------------------------------------------CCS関係
+ def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
+ #alias to_s to_utf8
+ alias to_s mcs_utf8
+
+ #----------------------------------------------------------------------
+ def to_er(codesys=nil) #実体参照を返す、希望するcodesysが引数(未実装)
+ return "" if @char_id == nil
+ return sprintf("&U+%04X;", @char_id) if @char_id <= 0xffff
+ return sprintf("&U-%05X;", @char_id) if @char_id <= 0xfffff
+ EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
+ code = self[codesys]
+ next if code == nil
+ return sprintf("&#{er_prefix}%0#{keta}#{numtype};", code)
+ }
+ return sprintf("&MCS-%08X;", @char_id) #本当はこれは無しにしたい
+ end
+ def to_er_list()
+ ar = []
+ EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
+ er = to_er(codesys)
+ ar << er if er != nil
+ }
+ ar
+ end
+
+ def inspect_x()
+ return "<>" if @char_id == nil
+ ar = [to_utf8(), to_er().sub(/^&/,'').chop]
+ "<"+ar.join(',')+">"
+ end
+ alias inspect inspect_x
+ def inspect_all_codesys()
+ #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
+ end
+ def inspect_all()
+ ar = [inspect.chop]
+ alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
+ return ar.join(',')+">"
+ end
+ def get_attributes()
+ str = ""
+ alist.to_a.sort.each {|a, v|
+ str += "#{a}: #{v}\n"
+ }
+ str
+ end
+
+ #----------------------------------------------------------------------IDS関係
+ def decompose
+ k = self.to_s
+# idss = self['ids']
+# return idss if idss
+ idss = self['ids-aggregated']
+ return idss if idss != nil && 0 < idss.length && k != idss
+ idss = self['ids']
+ return idss if idss != nil && 0 < idss.length && k != idss
+ return k
+# return k if idss.nil? || idss.length == 0 || k == idss
+# if idss.char_length == 2
+# p ['What???', k, idss, k.inspect_all]
+# #return idssx[1] #二個目だけ返すとか?
+# return k #IDSに展開する方法が無いと。
+# end
+# return k if k == idss
+# if idss.include?(k) #<C5-4C4D><C6-4A37>この二文字のBUG対策
+# #return idss.sub(k, '')
+# return k #IDSに展開する方法が無いと。
+# end
+# return idss
+ end
+ def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
+ def ids_operator_argc()
+ return 0 unless is_ids?
+ return 3 if @char_id == 0x2ff2 || @char_id == 0x2ff3
+ return 2
+ end
+ end
+
+ class DBS #======================================================================複数のDBを集めたclass
+ end
+
+ class ADB < BDB::Hash #======================================================================一つのDB
+ def initialize(*args)
+ super
+ @modified = false
+ at_exit {
+ if @modified
+ self.close #これがないと、うまくデータベースがセーブされないのです。
+ end
+ }
+ end
+ def self.open_create(filename)
+ ADB.open(filename, nil, BDB::CREATE | BDB::EXCL) #上書きはしない
+ end
+ def mykey(key)
+ if key.is_a?(String)
+ if key.char_length == 1
+ return '?'+key #Stringだったら引く前に?を足す
+ end
+ end
+ #key = key.to_s if key.is_a?(Numeric) #NumberだったらStringにする。
+ #ここで && key ! =~ /^\?/ をいれると、?自身を検索できなくなってしまう。
+ return key
+ end
+ def myvalue(v)
+ return v if v == nil
+ return v.to_i if v =~ /^\d+$/ #数字だったらここで変換しておく
+ return v.sub(/^\?/, '') if v =~ /^\?/ #冒頭の?は取り除く
+ return $1 if v =~ /^"(.+)"$/ #最初と最後に"がついていたら、取り除く
+ #p ['get', v, t, key, db]
+ #return parse_sexp(v) if v =~ /^\(.+\)$/ #最初と最後が()の時は、S式にparseする
+ return v #それ以外って何?
+ end
+ def myget(key) #keyキーを引いて返す
+ key = mykey(key)
+ v = get(key) #存在しなかったらnilを返すことになる
+ return myvalue(v)
+ end
+ def myput(key, v) #keyにvをいれる
+ key = mykey(key)
+ put(key, v) #putする
+ @modified = true
+ end
+ end
+
+ class DB #======================================================= データベース群のabstract class
+ def self.unix_to_win(unix) #Windowsファイル名制限のため、変換する
+ win = unix.gsub(/</, '(')
+ win.gsub!(/>/, ')')
+ win.gsub!(/\*/, '+')
+ win.gsub!(/\?/, '!')
+ return win
+ end
+ def self.win_to_unix(win)
+ unix = win.gsub(%r|\)|, '>')
+ unix.gsub!(%r|\(|, '<')
+ unix.gsub!(%r|!|, '?')
+ unix.gsub!(%r|\+|, '*')
+ return unix
+ end
+# def windows?() DB.windows?() end
+ def get_filename(t)
+ return @pre + DB.unix_to_win(t) + @post if windows?
+ return @pre + t + @post
+ end
+ def get_dirname(t) File.dirname(get_filename(t)) end
+ def open_dbs()
+ @dbs = Hash.new
+ keys = find_keys()
+ keys.each {|key| open_db(key) }
+ end
+ def find_keys()
+ files = []
+ Dir.glob(@glob){|f|
+ next if ! File.file?(f)
+ next if f =~ /.txt$/
+ files << f
+ }
+ keys = []
+ files.each {|f|
+ t = DB.win_to_unix(f)
+ t.sub!(%r|^#{@pre}|, '')
+ t.sub!(%r|#{@post}$|, '') if @post != ""
+ keys << t
+ }
+ return keys
+ #return keys.sort
+ end
+ def close_db(t)
+ db = get(t)
+ return nil if db.nil?
+ db.close
+ @dbs.delete(t)
+ end
+ def open_db(t)
+ return nil if get(t) #すでにopenしていたら再openはしない。
+ begin
+ bdb = ADB.open(get_filename(t), nil, 0)
+ @dbs[t] = bdb if bdb != nil
+ rescue
+ p ["open error", get_filename(t)]; return nil
+ end
+ return true
+ end
+ def make_db(t, h=nil) #tという名前でhという中身のデータベースを作る
+ return nil if get(t) #すでにある場合はreturn
+ Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
+ db = nil
+ begin
+ db = ADB.open_create(get_filename(t)) #上書きはしない
+ if h != nil
+ h.each {|k, v|
+ k = '?'+k if k.is_a?(String)
+ db[k] = v
+ }
+ end
+ db.close
+ rescue
+ p ["make error", get_filename(t)]; return nil
+ end
+ return true
+ end
+ def make_db_no_question_mark(t, h=nil) #tという名前でhという中身のデータベースを作る
+ return nil if get(t) #すでにある場合はreturn
+ Dir.mkdir(get_dirname(t)) unless FileTest.exist?(get_dirname(t))
+ db = nil
+ begin
+ db = ADB.open_create(get_filename(t)) #上書きはしない
+ if h != nil
+ h.each {|k, v|
+# k = '?'+k if k.is_a?(String)
+ db[k] = v
+ }
+ end
+ db.close
+ rescue
+ p ["make error", get_filename(t)]; return nil
+ end
+ return true
+ end
+ def remove_db(t) #tという名前のデータベースを消去する
+ db = get(t)
+ if db
+ db.close
+ @dbs.delete(t)
+ end
+ begin
+ File.unlink(get_filename(t)) if FileTest.file?(get_filename(t))
+ rescue
+ p ["unlink error", get_filename(t)]; return nil
+ end
+ dn = get_dirname(t)
+ Dir.rmdir(dn) if FileTest.directory?(dn) && Dir.entries(dn).length <= 2 #空directoryだったら消す
+ return true
+ end
+ def to_num(s)
+ return s.to_i if s =~ /^\d+$/
+ return s
+ end
+ def dump_db(t)
+ db = get(t)
+ return nil unless db
+ file = get_filename(t)
+ open("#{file}.txt", "w"){|out|
+# out.binmode.sync = true
+ ar = db.to_a
+ ar.map! {|k, v| [to_num(k), to_num(v)] }
+ ar.sort.each {|k, v|
+ out.printf("%s\t%s\n", k, v)
+ }
+ }
+ return true
+ end
+ def each_db() @dbs.to_a.sort.each {|t, db| yield(t, db) } end
+ def dump_all() each_db {|t, db| dump_db(t) } end
+ def close_all() each_db {|t, db| db.close } end
+ def keys() @dbs.keys end
+ def each(t)
+ return unless block_given?
+ db = @dbs[t]
+ return nil unless db
+ db.each {|k, v|
+ k = to_num(k)
+ v = to_num(v)
+ k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
+ vv = get(t, k) #p ['each', t, k, v, vv]
+ yield(k, vv)
+ }
+ end
+ def each_sort(t)
+ return unless block_given?
+ db = @dbs[t]
+ return nil unless db
+ ar = db.to_a
+ ar.map! {|k, v| [to_num(k), to_num(v)] }
+ ar.sort.each {|k, v|
+ k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
+ vv = get(t, k) #p ['each', t, k, v, vv]
+ yield(k, vv)
+ }
+ end
+ #----------------------------------------------------------------------
+ def get(t, key=nil) #tというデータベースのkeyキーを引いて返す
+ db = @dbs[t]
+ return db if key.nil?
+ return nil unless db
+ return db.myget(key)
+ end
+ def put(t, key, v) #tというデータベースのkeyにvをいれる
+ db = @dbs[t]
+ if db == nil
+ db = make_db(t)
+ db = open_db(t)
+ db = @dbs[t]
+ end
+ db.myput(key, v) #putする
+ end
+ end
+
+ class CharDB < DB #------------------------------------ MCS-UTF8をキーとした属性へのデータベース
+ include Singleton
+ def initialize()
+ super
+ @glob, @pre, @post = "#{DB_DIR}/system-char-id/*", "#{DB_DIR}/system-char-id/", ""
+ open_dbs()
+ end
+ def get_all(u8) #全データベースのu8キーを引いてHashにまとめて返す
+ atrs = Hash.new
+ @dbs.each {|t, db|
+ v = get(t, u8)
+ atrs[t] = v if v != nil
+ }
+ return atrs
+ end
+ end
+
+ class CodesysDB < DB #----------------------------------------------------------------------
+ include Singleton
+ def initialize()
+ super
+ @glob, @pre, @post = "#{DB_DIR}/*/system-char-id", "#{DB_DIR}/", "/system-char-id"
+ open_dbs()
+ end
+ #def keys() @dbs.keys.sort end #どんなCodesysの情報を持っているかの一覧
+ def keys() @dbs.keys end #どんなCodesysの情報を持っているかの一覧
+ def get_codesys(t)
+ db = get(t)
+ return nil unless db
+ return Codesys.new(t)
+ end
+ end
+
+ class Codesys < DB #======================================================================
+ def initialize(name)
+# super
+ @name = name
+ @dbs = CodesysDB.instance
+ end
+ def keys() #どんなコードポイントの情報を持っているかの一覧
+ ks = @dbs.get(@name).keys
+ if @name =~ /jisx0208/ #特別処理
+ n = @dbs.get('=jis-x0208').keys
+ # p ['keys', @name, ks, n]
+ ks += n
+ end
+ ks.map! {|k| to_num(k) }
+ ks
+ end
+ def get(key)
+ v = @dbs.get(@name, key)
+ return v if v
+ if @name =~ /jisx0208/ #jisx0208が含まれている場合だけ特別処理する
+ return @dbs.get('=jis-x0208', key)
+ end
+ return nil
+ end
+ def each()
+ return unless block_given?
+ db = @dbs.get(@name)
+ return nil unless db
+ db.each {|k, v|
+ k = to_num(k)
+ v = to_num(v)
+ k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
+ vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
+ yield(k, vv)
+ }
+ end
+ def each_sort()
+ return unless block_given?
+ db = @dbs.get(@name)
+ return nil unless db
+ ar = db.to_a
+ ar.map! {|k, v| [to_num(k), to_num(v)] }
+ ar.sort.each {|k, v|
+ k.sub!(/^\?/, '') if k =~ /^\?/ #冒頭の?は取り除く
+ vv = @dbs.get(@name, k) #p ['each', t, k, v, vv]
+ yield(k, vv)
+ }
+ end
+ end
+
+ class IDS_TEXT_DB < DB #======================================================================
+ include Singleton
+ if CHISE.windows?()
+ IDS_DB_DIR = 'd:/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
+ else
+ IDS_DB_DIR = '/home/eto/work/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
+ end
+ IDS_LIST = "
+IDS-JIS-X0208-1990.txt
+IDS-CBETA.txt
+IDS-Daikanwa-01.txt
+IDS-Daikanwa-02.txt
+IDS-Daikanwa-03.txt
+IDS-Daikanwa-04.txt
+IDS-Daikanwa-05.txt
+IDS-Daikanwa-06.txt
+IDS-Daikanwa-07.txt
+IDS-Daikanwa-08.txt
+IDS-Daikanwa-09.txt
+IDS-Daikanwa-10.txt
+IDS-Daikanwa-11.txt
+IDS-Daikanwa-12.txt
+IDS-Daikanwa-dx.txt
+IDS-Daikanwa-ho.txt
+IDS-UCS-Basic.txt
+#IDS-UCS-Compat-Supplement.txt
+#IDS-UCS-Compat.txt
+IDS-UCS-Ext-A.txt
+IDS-UCS-Ext-B-1.txt
+IDS-UCS-Ext-B-2.txt
+IDS-UCS-Ext-B-3.txt
+IDS-UCS-Ext-B-4.txt
+IDS-UCS-Ext-B-5.txt
+IDS-UCS-Ext-B-6.txt
+".split
+ def initialize()
+ super
+ @ids_list = IDS_LIST
+ @chars = []
+ @glob, @pre, @post = "#{IDS_DB_DIR}/db/*", "#{IDS_DB_DIR}/db/", ""
+ dir = File.dirname(@pre)
+ Dir.mkdir(dir) unless FileTest.exist?(dir)
+ open_dbs()
+ end
+ def each_file()
+ return unless block_given?
+ @ids_list.each {|file|
+ next if file =~ /^#/
+ yield(IDS_DB_DIR+file)
+ }
+ end
+ def each_line(file)
+ open(file){|f|
+ while line = f.gets
+ next if line =~ /^;/ #コメントはとばす
+ line.chomp!
+ code, char, ids = line.split
+ yield(code, char, ids)
+ end
+ }
+ end
+ def dump_text_all
+ each_file {|file|
+ dir = File.dirname(file) + '/../ids-new/'
+ Dir.mkdir(dir) if ! FileTest.directory?(dir)
+ newfile = dir + File.basename(file)
+ p [file, newfile]
+ open(newfile, "w"){|out|
+ out.binmode.sync = true
+ each_line(file){|code, ch, ids|
+ char = Character.get(ch)
+ ids = char.decompose
+ out.print "#{code} #{ch} #{ids}\n"
+ }
+ }
+ }
+ end
+ def make_ids_error
+ each_file {|file|
+ dir = File.dirname(file) + '/../ids-error'
+ Dir.mkdir(dir) unless FileTest.exist?(dir)
+ errfile = dir + '/' + File.basename(file)
+# p [file, errfile]
+ open(errfile, "w"){|out|
+ out.binmode.sync = true
+ each_line(file){|code, ch, ids|
+ char = Character.get(ch)
+ ids_error = char['ids-error']
+ next if ids_error.nil?
+ out.print "#{code} #{ch} #{ids} #{ids_error}\n"
+ }
+ }
+ }
+ end
+ end
+
+ class IDS_DB < DB #======================================================================BDB化したIDS DBを扱う
+ include Singleton
+ def initialize
+ @dbs = CharDB.instance
+ end
+ def make_ids_db
+ db = IDS_TEXT_DB.instance
+ db.each_file {|file|
+ db.each_line(file){|code, ch, ids|
+ char = Character.get(ch) #実体参照である
+ ids = "" if ids == nil
+ ids.de_er! #実体参照を解除する
+ char['ids-text'] = ids
+ }
+ p [file, CharacterFactory.instance.length]
+ CharacterFactory.instance.reset()
+ }
+ @dbs.dump_db('ids-text') #テキスト化する
+ end
+ def make_ids_error_check
+ @dbs.each('ids-text') {|k, ids|
+ next if k.nil? || k == "" || ids.nil? || ids == "" #無視します
+ next if k == ids #問題無しなので
+ char = k.char
+ idstree = IDS_Tree.new(ids)
+ c = idstree.check_integrity
+ c = "contains self" if ids.include?(k)
+ c = "no attribute" if !char.has_attribute? #isolated characterはまぎれこませない。
+ if c
+ char['ids-error'] = c
+ else
+ char['ids'] = ids
+ end
+# print c,"\t", k.char.to_er,"\t", k,"\t", v,"\n"
+ }
+ @dbs.dump_db('ids-error') #テキスト化する
+ @dbs.dump_db('ids') #テキスト化する
+ end
+ def make_ids_reverse
+ h = Hash.new
+ @dbs.each('ids') {|k, v|
+ char = k.char
+ ids = char.decompose
+ h[ids] = "" if h[ids].nil?
+ h[ids] += k #追加する
+ }
+ h.each {|k, v|
+ h[k] = char_sort(v) #文字の順番を、よく使うっぽいものからの順番にする
+ }
+ h.delete_if {|k, v| #h[k]が""になる可能性もあるが、それはkeyとして入れないことにする。
+ v == ""
+ }
+ p ['length', h.length]
+ cdb = CodesysDB.instance
+ cdb.make_db_no_question_mark('ids', h)
+ cdb.dump_db('ids')
+ end
+ def char_sort(composed)
+ return composed if composed.char_length == 1
+ ar = composed.to_a
+ arorg = ar.dup
+ ar2 = []
+ ar.dup.each {|ch|
+ char = ch.char
+ if char.char_id < 0xfffff #Unicodeっぽい?
+ ar2 << ch
+ ar.delete(ch)
+ end
+ }
+ if 0 < ar.length
+ EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
+ ar.each {|ch|
+ char = ch.char
+ v = char[codesys]
+# p [codesys, v] if v
+ if v #EntityReferenceの順番に準拠する。
+ ar2 << ch
+ ar.delete(ch)
+ end
+ }
+ }
+ end
+ if 0 < ar.length
+# p ['yokuwakaran character', ar, ar[0].inspect_all, arorg]
+ EntityReference.each_codesys{|codesys, er_prefix, keta, numtype|
+ ar.dup.each {|ch|
+ char = ch.char
+ v = char[codesys]
+# p [codesys, v] if v
+ }
+ }
+ end
+ return ar2.join("")
+ end
+ def dump_ids_duplicated
+ open('ids-duplicated.txt', 'w'){|out|
+ #out.binmode
+ CodesysDB.instance.each('ids') {|k, v|
+ if v.nil?
+ out.print "nil #{k} #{v}\n"
+ next
+ end
+ n = v.char_length
+ next if n == 1
+ out.print "#{n} #{k} #{v}"
+ v.each_char {|ch|
+ char = ch.char
+ out.print " #{char.inspect}"
+ }
+ out.print "\n"
+ }
+ }
+ end
+ def make_ids_aggregated
+ @dbs.each('ids') {|k, v|
+ char = k.char
+ ids = char.decompose
+ ag = ids.aggregate
+ char['ids-aggregated'] = ag
+ }
+ @dbs.dump_db('ids-aggregated')
+ end
+ def dump_ids_aggregated
+ open('ids-aggregated.txt', 'w'){|out|
+ #out.binmode
+ @dbs.each('ids') {|k, v|
+ char = k.char
+ ids = char['ids']
+ ag = char['ids-aggregated']
+ out.print "#{char.to_s} #{ag} #{ids}\n" if ids != ag
+ }
+ }
+ end
+ def make_ids_parts
+ @dbs.each('ids') {|k, v|
+ char = k.char
+ pids = char.to_s
+ ar = []
+ counter = 0
+ loop {
+ ids = pids.decompose
+ break if ids == pids #これ以上分割できないようだったら終了〜。
+ ar += ids.to_a
+ counter += 1
+ p [char.to_s, pids, ids, ar] if 10 < counter #これは何かおかしいぞと
+ pids = ids
+ }
+ ar.sort!
+ ar.uniq!
+#やっぱりIDS文字も加えることにする. by eto 2003-02-05
+# ar.delete_if {|ch|
+# ch.char.is_ids? #IDS文字はまぎれこませない。
+# }
+ str = ar.join('')
+ char['ids-parts'] = str
+ }
+ @dbs.dump_db('ids-parts')
+ end
+ def make_ids_contained
+ h = Hash.new
+ @dbs.each('ids-parts') {|k, v|
+ char = k.char
+ parts = char.ids_parts
+ parts.each_char {|ch|
+# part = ch.char
+ h[ch] = [] if h[ch].nil?
+ h[ch] << k
+# h[ch] += k
+# part['ids-contained'] = "" if part['ids-contained'].nil?
+# part['ids-contained'] += k
+ }
+ }
+ h.each {|k, v|
+ char = k.char
+ v.sort!
+ char['ids-contained'] = v.join('')
+
+ }
+ @dbs.dump_db('ids-contained')
+ end
+ def make_ids_decomposed
+ @dbs.each('ids') {|k, v|
+ char = k.char
+ de= char.decompose_all
+ char['ids-decomposed'] = de
+ }
+ @dbs.dump_db('ids-decomposed')
+ end
+ end
+
+ class Node < Array #=======================================================木構造の中の一つの枝
+ def initialize(nodeleaf=nil, nodenum=nil)
+ super()
+ @nodeleaf = nodeleaf
+ @nodenum = nodenum
+ if @nodeleaf
+ original_add(@nodeleaf)
+ end
+ end
+ attr_reader :nodenum
+ alias original_add <<
+ private :original_add
+ def <<(obj)
+ original_add(obj)
+ @nodenum -= 1 if @nodenum
+ end
+ def nodes
+ ar = []
+ ar << self.to_s
+ self.each {|n|
+ ar += n.nodes if n.is_a? Node
+ }
+ return ar
+ end
+ end
+
+ class Tree #======================================================================木構造を扱う
+ def initialize()
+ @root = Node.new()
+ @stack = [@root]
+ @leafnum = 0
+ @depth = 1 #stackの深さが最大になったところの値、木構造が無いときは1となる
+ end
+ def depth() @depth - 1 end
+ def add_node(nodeleaf=nil, nodenum=nil) #枝を追加
+ new_node = Node.new(nodeleaf, nodenum)
+ @stack.last << new_node
+ @stack << new_node
+ if @depth < @stack.length
+ @depth = @stack.length
+ end
+ self
+ end
+ def end_node() #この枝は終り
+ @stack.pop
+ self
+ end
+ def add_leaf(a) #葉を追加
+ @stack.last << a
+ end_check()
+ self
+ end
+ def end_check()
+ n = @stack.last.nodenum
+ if n && n == 0
+ end_node()
+ end_check() #再帰
+ end
+ end
+ def check_integrity
+ n = @stack.last.nodenum
+ return nil if @root.length == 0 #no tree is good tree
+ return "unmatch leaves" if n && n != 0
+ return "extra nodes" if @root.first.is_a?(Node) && @root.length != 1
+ return "extra leaves" if @root.length != 1
+ return nil
+ end
+ def nodes
+ r = @root.nodes
+ r.shift
+ r
+ end
+ def sub_nodes
+ r = nodes
+ r.shift
+ r
+ end
+ def to_s() @root.to_s end
+ def inspect() @root.inspect end
+ end
+
+ class IDS_Tree < Tree #======================================================================
+ def initialize(str)
+ @str = str
+ super()
+ parse()
+ end
+ def parse()
+ @str.each_char {|ch|
+ char = Character.new(ch)
+ if is_ids?(char)
+ add_node(char, ids_operator_argc(char))
+ else
+ add_leaf(char)
+ end
+ }
+ end
+ def is_ids?(obj)
+ return true if "+*".include?(obj.to_s) #テスト用ですかね
+ return true if obj.is_ids?
+ return false
+ end
+ def ids_operator_argc(obj)
+ return obj.ids_operator_argc if 0 < obj.ids_operator_argc
+ return 2 #テスト用ってことで
+ end
+ def check_integrity
+ r = super
+ return r if r #不完全がすでにわかっているならreturn
+ return "contains ques" if @str =~ /\?/ #?が含まれている?
+ return nil
+ end
+
+ end
+
+ class IDS #======================================================================IDSそのものを扱うclass
+ def initialize(str) #IDS文字列をうけとる。
+ @str = str
+ end
+ def parse
+ end
+ def parse_x #柔軟型のParse. IDSキャラクターが前にきてなくてもよい。などなど。
+ end
+ end
+
+ class Counter #======================================================================
+ #使い方
+ #counter = Counter.new(50) { exit }
+ #counter.count
+ def initialize(max)
+ @max = max
+ @count = 0
+ @proc = proc
+ end
+ def count
+ @count += 1
+ if @max <= @count
+ @proc.call
+ end
+ end
+ end
+
+end
+
+#----------------------------------------------------------------------終了