2 # Ruby/CHISE module by eto 2002-1114
3 # $Id: char.rb,v 1.3 2003-11-30 13:16:38 eto Exp $
4 # Copyright (C) 2002-2003 Kouichirou Eto, All rights reserved.
5 # This is free software with ABSOLUTELY NO WARRANTY.
6 # You can redistribute it and/or modify it under the terms of the GNU GPL2.
10 $LOAD_PATH << '../../lib' if $0 == __FILE__
12 require 'chise/config'
14 #$KCODE = 'u' #今のところこれ以外では動かない。String.splitが影響大。inspectも影響。
15 $debug = false #これはテスト用
16 $debug = true #これはテスト用
17 $stdout.binmode if $debug
18 $stdout.sync = true if $debug
20 class String #======================================================================
22 k = $KCODE; $KCODE = 'u'
23 ar = self.split(//) #$KCODEによりUTF-8的な一文字づつがarに入る
27 def each_char() to_a.each {|ch| yield ch } end
28 def each_character() to_a.each {|ch| yield ch.char } end
29 def char_length() to_a.length end
30 def char_at(n) to_a()[n] end
31 def first_char() to_a[0] end
32 def char() Character.get(to_a[0]) end
34 #def char_id() char.char_id() end #なんとなく廃止
35 #def get_char_attribute(a) char.get_char_attribute(a) end #なんとなく廃止
36 #def ucs() char.ucs() end
43 def map_char(block = Proc.new)
44 return unless block_given?
45 return self.to_a.map {|ch| (block.call(ch)).to_s }.join("")
47 def map_char!(block = Proc.new)
48 return unless block_given?
49 self.replace(self.map_char {|ch| block.call(ch)})
51 def map_character(block = Proc.new)
52 return unless block_given?
53 return self.to_a.map {|ch| (block.call(ch.char)).to_s }.join("")
55 def map_character!(block = Proc.new)
56 return unless block_given?
57 self.replace(self.map_char {|ch| block.call(ch.char)})
60 def method_missing(mid, *args)
61 if char_length == 1 #省略形が有効なのは、一文字の時だけ
62 char.method_missing(mid, *args)
64 raise NameError, "undefined method `#{mid.id2name}'", caller(1)
68 def map_utf8() map_char {|ch| ch.char.map_utf8 } end
69 alias map_ucs map_utf8
70 def map_ucs_er() map_char {|ch| ch.char.map_ucs_er } end
71 def to_er() map_char {|ch| ch.char.to_er } end
73 #put関係、[]関係は用意しないことにした。
74 def de_er!() #EntityReferenceを取り除く
75 return self unless self =~ Regexp.new(EntityReference::REGEXP_PART) #それらしいのが無ければ何もしない
77 self.sub!(Regexp.new(Regexp.escape(er)), Character.new(er).mcs_utf8) #変換自体はCharacterにまかせる
78 return self.de_er! if self =~ Regexp.new(EntityReference::REGEXP_PART) #まだあったら再帰
81 def de_er() return self.dup.de_er!; end
83 def inspect_all() map_char {|ch| ch.char.inspect_all } end
84 def inspect_x() map_char {|ch| ch.char.inspect_x } end
86 # def to_euc() map_char {|ch| ch.char.to_euc } end
87 def map_euc() map_char {|ch| ch.char.map_euc } end
88 # def to_sjis() map_char {|ch| ch.char.to_sjis } end
89 def map_sjis() map_char {|ch| ch.char.map_sjis } end
91 def glyph_decompose() map_char {|ch| ch.char.glyph_decompose } end
92 def decompose() map_char {|ch| ch.char.decompose } end
93 def decompose!() self.replace(self.decompose); self; end
94 def decompose_all_nu(level=nil)
95 level = 0 if level.nil?
97 p ['too many recursive', self]
101 return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
102 de #もうこれ以上変化は無さそうだぞと。
104 def decompose_all() map_char {|ch| ch.char.decompose_all } end
105 def decompose_all!() self.replace(self.decompose_all); self; end
107 def find() #"日雲"→"曇"とかいう感じの操作
109 length = char_length()
112 ar << char.ids_contained #その文字を含んでいる漢字のリスト
124 if length == v #全部に顔を出していたら
132 db = CodesysDB.instance
133 composed = db.get('ids', self)
134 return "" if composed.nil? #なかったよと。
135 return "" if composed.char_length == 0 #なにごと?
136 return composed if composed.char_length == 1
137 composed.each_char {|ch|
139 return ch if char.has_attribute? #とりあえず最初にみつかったものを返すというヌルい仕様
141 return "" #attributeを持つものが一つも無かったら、""にする
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|
150 next if c.nil? || c == ""
151 # print "#{self} #{node} #{c}\n"
153 n = self.gsub(node, c)
156 return self #おきかえられるものがまったくなかったら、自分をかえす。
160 module CHISE #======================================================================
162 (RUBY_PLATFORM =~ /cygwin/ || RUBY_PLATFORM =~ /mswin32/ || RUBY_PLATFORM =~ /mingw32/)
164 module_function :windows?
166 DB_DIR = Config.instance.db_dir #この後に/sysmtem-char-id/ucsという感じに続く
167 IDS_DB_DIR = Config.instance.ids_dir #この後にIDS-JIS-X0208-1990.txtという感じに続く
169 DB_DIR = '/usr/local/lib/xemacs-21.4.12/i686-pc-linux/char-db' #この後に/sysmtem-char-id/ucsという感じに続く
170 IDS_DB_DIR = '/home/eto/chise/ids/' #この後にIDS-JIS-X0208-1990.txtという感じに続く
173 class EntityReference #======================================================================
174 #状況によってどのERに変換するかが異なる可能性があるので、普通のclassとして実装したほうがいい?
176 # %w( chinese-big5-cdp CDP- 4 X),
177 # %w( ideograph-daikanwa M- 5 d),
178 # %w( ideograph-cbeta CB 5 d),
179 # %w( ideograph-gt GT- 5 d),
180 # %w( ideograph-gt-k GT-K 5 d),
181 # %w( japanese-jisx0208-1990 J90- 4 X),
182 # %w( japanese-jisx0208 J83- 4 X),
183 # %w( japanese-jisx0213-1 JX1- 4 X),
184 # %w( japanese-jisx0213-2 JX2- 4 X),
185 # %w( japanese-jisx0212 JSP- 4 X),
186 # %w( japanese-jisx0208-1978 J78- 4 X),
187 # %w( chinese-cns11643-1 C1- 4 X),
188 # %w( chinese-cns11643-2 C2- 4 X),
189 # %w( chinese-cns11643-3 C3- 4 X),
190 # %w( chinese-cns11643-4 C4- 4 X),
191 # %w( chinese-cns11643-5 C5- 4 X),
192 # %w( chinese-cns11643-6 C6- 4 X),
193 # %w( chinese-cns11643-7 C7- 4 X),
194 # %w( korean-ksc5601 K0- 4 X),
196 # CODESYS_ORDER = %w(japanese chinese korean ideograph)
198 %w( =jis-x0208-1990 J90- 4 X),
199 %w( =jis-x0208-1983 J83- 4 X),
200 %w( =jis-x0208-1978 J78- 4 X),
201 %w( =jis-x0208 J90- 4 X), #継承のアドホックな実装
202 %w( =jis-x0208 J83- 4 X), #継承のアドホックな実装
203 %w( =jis-x0208 J78- 4 X), #継承のアドホックな実装
204 %w( =jis-x0213-1-2000 JX1- 4 X),
205 %w( =jis-x0213-2-2000 JX2- 4 X),
206 %w( =jis-x0212 JSP- 4 X),
207 %w( =big5-cdp CDP- 4 X),
208 %w( =cns11643-1 C1- 4 X),
209 %w( =cns11643-2 C2- 4 X),
210 %w( =cns11643-3 C3- 4 X),
211 %w( =cns11643-4 C4- 4 X),
212 %w( =cns11643-5 C5- 4 X),
213 %w( =cns11643-6 C6- 4 X),
214 %w( =cns11643-7 C7- 4 X),
215 %w( =ks-x1001 K0- 4 X),
216 %w( =daikanwa M- 5 d),
221 REGEXP_PART = "&([-+0-9A-Za-z#]+);"
222 REGEXP_ALL = "^#{REGEXP_PART}$"
224 def self.match?(er) (er =~ Regexp.new(REGEXP_PART)) != nil end
226 def self.parse(er) #char_idをFIXNUMで返す
227 return "" unless er =~ Regexp.new(REGEXP_ALL) #なんか、間違ってる?
228 er = $1 #ついでに中身の部分を取り出す
229 return $1.hex if er =~ /^MCS-([0-9A-Fa-f]+)/ #MCS
230 # if er =~ /^U[-+]?([0-9A-Fa-f]+)/ #Unicode直打ち
231 if er =~ /^U[-+]?([0-9A-Fa-f]+)/ || er =~ /^#x([0-9A-Fa-f]+)/ #Unicode直打ち
233 elsif er =~ /^#([0-9]+)/ #Unicode直打ち
235 elsif er =~ /^my-([0-9]+)/i #マイ文字コード
236 return (0xe000 + $1.to_i)
239 er.sub!(/^I-/, '') if er =~ /^I-/ #I-がついてるとどう違うのかはよくわからない
240 each_codesys {|codesys, er_prefix, keta, numtype| #p [codesys, er_prefix, keta, numtype]
241 numtyperegex = '\d' #if numtype == 'd'
242 numtyperegex = '[0-9A-Fa-f]' if numtype == 'X'
243 regexpstr = "^#{er_prefix}(#{numtyperegex}{#{keta},#{keta}})$"
244 if er =~ Regexp.new(regexpstr)
246 code = codestr.to_i #if numtype == 'd'
247 code = codestr.hex if numtype == 'X'
248 char_id_u8 = EntityReference.get_database(codesys, code)
249 char_id_num = Character.parse_char_id(char_id_u8)
250 next if char_id_num == nil
257 def self.each_codesys()
258 # CODESYS_ORDER.each {|lang|
259 # CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype| #普通こういう書き方はしない。ループ一個にする。
260 # next unless codesys =~ lang
261 # yield(codesys, er_prefix, keta, numtype)
264 CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype|
265 yield(codesys, er_prefix, keta, numtype)
268 def self.get_database(codesys, code)
269 c = CodesysDB.instance.get(codesys, code)
271 # if codesys =~ /-jisx0208/
272 # #return self.get_database("=jis-x0208", code) #再帰でどうだ?
273 # c = CodesysDB.instance.get("=jis-x0208", code)
280 class CharacterFactory #============================================文字オブジェクトの生成、cache
289 n = Character.parse_char_id(char_id)
291 @chars[n] = Character.new(n) if @chars[n] == nil
297 GC.start #ガーベージコレクション
299 def length() @chars.length; end
301 reset if @max < @chars.length #MAXを超えたらresetしてしまう。乱暴じゃがcacheなのでこれでいいのだ。
305 class Character #=============================================================== 文字オブジェクト
306 def initialize(char_id=nil)
307 @char_id = Character.parse_char_id(char_id)
308 @attributes = Hash.new
309 @check_all_database = false
312 def to_i() @char_id end
313 def mcs_utf8() Character.u4itou8(@char_id) end
314 def mcs_hex() sprintf("%x", @char_id) end
316 #----------------------------------------------------------------------
317 def self.get(char_id) CharacterFactory.instance.get(char_id) end #flyweightパターン
319 #----------------------------------------------------------------------
320 def normalize_attribute_name(b)
322 a.gsub!(/_/, '-') #underlineは-に置換
324 a.sub!(/^map-/, '=>')
326 a.sub!(/^from-/, '<-')
329 def get_char_attribute(b) # XEmacs UTF-2000互換API群
330 a = normalize_attribute_name(b)
334 atr = check_database(a)
339 return get_char_attribute("="+a) unless a =~ /^=/ #頭に=がついてない場合はそれが省略されていることを仮定して、再帰する
342 def put_char_attribute(b,v)
343 a = normalize_attribute_name(b)
345 CharDB.instance.put(a, mcs_utf8(), v)
347 def char_attribute_alist() check_all_database(); @attributes; end
348 def char_attribute_list() check_all_database(); @attributes.keys; end
349 alias [] get_char_attribute #その略称
350 alias []= put_char_attribute
351 alias alist char_attribute_alist
352 alias list char_attribute_list
354 def method_missing(mid, *args) #参考:ostruct.rb
356 return get_char_attribute(mname) if args.length == 0
357 put_char_attribute(mname.chop, args[0]) if mname =~ /=$/ #代入
360 def has_attribute?() #意味のあるattributeを持ってますか?
365 return (keys.length != 0)
368 #----------------------------------------------------------------------
370 return false if ch == nil
371 return false unless ch.is_a? Character
372 self.char_id == ch.char_id
375 #----------------------------------------------------------------------
376 def self.parse_char_id(char_id) #FIXNUMを返す
377 return nil if char_id == nil
378 if char_id.is_a?(Numeric) #p [char_id]
379 char_id = 0x80000000 + char_id if char_id < 0 #補数表現
381 elsif char_id.is_a?(String)
382 return char_id.to_i if char_id =~ /^\d+$/ && 1 < char_id.length #文字列による数字だったら数値化してreturn
383 return EntityReference.parse(char_id) if char_id =~ Regexp.new(EntityReference::REGEXP_ALL) #実体参照?
384 char_id.sub!(/^\?/, '') if char_id =~ /^\?/ #もし先頭に?がついていたら削除
385 #このへん本当はもっとちゃんとチェックするべし
387 u4 = Uconv.u8tou4(char_id) #UCS-4文字列に変換
393 return Character.u4tou4i(u4) #UCS-4数値にしてreturn
395 raise ArgumentError, "unknown object for char_id", caller(1)
399 return 0 if u4 == nil || u4 == ""
400 return (u4[3] << 24 | u4[2] << 16 | u4[1] << 8 | u4[0]) #UCS-4数値にしてreturn
402 def self.u4itou4(num)
403 return "" unless num.is_a?(Integer)
404 return sprintf("%c%c%c%c", num&0xff, (num >> 8)&0xff, (num >> 16)&0xff, (num >> 24)&0xff) #UCS-4数値を文字列にしてreturn
406 def self.u4itou8(char_id) #ucsの数値を受けとり、UTF-8の文字一文字を返す
408 u4 = Character.u4itou4(char_id)
409 u8 = Uconv.u4tou8(u4)
412 #raise ArgumentError, "invalid char_id (#{char_id})", caller(1)
418 #----------------------------------------------------------------------
419 def check_database(a)
422 v = db.get(a, u8) #u8で表される文字のaアトリビュートを調べる。
425 def check_all_database() #現在の@char_idから、文字データベースを参照する
426 return if @check_all_database
427 return if @char_id == nil
430 atrs = db.get_all(u8) #u8で表される文字のアトリビュートを全部持ってこい
432 @attributes[a] = v #とかいう感じで代入するのでええかな?
434 @check_all_database = true #重い処理なので一応checkする
437 #----------------------------------------------------------------------
439 #ar = %w{ucs ucs-big5 ucs-cdp ucs-cns ucs-jis ucs-ks =>ucs =>ucs* =>ucs-jis}
440 #ar = %w{ucs ucs-jis ucs-big5 ucs-cdp ucs-cns ucs-ks =>ucs =>ucs* =>ucs-jis}
441 ar = %w{ucs-jis ucs =>ucs-jis}
442 #並び順は恣意的で、ucs-jisを先に出している。本来はこれも指定できるようにするべき。
444 u = get_char_attribute(a)
450 #----------------------------------------------------------------------CCS関係
451 def to_utf8() Uconv.u4tou8(Character.u4itou4(ucs())) end #UTF8文字列を返す
456 if u.nil? || 0xffff < u
462 alias map_ucs map_utf8
465 if u.nil? || 0xffff < u
468 return Character.get(u).to_er()
473 return "" if u.nil? || 0xffff < u
474 Uconv.u16toeuc(Uconv.u4tou16(Character.u4itou4(ucs())))
483 return "" if u.nil? || 0xffff < u
484 Uconv.u16tosjis(Uconv.u4tou16(Character.u4itou4(ucs())))
492 #----------------------------------------------------------------------
493 def to_er(codesys=nil) #実体参照を返す、希望するcodesysが引数(未実装)
494 return "" if @char_id == nil
495 return sprintf("&#x%04x;", @char_id) if @char_id <= 0xffff
496 return sprintf("&#x%05x;", @char_id) if @char_id <= 0xfffff
497 EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
500 return sprintf("&#{er_prefix}%0#{keta}#{numtype};", code)
502 return sprintf("&MCS-%08X;", @char_id) #本当はこれは無しにしたい
506 EntityReference.each_codesys {|codesys, er_prefix, keta, numtype|
508 ar << er if er != nil
514 return "<>" if @char_id == nil
515 ar = [to_utf8(), to_er().sub(/^&/,'').chop]
518 alias inspect inspect_x
519 def inspect_all_codesys() #未完成
520 #to_erを全てのcodesysにおいて実行する。その結果をコンパクトにまとめる
524 alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
525 return ar.join(',')+">"
529 alist.to_a.sort.each {|a, v| ar << "#{a}:#{v}" }
530 return ar.join('\n')+'\n'
534 alist.to_a.sort.each {|a, v|
535 str += "#{a}: #{v}\n"
540 def inspect_ids(hex_flag=false)
543 ar << (hex_flag ? "x"+mcs_hex : to_utf8)
544 if to_s != ids #idsが部品そのものだったら部品追加はしない
549 ar << "x"+char.mcs_hex
560 return "("+ar.join("\t")+")"
563 #----------------------------------------------------------------------IDS関係
564 def glyph_decompose() do_decompose(false) end
565 def decompose() do_decompose(true) end
566 def do_decompose(check_meaning = true)
569 # return idss if idss
570 # return k if self.is_basic_kanji? #基本漢字はstop kanjiとするぞと。
572 return self['ids-represent'] if self['ids-represent'] #ids_representを持っている場合はその値とする。
573 return self['ids-element'] if self['ids-element'] #ids_elementを持っている場合はその値とする。
574 idss = self['ids-meaning']
575 return idss if idss != nil && 0 < idss.length && k != idss
577 idss = self['ids-aggregated']
578 return idss if idss != nil && 0 < idss.length && k != idss
580 return idss if idss != nil && 0 < idss.length && k != idss
582 # return k if idss.nil? || idss.length == 0 || k == idss
583 # if idss.char_length == 2
584 # p ['What???', k, idss, k.inspect_all]
585 # #return idssx[1] #二個目だけ返すとか?
586 # return k #IDSに展開する方法が無いと。
588 # return k if k == idss
589 # if idss.include?(k) #<C5-4C4D><C6-4A37>この二文字のBUG対策
590 # #return idss.sub(k, '')
591 # return k #IDSに展開する方法が無いと。
597 de = self.decompose #出発点
601 de = pde.decompose #もう一度分解をしてみる。
602 break if pde == de #ループを抜けだす
603 exit if 10 < level #p ['too many recursive', self]
608 def decompose_all_nu(level=nil)
609 level = 0 if level.nil?
611 p ['too many recursive', self]
615 return de.decompose_all(level+1) if de != self #なにか変化があったから再帰
616 return de #もうこれ以上変化は無さそうだぞと。
618 def is_ids?() 0x2ff0 <= @char_id && @char_id <= 0x2fff end
619 def ids_operator_argc()
620 return 0 unless is_ids?
621 return 3 if @char_id == 0x2ff2 || @char_id == 0x2ff3
628 #----------------------------------------------------------------------終了