add libchise extension.
[chise/ruby.git] / src / chise.rb
index feed0b6..8d302af 100755 (executable)
@@ -10,22 +10,36 @@ $debug = true #これはテスト用
 #$stdout.binmode if $debug
 $stdout.sync = true if $debug
 
-class String
+class String #======================================================================
   def to_a() self.split(//) end #$KCODEが設定されているので、UTF-8的な一文字づつがchにはいる
   def each_char() to_a.each {|ch| yield ch } end
+  def each_character() to_a.each {|ch| yield ch.char } 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
+  #alias to_c char #悩み中
   def char_id() char.char_id() end
   def get_char_attribute(a) char.get_char_attribute(a) end
-  def ucs() char.ucs() end
+  #def ucs() char.ucs() end
   def to_utf8()
     return to_a.map {|ch|
       ch.char.to_utf8
     }.join('')
   end
 
+  def method_missing(mid, *args)
+    if char_length == 1 #省略形が有効なのは、一文字の時だけ
+      char.method_missing(mid, *args)
+    else
+      raise NameError, "undefined method `#{mid.id2name}'", caller(1)
+    end
+  end
+
+  def map_utf8() map_char {|ch| ch.char.map_utf8 } end
+  alias map_ucs map_utf8
+  def map_ucs_er() map_char {|ch| ch.char.map_ucs_er } end
+  def to_er() map_char {|ch| ch.char.to_er } end
+
   #put関係、[]関係は用意しないことにした。
   def de_er!() #EntityReferenceを取り除く
     return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
@@ -47,9 +61,14 @@ class String
   def inspect_all() map_char {|ch| ch.char.inspect_all } end
   def inspect_x()   map_char {|ch| ch.char.inspect_x   } end
 
+  def to_euc()   map_char {|ch| ch.char.to_euc   } end
+  def map_euc()  map_char {|ch| ch.char.map_euc  } end
+  def to_sjis()  map_char {|ch| ch.char.to_sjis  } end
+  def map_sjis() map_char {|ch| ch.char.map_sjis } end
+
   def decompose() map_char {|ch| ch.char.decompose } end
   def decompose!() self.replace(self.decompose); self; end
-  def decompose_all(level=nil)
+  def decompose_all_nu(level=nil)
     level = 0 if level.nil?
     if 10 < level
       p ['too many recursive', self] 
@@ -59,6 +78,7 @@ class String
     return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
     de #もうこれ以上変化は無さそうだぞと。
   end
+  def decompose_all() map_char {|ch| ch.char.decompose_all } end
   def decompose_all!() self.replace(self.decompose_all); self; end
 
   def find() #"日雲"→"曇"とかいう感じの操作
@@ -114,7 +134,7 @@ class String
   end
 end
 
-module CHISE
+module CHISE #======================================================================
   def windows?()
     (RUBY_PLATFORM =~ /cygwin/ || RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/)
   end
@@ -149,7 +169,7 @@ module CHISE
       %w( korean-ksc5601       K0- 4 X),
     ]
     CODESYS_ORDER = %w(japanese chinese korean ideograph)
-    REGEXP_PART = "&([-+0-9A-Za-z]+);"
+    REGEXP_PART = "&([-+0-9A-Za-z#]+);"
     REGEXP_ALL = "^#{REGEXP_PART}$"
 
     def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
@@ -158,7 +178,10 @@ module CHISE
       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直打ち
+#      if er =~ /^U[-+]?([0-9A-Fa-f]+)/ #Unicode直打ち
+      if er =~ /^U[-+]?([0-9A-Fa-f]+)/ || er =~ /^#x([0-9A-Fa-f]+)/ #Unicode直打ち
+       return $1.hex 
+      end
 
       er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
       each_codesys {|codesys, er_prefix, keta, numtype|        #p [codesys, er_prefix, keta, numtype]
@@ -223,29 +246,45 @@ module CHISE
   end
 
   class Character #=============================================================== 文字オブジェクト
+    BASIC_KANJI = "人子女母父王口耳手足力目首毛心犬牛鳥貝角羽虫馬魚羊肉皮米竹木麦豆山川雨風水土石金田穴日月火音糸刀舟門戸衣矢弓車皿一二三四五六七八九十百千万寸尺上中下本玉立回食行止交向歩考入示走生出来書言大小白青多少高長"
+    def is_basic_kanji?
+      BASIC_KANJI.include?(self.to_s)
+    end
+
     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 to_i() @char_id end
     def mcs_utf8() Character.u4itou8(@char_id) end
+    def mcs_hex() sprintf("%x", @char_id) end
 
     #----------------------------------------------------------------------
     def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
 
     #----------------------------------------------------------------------
-    def get_char_attribute(a) # XEmacs UTF-2000互換API群
+    def normalize_attribute_name(b)
+      a = b.dup
       a.gsub!(/_/, '-') #underlineは-に置換
+      a.sub!(/^map-/,  '=>')
+      a.sub!(/^to-/,   '->')
+      a.sub!(/^from-/, '<-')
+      a
+    end
+    def get_char_attribute(b) # XEmacs UTF-2000互換API群
+      a = normalize_attribute_name(b)
+      #p [a, b]
       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 get_char_attribute("=jis-x0208") if a =~ /jisx0208/ #ここだけ特殊形
       return @attributes[a]
     end
-    def put_char_attribute(a,v)
-      a.gsub!(/_/, '-') #underlineは-に置換
+    def put_char_attribute(b,v)
+      a = normalize_attribute_name(b)
       @attributes[a] = v;
       CharDB.instance.put(a, mcs_utf8(), v)
     end
@@ -284,11 +323,17 @@ module CHISE
        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 char_id.to_i if char_id =~ /^\d+$/ && 1 < char_id.length #文字列による数字だったら数値化して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文字列に変換
+       begin
+         u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
+       rescue
+         p $!
+         p char_id
+         return 0
+       end
        return Character.u4tou4i(u4) #UCS-4数値にしてreturn
       else
        raise ArgumentError, "unknown object for char_id", caller(1)
@@ -299,7 +344,7 @@ module CHISE
       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 "" unless num.is_a?(Integer)
       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の文字一文字を返す
@@ -336,7 +381,8 @@ module CHISE
     #----------------------------------------------------------------------
     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}
+      #ar = %w{ucs ucs-jis ucs-big5 ucs-cdp ucs-cns ucs-ks =>ucs =>ucs* =>ucs-jis}
+      ar = %w{ucs-jis ucs =>ucs-jis}
       #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
       ar.each {|a|     #p [a]
         u = get_char_attribute(a)
@@ -349,12 +395,49 @@ module CHISE
      def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
      #alias to_s to_utf8
      alias to_s mcs_utf8
+     def map_utf8()
+       u = ucs()
+       if u.nil? || 0xffff < u
+        return to_er()
+       else
+        return to_utf8()
+       end
+     end
+     alias map_ucs map_utf8
+     def map_ucs_er()
+       u = ucs()
+       if u.nil? || 0xffff < u
+        return to_er()
+       else
+        return Character.get(u).to_er()
+       end
+     end
+     def to_euc()
+       u = ucs()
+       return "" if u.nil? || 0xffff < u
+       Uconv.u16toeuc(Uconv.u4tou16(Character.u4itou4(ucs())))
+     end
+     def map_euc()
+       e = to_euc()
+       return e if e != ""
+       return to_er()
+     end
+     def to_sjis()
+       u = ucs()
+       return "" if u.nil? || 0xffff < u
+       Uconv.u16tosjis(Uconv.u4tou16(Character.u4itou4(ucs())))
+     end
+     def map_sjis()
+       e = to_sjis()
+       return e if e != ""
+       return to_er()
+     end
 
      #----------------------------------------------------------------------
      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
+       return sprintf("&#x%04x;", @char_id) if @char_id <= 0xffff
+       return sprintf("&#x%05x;", @char_id) if @char_id <= 0xfffff
        EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
         code = self[codesys]
         next if code == nil
@@ -377,7 +460,7 @@ module CHISE
        "<"+ar.join(',')+">"
      end
      alias inspect inspect_x
-     def inspect_all_codesys()
+     def inspect_all_codesys() #未完成
        #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
      end
      def inspect_all()
@@ -393,11 +476,40 @@ module CHISE
        str
      end
 
+     def inspect_ids(hex_flag=false)
+       ids = decompose
+       ar = []
+       ar << (hex_flag ? "x"+mcs_hex : to_utf8)
+       if to_s != ids #idsが部品そのものだったら部品追加はしない
+        ids.each_char {|ch|
+          char = ch.char
+          next if char.is_ids?
+          if hex_flag then
+            ar << "x"+char.mcs_hex
+          else
+            u = char.to_utf8
+            if u != ""
+              ar << u
+            else
+              ar << char.to_er
+            end
+          end
+         }
+       end
+       return "("+ar.join("\t")+")"
+     end
+
      #----------------------------------------------------------------------IDS関係
      def decompose
        k = self.to_s
 #       idss = self['ids']
 #       return idss if idss
+#       return k if self.is_basic_kanji? #基本漢字はstop kanjiとするぞと。
+       return self['ids-represent'] if self['ids-represent'] #ids_representを持っている場合はその値とする。
+       return self['ids-element'] if self['ids-element'] #ids_elementを持っている場合はその値とする。
+
+       idss = self['ids-meaning']
+       return idss if idss != nil && 0 < idss.length && k != idss
        idss = self['ids-aggregated']
        return idss if idss != nil && 0 < idss.length && k != idss
        idss = self['ids']
@@ -416,6 +528,29 @@ module CHISE
 #       end
 #       return idss
      end
+     def decompose_all
+       pde = ""
+       de = self.decompose #出発点
+       level = 0
+       while true
+        pde = de
+        de = pde.decompose #もう一度分解をしてみる。
+        break if pde == de #ループを抜けだす
+        exit if 10 < level #p ['too many recursive', self] 
+        level += 1
+       end
+       return de
+     end
+     def decompose_all_nu(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 #なにか変化があったから再帰
+       return de #もうこれ以上変化は無さそうだぞと。
+     end
      def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
      def ids_operator_argc()
        return 0 unless is_ids?
@@ -424,7 +559,7 @@ module CHISE
      end
    end
 
-   class DBS #======================================================================複数のDBを集めたclass
+   class DBS #======================================================================複数のDBを集めたclass、未完成
    end
 
    class ADB < BDB::Hash #======================================================================一つのDB
@@ -486,7 +621,6 @@ module CHISE
        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
@@ -512,7 +646,6 @@ module CHISE
         keys << t
        }
        return keys
-       #return keys.sort
      end
      def close_db(t)
        db = get(t)
@@ -854,9 +987,10 @@ IDS-CBETA.txt
          if char != charimg #code表記と文字が一致していない?
            unless code =~ /^M-/ || code =~ /^MH-/ || code =~ /^CB/ #食い違っていて当然であるので何もしない
              print "unknown char       #{char.inspect} #{code} #{ch}   #{ids}\n"
-             next #それ以外の場合はエラメッセージをだして、次へ。
+             next #それ以外の場合はエラーメッセージをだして、次へ。
            end
          end
+         #next if !char.has_attribute? #isolated characterはまぎれこませない。
 
          ids.de_er! #実体参照を解除する
          next if ids == char.to_s #もし文字とまったく一緒なら、意味が無いので情報を持たない
@@ -865,7 +999,6 @@ IDS-CBETA.txt
          idstree = IDS_Tree.new(ids)
          c = idstree.check_integrity
          c = "contains self" if ids.include?(char.to_s)
-#        c = "no attribute"  if !char.has_attribute? #isolated characterはまぎれこませない。
          if c #ちょっとでもエラーがある場合は、
            char['ids-error'] = c #エラーを記録して、データとしては保持しない
            next
@@ -906,10 +1039,8 @@ IDS-CBETA.txt
       print "length    #{h.length}\n"
       cdb = CodesysDB.instance
       cdb.make_db_no_question_mark('ids', h)
-      p ['make db no q mark done.']
       cdb.open_db('ids') #これが無いと、dump_dbされません。
       cdb.dump_db('ids')
-      p ['dump_db done.']
     end
     def char_sort(composed)
       return composed if composed.char_length == 1
@@ -1186,6 +1317,102 @@ IDS-CBETA.txt
     end
   end
 
+  class DBS_Management #======================================================================ファイル管理
+    OBSOLETE_ATTRIBUTES = "
+cns-radical
+cns-radical?
+kangxi-radical
+daikanwa-radical
+unicode-radical
+
+cns-strokes
+kangxi-strokes
+daikanwa-strokes
+shinjigen-1-radical
+gb-original-radical
+japanese-strokes
+jis-strokes-a
+jis-strokes-b
+jisx0208-strokes
+jis-x0213-strokes
+jisx0213-strokes
+unicode-strokes
+
+totalstrokes
+cns-total-strokes
+jis-total-strokes-b
+
+non-morohashi
+
+=>ucs*
+#=>mojikyo
+#=mojikyo
+->identical
+
+ancient-ideograph-of
+ancient-char-of-shinjigen-1
+original-ideograph-of
+original-char-of-shinjigen-1
+simplified-ideograph-of
+vulgar-ideograph-of
+vulgar-char-of-shinjigen-1
+ideograph=
+ideographic-variants
+variant-of-shinjigen-1
+
+iso-10646-comment
+".split
+    def initialize
+      @odir = DB_DIR+"/system-char-id/obsolete" #直打ちしている。
+    end
+    def move_obsolete_files # 廃止予定のbdbファイルをobsoleteディレクトリーにつっこむ
+      db = CharDB.instance
+      db.close_all
+      Dir.mkdir(@odir) unless FileTest.directory? @odir
+      OBSOLETE_ATTRIBUTES.each {|attr|
+       next if attr =~ /^#/
+       filename = db.get_filename(attr)
+       move_to_obsolete(filename)
+       move_to_obsolete(filename+".txt")
+      }
+    end
+    def move_to_obsolete(file)
+      cmd = "mv #{file} #{@odir}"
+#      p cmd
+      system cmd
+    end
+  end
+
+  class JoyoList #======================================================================
+    include Singleton
+    #JP_JOYO_FILE = DB_DIR+"/../jp-joyo.txt" #EUC-jisx0213
+    JP_JOYO_FILE = DB_DIR+"/../joyo-ucs.txt" #UCS
+    COMPOSIT_KANJI = "鳴名加品古知問間聞取兄見切分粉貧林森校東明住位好岩砂里男畑習休短空坂島倉美孝赤看光初努協解新歌語話張強忘悲答晴現正字安守灰秋秒困国医包同合舌居右左受友反道返迷花菜集机主太氷州点店庫仕帳幼防引配早直班筆重番北化比死夏後進酒福私家世内谷半原前寺思電雲気布旅衆泣"
+#    COMPOSIT_KANJI = "鳴名加品古"
+    def initialize
+      @nchars = []
+      read_file
+    end
+    attr_reader :nchars
+    def read_file
+      open(JP_JOYO_FILE) {|f|
+       while line = f.gets
+         next if line =~ /^;/ #コメントはとばす
+         line.chomp!
+         #stroke, nchar, ochar = line.split #new char, old char, old charはnilが多い
+         stroke, nchar = line.split
+         @nchars << nchar
+       end
+      }
+    end
+    def dump_ids(ar)
+      ar.each {|ch|
+       char = ch.char
+       print char.inspect_ids(true), "\t;", char.inspect_ids(false), "\n"
+      }
+    end
+  end
+
 end
 
 #----------------------------------------------------------------------終了