From 83a9ac5eb6bcec43fdd55ab04b19a75d295454ec Mon Sep 17 00:00:00 2001 From: eto Date: Thu, 13 Feb 2003 11:55:39 +0000 Subject: [PATCH 1/1] start Ruby/CHISE --- Makefile | 17 + README | 17 + doc/index.html | 331 ++++++++++++++ doc/style.css | 153 +++++++ install.rb | 20 + src/chise.rb | 1201 ++++++++++++++++++++++++++++++++++++++++++++++++++ t/Makefile | 6 + t/tc_char.rb | 121 +++++ t/tc_db.rb | 96 ++++ t/tc_ids.rb | 198 +++++++++ t/tc_str.rb | 91 ++++ t/test1.rb | 20 + t/ts_chise.rb | 11 + tools/Makefile | 18 + tools/README | 27 ++ tools/dbdumball.rb | 11 + tools/make_ids_db.rb | 22 + tools/mkdbtarball.rb | 38 ++ tools/trim_bom.rb | 9 + 19 files changed, 2407 insertions(+) create mode 100755 Makefile create mode 100755 README create mode 100755 doc/index.html create mode 100755 doc/style.css create mode 100755 install.rb create mode 100755 src/chise.rb create mode 100755 t/Makefile create mode 100755 t/tc_char.rb create mode 100755 t/tc_db.rb create mode 100755 t/tc_ids.rb create mode 100755 t/tc_str.rb create mode 100755 t/test1.rb create mode 100755 t/ts_chise.rb create mode 100755 tools/Makefile create mode 100755 tools/README create mode 100755 tools/dbdumball.rb create mode 100755 tools/make_ids_db.rb create mode 100755 tools/mkdbtarball.rb create mode 100755 tools/trim_bom.rb diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..c3ecfd8 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +# by eto 2003-0116 + +all: + @echo "make test or install" + +test: + cd t; make test + +clean: + rm */*~ + rm *~ + +install: + ruby install.rb src/chise.rb +# cp lib/chise.rb c:/ruby/lib/ruby/site_ruby/1.6/ + +#----------------------------------------------------------------------end. diff --git a/README b/README new file mode 100755 index 0000000..1580633 --- /dev/null +++ b/README @@ -0,0 +1,17 @@ +---------------------------------------------------------------------- +¡Ruby/CHISE +Ruby/CHISE‚́AXEmacs CHISE‚É‚¨‚¯‚éChaonŽÀ‘•‚ð +Ruby‚ֈڐA‚·‚邱‚Æ‚ðŽŽ‚Ý‚½ƒ‚ƒWƒ…[ƒ‹‚Å‚·B + +¡Žg‚¢•û‚È‚Ç +doc/index.html‚ðŒ©‚Ä‚Ý‚Ä‚­‚¾‚³‚¢ + +¡homepage, http://eto.com/2003/ruby/ +¡contact, Kouichirou Eto <2003 at eto.com> + +---------------------------------------------------------------------- +¡–¼Ì•ÏX +ˆÈ‘O‚ÍRuby/UTF-2000‚Æ‚¢‚¤–¼‘O‚Å‚µ‚½‚ªA +2003/02/01‚ð‚à‚Á‚āARuby/CHISE‚Æ‚¢‚¤–¼Ì‚ɕύX‚µ‚Ü‚µ‚½B + +---------------------------------------------------------------------- diff --git a/doc/index.html b/doc/index.html new file mode 100755 index 0000000..991d7e2 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,331 @@ + + +Ruby/CHISE + + + + +
+ +

Ruby/CHISE

+ + + +
+ +
+

■Ruby/CHISEとはなにか

+

Ruby/CHISEは、XEmacs CHISEにおけるChaon実装を
+Rubyへ移植することを試みたモジュールである。
+ +

■Chaonモデルとはなにか

+ +

Chaonモデルとは、文字を符号ではなく属性によって扱う方法を意味する。 +

Ruby/CHISEではそれを拡張し、文字をオブジェクトとして扱っている。 + +

+ +
+ +

■download & history

+
    +
  • 2003-0110 テスト公開 +
  • 2003-0112 XString追加 +
  • 2003-0115 IDSの読み込み機能β版 +
  • 2003-0116 IDSの読み込み機能1.0 +
  • 2003-0117 XStringを廃止し、Stringに一本化。IDSの読み込み機能を強化。 +
  • 2003-0120 IDS_Treeの読み込み機能を追加。木構造の整合性checkを追加。 +
  • 2003-0130 IDSの逆変換機能などを追加。 +
  • 2003-0213 ruby-chise-20030213.tar.bz2
    +名称をRuby/UTF-2000からRuby/CHISEへと変更。 +
+
+ +
+ +

■install

+

展開して、make installする。 +

通常、/usr/local/lib/ruby/site_ruby/以下にinstallされる。 + +


+

■config

+

src/chise.rb内 +

DB_DIR = '/usr/local/lib/xemacs-21.4.10/i686-pc-linux/char-db'
+必要に応じて変更する。 +

IDS_DB_DIR = '/home/eto/work/chise/ids/''
+IDSのテキストファイルが置かれているディレクトリーを指す。(下記の字形分解・合成についてを参照) + +


+

■依存関係

+

下記のパッケージが必要。 +

+ +

一般にRubyのパッケージは +RAAを使って探すことができる。 + +


+

■Unicode

+

現状では、Ruby/CHISEに渡す文字コードはUTF-8のUnicodeにしておくと便利である。 +

これは望ましいものではなく、将来的にはプログラム自体がSJIS、EUCなどで書かれていても +問題なく処理できるようにする予定である。 +

WindowsでUnicodeを使えるエディターとして、私はMeadow + Mule-UCSを使っている。 +

他、Windows付属のメモ帳を使うことができる。 +

また、見るだけであればIEに落すと表示される。 +

フリーのUnicode対応エディターとして他にYuditがあるが、まだよく使い方はわからない。 +

+ +
+ +

■使い方

+

+ +

■全体的な使い方

+
+require 'chise'
+include CHISE
+
+str = "字" #Stringを拡張している。UTF8で与えること。
+p str.ucs #とすると、その文字のucsの値が表示される
+p str.total_strokes #画数が表示される
+p str.chinese_gb2312 #などなど
+str.char.alist.each {|a, v| #こんな感じで全属性を表示できる
+  print a, ': ', v, "\n"
+}
+p str.inspect_x #Characterについての情報が表示される。
+p str.inspect_all #持っている属性情報を全て表示する。
+
+str = "文字列" #もちろん一文字でなく文字列も扱える。UTF-8で与える。
+p str.inspect_x #各文字の情報が表示される。
+p str.inspect_all #各文字の属性情報を全て表示する。
+
+ +
+

■様々な用例案

+

下記のような文章を入力、表示できるようになることを例として考える。 +

    +
  • 「電話は中国繁体字だと電話と書き、中国簡体字だと電話と書く」 +
  • 「吉野屋の吉は、土吉の吉である。」 +
  • 「高橋さんは高橋さんと表記されるのを嫌う。」 +
  • 「日本語の骨を、中国簡体字だと骨と書く」 +
+

が、まだ入力できません。未完成です。 +

+ +
+

■字形分解・合成

+

Ruby/CHISEは、もともと字形分解・合成を扱うために作られたため、その機能が強化されている。 + +

字形分解・合成は、現在はUnicodeにおけるIDS(Ideographic Description Structure)という仕様に準拠している。 +U+2FF0〜U+2FFBで表わされるIDC(Ideographic Description Characters)によって合成方法を指定し、 +これに続く二文字から三文字の文字を合成して表示する。 + +

これは元々必要な漢字が文字コードに無い場合にその代替物として表記するために考えられた仕様だ。 +もし文字表示機能が字形合成に対応している場合は、その合成された字を表示する。 +もし字形合成の機能が無い場合は、IDC自体を目に見えるように表示し、 +ユーザーの想像力に任せることになる。 + +

実際のところ、IDSを使った字形合成機能を持つ文字表示エンジンが存在するとは聞いたことがない。 +そのため現状ではこの仕様は絵に書いた餅になっている。 + +

ここではその仕様を転用し、漢字の字形を指示するために使っている。 + +

ちょっと想像してみればわかるが、IDSはまともな実装が存在していないことからもわかる通り、 +普通には使えない仕様である。実際に漢字の字形を合成して表示するといっても、 +縦とか横につらなるなどといった単純な情報だけでは不十分で、もっと多様な情報が必要である。 +部品間の大きさのバランスなど、ついheuristicな方法で対処できるのではないかと考えてしまいがちだが、 +実際に見ておかしくない字を作るためには現状ではまだ人手によってデザインする必要がある。 +ここではその仕様を転じて、字形の成立ちを説明するために使っているが、 +このような使い道なら使えるようだ。 + +


+

■IDSを使うための準備

+ +

下記のようにして、IDSのテキストファイル群を持ってくる。 +

% cd ~/work/chise (このディレクトリーは適宜変更する)
+% cvs -d :pserver:anonymous@cvs.m17n.org:/cvs/root login
+password: (何も入れずにただもう一度return)
+
+% cvs -d :pserver:anonymous@cvs.m17n.org:/cvs/chise co -d ids ids
+
+ +

このようにすると、IDSのテキストファイル群を持ってくることができる。 + +

その後、src/chise.rb内
+IDS_DB_DIR = '/home/eto/work/chise/ids/''
+ここに、上記のIDSテキストファイル群を持ってきたディレクトリーを入れる。 +必要であれば、再度make installする。 +このようにして適切にIDS_DB_DIRを設定し、 +./tools/idsdbdumpall.rbを実行する。(かなり時間がかかる) +これで、文字属性として新たにids, ids-decomposeが加わった。 +それぞれ、IDSの文字列、それを再帰的に分解しきったものを意味する。 + +

実用上は差し支えない範囲だが、IDSテキストファイルにはまだ入力されて +いない字もある。./tools/idscheckintegrity.rbを実行する(かなり時 +間がかかる)と、IDSの木構造の整合性をチェックし、整合性がとれていない字 +を表示する。 + +


+

■字形分解

+

Stringに、decompose, decompose_allという二つのメソッドがある。 +decomposeは一段階だけ分解する。decompose_allはそれを再帰的に行う。 +

+p "字".decompose
+p "字".decompose_all
+p "榊".decompose
+p "榊".decompose_all
+p "終了".decompose
+p "終了".decompose_all
+p "鬱".decompose
+p "鬱".decompose_all
+
+ +

最初の説明から、字形分解されて出てきた結果の文字列には、 +IDSキャラクターが含まれているため、場合によってはうまく表示されない。 +メモ帳だと表示できるだろう。 + +


+

■字形合成

+

分解の逆に合成することもできる。ことにしようと思っているが、まだできていない。 + + +

+ +
+

■説明

+

まじめなメソッドの説明を書く。(未完) +

+class String
+	char	先頭の文字をCharacterに変換したものを返す
+→method_missingで、存在しないmethodを指定すると、自動的に先頭の文字を
+Characterに変換してそれへのmethodとして呼ぶ。
+
+class Character
+	get	ある文字をgetする。(flyweightパターン)
+	[]	ある属性をgetする。get_char_attributeも使える。
+		またmethod_missingも使える。
+	[]=	ある属性をputする。put_char_attributeも使える。
+		またmethod_missingによる入力も使える。
+存在しない属性を参照したときは、nilが返る。
+
+ +
+

■tools

+

詳しくはtools/READMEを参照。 +

    +
  • dbdumpall.rb, char-dbのBDBファイル群の中身をテキストとして展開する。 +
  • idsdumpall.rb, IDSのテキストファイル群を読みこみ、BDB化する。再帰的に展開したids-decomposeも作る。 +
  • idscheckintegrity.rb, IDSの木構造の整合性をチェックする。 +
  • mkdbtarball.rb, UNIXで作ったBDBファイル群をWindowsに持っていくときに使う。 +Windowsでtar.gzを展開するには、eoがおすすめ。 +
  • trim_bom.rb, Unicodeファイルを作ったときの先頭についてくるBOM(byte order mark)を削除する。 +
+
+ +
+

■悩みどころ

+ +
+
iso-2022へのencodeはどう実現すればよいのか? +
Characterはどうencodeするかの属性を持っていて、 +XStringはその実際のencodeの処理を行うという分離でいいかな。 + +
iso-2022-jpの処理はどうすればいいのか? +
iso-2022-jpは行末ではASCIIに戻すという行単位の扱いが必要になるが、 +XStringの中からはその判断はできない。 +class IOを拡張するのがいいのか? +
+ +
+

■字形合成

+
+ +
"+木木"(+はU+2FF0を意味する)という文字列が +あるとして、しかしこれは実は"林"という一文字を表している。 +この二重性をどう取り扱うか? +
newされた時点で問答無用で"+木木"を"林"というCharacter一文字に変換 +してしまうと、その時点で区別ができなくなってしまう。つまり必要に応じて +composeするべきである。しかしその必要に応じてというのはどのように判定 +すればいいのだろうか? 明示的に指定するしかないということか。 + +
Unicode対応のeditorはどうとりあつかっているのだろうか? +Unicodeの規定によれば、このIDSによって指定された文字列は、合成された文字そのものを +表すと規定されている。合成された文字を表示可能である場合は、IDS自体を表示してはいけない。 +逆に合成した文字を表示できない場合は、IDS自体を見えるように表示しないといけない。 +とすると、Unicode対応のeditorが適切な文字合成の機能を持っていた場合、 +それは合成された結果の文字を表示するのがいいのか? 合成される前の文字列を +表示するのがいいのか? 結局ユーザーが明示して切り替えられるようにするのがいいのか? + +
もしエラーが含まれていた場合は? +
"+木".to_x.compose_ids +とした場合は、オペレータの対象が一文字しか無いので、処理できない。 +これは例外をraiseするか、元の文字列をそのまま返すか、悩みどころ。 + +
もし文字が存在しなかった場合は? +
"+林林"とかした場合は、"木"が横に四つ並んでる漢字は存在しない(と思う)ので、 +これも例外とするか、元の文字列をそのまま返すか悩みどころ。 +どの文字コード体系にも存在しないような文字を表示できる字形合成エンジンがあると +仮定して、そのエンジンに手渡されるまでは、情報が失われないように処理 +するべきである。 + +
また、本来Chaonモデルはこのような「存在しない文字」をとりあつかえるように +するためのモデルなので、こういった文字もシームレスに扱えるようにするべきである。 +しかしどのようにすればいいのかわからない。 + +
+ +
+

■Ruby/M17Nとの整合性

+

Ruby/M17Nとの整合性をどうとればいいか。 +

+ +

Ruby/M17Nブランチが本体に反映されるのは、ruby-1.8以降が予定されている。 + +

ソースコード中のm17n.c, m17n.hが該当個所。 +内部的にはUTF-8として扱えるので、それを拡張すればいいか? +UTF-8の処理への追加という形で実装できる? + +

+ + + +
+ +
Kouichirou Eto, 2003 at eto.com
+ + + diff --git a/doc/style.css b/doc/style.css new file mode 100755 index 0000000..56926bd --- /dev/null +++ b/doc/style.css @@ -0,0 +1,153 @@ +/* by eto 2003-0112 */ + +body { + margin: 0em; + padding: 0em; + font-family: Helvetica,sans-self; +/* scrollbar-3d-light-color: #ff0000;*/ + scrollbar-arrow-color: #ff6600; + scrollbar-base-color: #ffffff; +/* scrollbar-dark-shadow-color: #ff0000;*/ + scrollbar-face-color: #ffffff; + scrollbar-highlight-color: #ffffff; + scrollbar-shadow-color: #ff6600; +} +.center { text-align: center;} +/*.e { font-family: Verdana,Helvetica,sans-self;}*/ +.e { font-family: Helvetica,sans-self;} +.w40em { width: 40em; } +.w60em { width: 60em; } + +.box { + margin: 0em 0em 1em 0em; + padding: 0em; + text-align: left; + border: 0.1em solid #ff6600; +} + +h1 { + background-color: #ff6600; + color: #ffffff; +/* margin: 0em 0em 0.5em 0em;*/ + margin: 0em; +/* padding: 0.0em 0.1em 0.1em 0.0em;*/ + padding: 0em; +} +h2 { + color: #ffffff; + background-color: #ff6600; + margin: 0em; + padding: 0em; + font-size: medium; +} +h3 { + color: #ff6600; + margin: 0em 0.5em 0em 0.5em; + padding: 0em; + font-size: small; +/* font-weight: normal;*/ +} +h4 { + color: #ff6600; + margin: 0em 0.5em 0em 0.5em; + padding: 0em; + font-size: small; + font-weight: normal; +} +p { + margin: 0.5em; + padding: 0em; +} +pre { + background-color: #fff8f0; + margin: 0.1em 0.5em 0.5em 0.5em; + padding: 0.5em; + border: 0.01em solid #ff6600; + font-size: small; +} + +a { color:#ff6600; } +a:link { color:#ff7700; } +a:visited { color:#ff8800; } +a:active { color:#ff0000; } +a:hover { color:#ff0000; text-decoration:none; } + +ul { +/* margin: 0em 0em 0em 2.0em; + padding: 1em 0em 1em 0.0em;*/ + margin: 0.5em 0em 0.5em 2.0em; + list-style-type: square; +} +li { +} + +dl { + margin: 0.5em 0em 0.5em 0.5em; +} +dt { +/* margin: 0.5em 0em 0.5em 0.5em;*/ + color: #dd3300; +} +dd { + margin: 0.1em 0em 0.5em 1.5em; +} +hr { + color: #ff6600; + margin: 0em; + padding: 0em; +} +address { + background-color: #ff6600; + color: #ffffff; + padding: 0.1em; +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; + font-style: normal; +} +.white { color: #ffffff; } + +strong { + color: #ff6600; +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; +} +.s { + color: #ff6600; +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; + font-weight: bold; +} + +b { +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; + font-weight: normal; + color: #ff6600; +} +.b { +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; + font-weight: normal; + color: #ff6600; +} + +.linkbox { + background-color: #ff6600; + color: #ffffff; + margin: 0em 0em 1em 0em; + padding: 0em; + text-align: center; +/* font-family: Verdana,Helvetica,sans-self;*/ + font-family: Helvetica,sans-self; +} + +.linkbox a { color:#ffffff; } +.linkbox a:link { color:#ffffff; } +.linkbox a:visited { color:#ffffff; } +.linkbox a:active { color:#ff0000; } +.linkbox a:hover { + background-color: #ffffff; + color:#ff6600; text-decoration:none; +} + +/* ---------------------------------------------------------------------- end. */ diff --git a/install.rb b/install.rb new file mode 100755 index 0000000..3a745b6 --- /dev/null +++ b/install.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +# by eto 2003-0109 + +def usage() + print "Usage: install.rb \n" + print "% ./install.rb src/chise.rb\n" + exit +end + +usage if ARGV.length < 1 +file = ARGV[0] +usage if ! FileTest.file?(file) + +require 'rbconfig' +ruby_sitedir = Config::CONFIG["sitedir"] +cmd = "cp #{file} #{ruby_sitedir}" +p cmd +system cmd + +#----------------------------------------------------------------------end. diff --git a/src/chise.rb b/src/chise.rb new file mode 100755 index 0000000..4901fa2 --- /dev/null +++ b/src/chise.rb @@ -0,0 +1,1201 @@ +#!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) #この二文字の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!(/\?/, '!') + 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 + +#----------------------------------------------------------------------終了 diff --git a/t/Makefile b/t/Makefile new file mode 100755 index 0000000..c21bfc4 --- /dev/null +++ b/t/Makefile @@ -0,0 +1,6 @@ +# by eto 2003-0112 + +test: + ./ts_chise.rb + +#----------------------------------------------------------------------end. diff --git a/t/tc_char.rb b/t/tc_char.rb new file mode 100755 index 0000000..364ad2d --- /dev/null +++ b/t/tc_char.rb @@ -0,0 +1,121 @@ +#!/usr/bin/env ruby +# by eto 2003-0112 + +require 'test/unit' +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +class TC_Character < Test::Unit::TestCase + def setup() @char = Character.get("字") end #UTF8で与えること + def test_char(char) + assert_equal(23383, char.char_id, "translate to char_id") + assert_equal(6, char.get_char_attribute('total_strokes'), "get total strokes by XEmacs UTF-2000 like method") + assert_equal(6, char['total_strokes'], "get total strokes by Hash like method") if char.is_a? Character + assert_equal(6, char.total_strokes, "get total strokes by method") + assert_equal(23383, char.ucs, "translate to ucs") + assert_equal(22358, char.chinese_gb2312, "get character code in chinese GB2312") + assert_equal(1777, char.shinjigen_2, "get shinjigen 2") + assert_equal(3, char.ideographic_strokes, "get") + assert_equal(39, char.ideographic_radical, "get") + end + def test_chars + test_char(Character.get("字")) + test_char(Character.new("字")) + test_char("字") + end + def test_create + assert_equal(23383, Character.parse_char_id("字")) + end + def test_put_attributes + @char.put_char_attribute('test_attribute', 'test') + assert_equal('test', @char.get_char_attribute('test_attribute'), "put, get") + @char['test_attribute'] = 'test' + assert_equal('test', @char['test_attribute'], "[]=, []") + end + def test_method + assert_instance_of(Hash, @char.char_attribute_alist, "returns Hash") + assert_instance_of(Hash, @char.alist, ".alist returns Hash") + assert_instance_of(Array, @char.char_attribute_list, "returns Array") + assert_instance_of(Array, @char.list, ".list returns Array") + assert_instance_of(String, @char.inspect) + end + def test_er + assert_equal(Character.get("&J90-3B7A;"), @char, "jisx0208") +# assert_equal("&J90-3B7A;", @char.to_er, "jisx0208") + assert_equal(Character.get("&MCS-00005B57;"), @char, "mcs") + assert_equal(Character.get("&M-06942;"), @char, "ideograph-daikanwa, Morohashi") + end + def test_latin + char = Character.get("A") + assert_equal(char.ascii, 65, "ascii") + assert_equal(char.bidi_category, "L", "bidi") + assert_equal(char.name, "LATIN CAPITAL LETTER A", "name") + assert_equal(char.ucs, 65, "ucs") + assert_equal(char.latin_jisx0201, 65, "jisx0201") + assert_equal(char.latin_viscii, 65, "viscii") #って何? +#->fullwidth: (((name . "FULLWIDTH LATIN CAPITAL LETTER A") (ucs . 65313))) +#->lowercase: (((name . "LATIN SMALL LETTER A") (ucs . 97))) +#general-category: (letter uppercase) +#このへんのS式の展開が必要なものは、また後程扱うべし。 + end + def test_ids + char = Character.get("â¿°") + assert_equal(char.name, "IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT", "ids name") + assert_equal(char.to_er, "&U+2FF0;", "ids er") + assert_equal(char.bidi_category, "ON", "ids bidi") + end + def test_jis + char = Character.get("逢") + assert_instance_of(String, char.get_attributes) + char = Character.get("å­¦") + assert_instance_of(String, char.get_attributes) + end + def test_flyweight + char1 = Character.new("字") + char2 = Character.new("字") #.newで生成した場合は別々のinstanceになるのだ + assert_equal(char1, char2) #==ではある + assert_not_same(char1, char2) #equal?かというと違う + + cf = CharacterFactory.instance + char1 = cf.get("字") + char2 = cf.get("字") + assert_equal(char1, char2, "factory") #==である + assert_same(char1, char2, "factory") #かつ同じinstanceであることが保証される + + char1 = Character.get("字") #Character.newの代りにCharacter.getを使うとCharacterFactoryを使ったのと同じ効果がある。 + char2 = Character.get("字") + assert_equal(char1, char2) #==ではある + assert_same(char1, char2) #equal?かというと違う + end + def p_er(er) + p er.de_er.char.inspect_all + end + def nu_test_has_attribute + assert("&J90-4833;".de_er.char.has_attribute?) #罪 + assert(! "&MCS-00E06E9B;;".de_er.char.has_attribute?) #罪のisolated character, attributeを持ってない + assert("&C1-602E;".de_er.char.has_attribute?) #渡 + assert("&J90-454F;".de_er.char.has_attribute?) #渡 + p_er("&C1-602E;") #渡 + p_er("&J90-454F;") + p_er("&J83-4D63;") #翼 + p_er("&J90-4D63;") + p_er("&J83-3958;") #è³¼ + p_er("&J90-3958;") + end + def teardown() @char = nil end +end + +#===== PRINT_ALL [字] MCS-00005B57 &J90-3B7A; ===== +#chinese-gb2312: 0x5756 +#chinese-isoir165: 0x5756 +#korean-ksc5601: 0x6D2E +#ucs: 0x5B57 +#chinese-cns11643-1: 0x4773 +#chinese-big5: 0xA672 + +# test_print(Character.get("&CDP-8B42;")) +# test_print(Character.get("&I-CDP-8AF6;")) +#===== PRINT_ALL [舛] MCS-00ECA524 &K0-743F; ===== + +#----------------------------------------------------------------------end. diff --git a/t/tc_db.rb b/t/tc_db.rb new file mode 100755 index 0000000..3fe679e --- /dev/null +++ b/t/tc_db.rb @@ -0,0 +1,96 @@ +#!/usr/bin/env ruby +# by eto 2003-0112 + +require 'test/unit' +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +class TC_DB < Test::Unit::TestCase + def setup + @cdb = CharDB.instance + @sdb = CodesysDB.instance + end + def test_db + assert_equal("()+!", DB.unix_to_win("<>*?")) + assert_equal("<>*?", DB.win_to_unix("()+!")) + end + def test_each_db(db) + assert_instance_of(Array, db.keys) + end + def test_make_db(db) + h = {'a' => 1, 'b' => 2, 'c' => 3} + db.remove_db('test-db') #まず最初に消しておく + assert_not_nil(db.make_db('test-db', h)) + assert_not_nil(db.open_db('test-db')) + assert_equal(1, db.get('test-db', 'a')) + assert_equal(2, db.get('test-db', 'b')) + assert_equal(3, db.get('test-db', 'c')) + db.remove_db('test-db') #最後にまた消しておく + end + def test_dbs + test_each_db(@cdb) + test_each_db(@sdb) +# test_make_db(@cdb) +# test_make_db(@sdb) + end + def test_db_put + char = "字".char + char.put_char_attribute('test-attribute', 'test') + assert_equal('test', char.test_attribute) + end +end + +class TC_Codesys < Test::Unit::TestCase + def setup + @db = CodesysDB.instance + end + def nu_test_db_length + assert_equal(6287, @db.get('=jis-x0208').keys.length, "keys") + assert_equal(590, @db.get('japanese-jisx0208').keys.length, "keys") + assert_equal(499, @db.get('japanese-jisx0208-1978').keys.length, "keys") + assert_equal(593, @db.get('japanese-jisx0208-1990').keys.length, "keys") + assert_equal(6067, @db.get('japanese-jisx0212').keys.length, "keys") + assert_equal(1697, @db.get('japanese-jisx0213-1').keys.length, "keys") + assert_equal(2345, @db.get('japanese-jisx0213-2').keys.length, "keys") + assert_equal(4270, @db.get('ucs-jis').keys.length, "keys") + end + def test_db + keys = @db.keys + assert_instance_of(Array, @db.keys, "db.keys") + db = @db.get('ascii') + assert_equal(128, db.keys.length, "can get keys") + assert_equal(63, @db.get('katakana-jisx0201').keys.length, "keys") + assert_equal(94, @db.get('latin-jisx0201').keys.length, "keys") + + counter = 0 + @db.each('=jis-x0208'){|k, v| #引数のCodesysデータベースのそれぞれに対して実行する + er0 = sprintf("&J90-%04X;", k) + er1 = Character.new(v).to_er + counter += 1; break if 10 < counter + } + end + def test_jis + db = CodesysDB.instance + codesys = db.get_codesys('ascii') + char = codesys.get(65) + assert_equal("A", char.to_s) + assert_equal(128, codesys.keys.length) + ks = codesys.keys + + codesys = db.get_codesys('japanese-jisx0208-1990') + ks = codesys.keys.sort #とすることによって、JISX0208 1990の集合全部のkeysが得られる +# assert_equal(6880, ks.length) + assert_equal(8481, ks.first) + assert_equal(29734, ks.last) + char = codesys.get(15226) #"字" + assert_equal("字", char.to_s) + + assert_equal("亜", codesys.get(12321)) + jis = "亜".char.japanese_jisx0208_1990 + assert_equal("亜", codesys.get(jis)) + assert_equal("亜", sprintf("&J90-%04X;", jis).de_er) + end +end + +#----------------------------------------------------------------------end. diff --git a/t/tc_ids.rb b/t/tc_ids.rb new file mode 100755 index 0000000..a7dc9e1 --- /dev/null +++ b/t/tc_ids.rb @@ -0,0 +1,198 @@ +#!/usr/bin/env ruby +# by eto 2003-0112 + +require 'test/unit' +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +class TC_IDS < Test::Unit::TestCase + def setup + end + def test_ids + char = "榊".char + assert_equal("⿰木神", char.ids) + assert_equal("⿰木神", char.decompose) + str = "榊" + assert_equal("⿰木神", str.ids) + assert_equal("⿰木神", str.decompose) + assert_equal("⿰木⿰⺭申", str.decompose.decompose) + assert_equal("⿰木神", str.decompose!) + assert_equal("⿰木⿰⺭申", str.decompose!) + str = "榊" + assert_equal("⿰木⿰⺭申", str.decompose_all) + assert_equal("⿰木⿰⺭申", str.decompose_all!) + assert_equal("⿰木⿰⺭申", str) + #今はまだcomposeはできない。 + + de = "ç´°".decompose + assert_match(/田$/, de) + assert_equal(3, de.char_length) + de = "&JX2-7577;".de_er.decompose + de = "&CDP-8B60;".de_er.decompose + assert_equal(1, de.char_length) + de = "&JX2-217E;".de_er.decompose + assert_match(/^â¿°/, de) + assert_equal(3, de.char_length) + assert_equal(6, de.decompose!.char_length) +# assert_equal(6, de.decompose!.char_length) + + assert("⿸".char.is_ids?) + assert(! "木".char.is_ids?) + assert_equal(2, "â¿°".char.ids_operator_argc) + assert_equal(2, "&U+2FF0;".de_er.char.ids_operator_argc) + assert_equal(2, "&U+2FF1;".de_er.char.ids_operator_argc) + assert_equal(3, "&U+2FF2;".de_er.char.ids_operator_argc) + assert_equal(3, "&U+2FF3;".de_er.char.ids_operator_argc) + + assert_equal("â¿°", "&U+2FF0;".de_er.to_s) + assert("&U+2FF0;".de_er.char.is_ids?) + assert("&U+2FFF;".de_er.char.is_ids?) + assert_match(/U\+2FF0/, "&U+2FF0;".de_er.char.inspect_x) + assert_match(/IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT/, "&U+2FF0;".de_er.char.inspect_all) + (0x2FF0..0x2FFB).each {|i| + assert_match(/IDEOGRAPHIC DESCRIPTION CHARACTER/, Character.new(i).name) + } + + assert_match(/LEFT TO RIGHT/, "&U+2FF0;".de_er.name) #∫ + assert_match(/ABOVE TO BELOW/, "&U+2FF1;".de_er.name) #∨ + assert_match(/LEFT TO MIDDLE AND RIGHT/, "&U+2FF2;".de_er.name) #∬ + assert_match(/ABOVE TO MIDDLE AND BELOW/, "&U+2FF3;".de_er.name) #∀ + assert_match(/FULL SURROUND/, "&U+2FF4;".de_er.name) #∃ + assert_match(/SURROUND FROM ABOVE/, "&U+2FF5;".de_er.name) #∩ + assert_match(/SURROUND FROM BELOW/, "&U+2FF6;".de_er.name) #∪ + assert_match(/SURROUND FROM LEFT/, "&U+2FF7;".de_er.name) #⊂ + assert_match(/SURROUND FROM UPPER LEFT/, "&U+2FF8;".de_er.name) #√ + assert_match(/SURROUND FROM UPPER RIGHT/, "&U+2FF9;".de_er.name) #∂ + assert_match(/SURROUND FROM LOWER LEFT/, "&U+2FFA;".de_er.name) #∠ + assert_match(/OVERLAID/, "&U+2FFB;".de_er.name) #∵ + end + def test_tree + assert_equal("[]", Tree.new().inspect) + assert_equal("[1]", Tree.new().add_leaf(1).inspect) + assert_equal("[1, 2]", Tree.new().add_leaf(1).add_leaf(2).inspect) + assert_equal("[[]]", Tree.new().add_node.inspect) + assert_equal("[[1]]", Tree.new().add_node.add_leaf(1).inspect) + assert_equal("[[1, 2]]", Tree.new().add_node.add_leaf(1).add_leaf(2).inspect) + assert_equal("[[1]]", Tree.new().add_node.add_leaf(1).end_node.inspect) + assert_equal("[[1], [1]]", Tree.new().add_node.add_leaf(1).end_node.add_node.add_leaf(1).end_node.inspect) + + tree = Tree.new + assert_equal("[]", tree.inspect) + assert_equal("[1]", tree.add_leaf(1).inspect) + assert_equal(0, tree.depth) + assert_equal("[1, 2]", tree.add_leaf(2).inspect) + assert_equal("[1, 2, []]", tree.add_node.inspect) + assert_equal("[1, 2, [3]]", tree.add_leaf(3).inspect) + assert_equal(1, tree.depth) + assert_equal("[1, 2, [3, 4]]", tree.add_leaf(4).inspect) + assert_equal("[1, 2, [3, 4]]", tree.end_node.inspect) + assert_equal("[1, 2, [3, 4], [5]]", tree.add_node.add_leaf(5).inspect) + assert_equal("[1, 2, [3, 4], [5, [6]]]", tree.add_node.add_leaf(6).inspect) + assert_equal(2, tree.depth) + + tree = Tree.new + assert_equal('[["+"]]', tree.add_node("+", 2).inspect) + assert_equal('[["+", 1]]', tree.add_leaf(1).inspect) + assert_equal("unmatch leaves", tree.check_integrity) + assert_equal('[["+", 1, 2]]', tree.add_leaf(2).inspect) + assert_nil(tree.check_integrity) + assert_equal('[["+", 1, 2], 3]', tree.add_leaf(3).inspect) + assert_equal("extra nodes", tree.check_integrity) + + tree = Tree.new + assert_equal('[["+"]]', tree.add_node("+", 2).inspect) + assert_equal("unmatch leaves", tree.check_integrity) + assert_equal('[["+", 1]]', tree.add_leaf(1).inspect) + assert_equal("unmatch leaves", tree.check_integrity) + assert_equal('[["+", 1, ["+"]]]', tree.add_node("+", 2).inspect) + assert_equal("unmatch leaves", tree.check_integrity) + assert_equal('[["+", 1, ["+", 2]]]', tree.add_leaf(2).inspect) + assert_equal("unmatch leaves", tree.check_integrity) + assert_equal('[["+", 1, ["+", 2, 3]]]', tree.add_leaf(3).inspect) + assert_nil(tree.check_integrity) + + tree = Tree.new + assert_equal('[1]', tree.add_leaf(1).inspect) + assert_nil(tree.check_integrity) + assert_equal('[1, 2]', tree.add_leaf(2).inspect) + assert_equal("extra leaves", tree.check_integrity) + end + def test_ids_tree + assert_equal('[[<+,U+002B>, , ]]', IDS_Tree.new("+AB").inspect) + assert_equal('[[<+,U+002B>, , ], ]', IDS_Tree.new("+ABC").inspect) + assert_equal('[[<+,U+002B>, , [<+,U+002B>, , ]]]', IDS_Tree.new("+A+BC").inspect) + assert_equal('[[<+,U+002B>, , [<+,U+002B>, , ]], ]', IDS_Tree.new("+A+BCD").inspect) + + assert_equal('[<榊,U+698A>]', IDS_Tree.new("榊").inspect) +# assert_equal('[[<â¿°,U+2FF0>, <木,J90-4C5A>, <神,J90-3F40>]]', IDS_Tree.new("⿰木神").inspect) + assert_equal(1, IDS_Tree.new("⿰木神").depth) +# assert_equal('[[<â¿°,U+2FF0>, <木,J90-4C5A>, [<â¿°,U+2FF0>, <⺭,CDP-8B70>, <申,J90-3F3D>]]]', IDS_Tree.new("⿰木⿰⺭申").inspect) + assert_equal(2, IDS_Tree.new("⿰木⿰⺭申").depth) + assert_equal("unmatch leaves", IDS_Tree.new("⿰木").check_integrity) + assert_nil(IDS_Tree.new("⿰木神").check_integrity) + assert_equal("unmatch leaves", IDS_Tree.new("⿰木⿰申").check_integrity) + assert_nil(IDS_Tree.new("⿰木⿰⺭申").check_integrity) + assert_equal("extra nodes", IDS_Tree.new("⿰木⿰⺭申申").check_integrity) + assert_nil(IDS_Tree.new("榊").check_integrity) + assert_equal("extra leaves", IDS_Tree.new("榊榊").check_integrity) + + assert_equal(3, "⿳".char.ids_operator_argc) + assert_equal("⿳士冖匕", "壱".char.ids) + assert_equal(3, "壱".char.ids.char.ids_operator_argc) + assert_nil(IDS_Tree.new("⿳士冖匕").check_integrity) + assert_equal("unmatch leaves", IDS_Tree.new("⿳士冖").check_integrity) + assert_equal("extra nodes", IDS_Tree.new("⿳士冖匕匕").check_integrity) + + assert_equal("contains ques", IDS_Tree.new("⿳士冖?").check_integrity) + end + def test_tree_depth + assert_equal(1, IDS_Tree.new("林".decompose).depth) + assert_equal('["⿰木木"]', IDS_Tree.new("林".decompose).nodes.inspect) + assert_equal('[]', IDS_Tree.new("林".decompose).sub_nodes.inspect) + assert_equal(2, IDS_Tree.new("榊".decompose_all).depth) + assert_equal('["⿰木⿰⺭申", "⿰⺭申"]', IDS_Tree.new("榊".decompose_all).nodes.inspect) + assert_equal('["⿰⺭申"]', IDS_Tree.new("榊".decompose_all).sub_nodes.inspect) + + assert_equal(3, IDS_Tree.new("焔".decompose_all).depth) + assert_equal(3, IDS_Tree.new("焔".decompose_all).nodes.length) + assert_equal(2, IDS_Tree.new("焔".decompose_all).sub_nodes.length) + + assert_equal(2, IDS_Tree.new("屡".decompose_all).depth) + assert_equal("⿸尸娄", "⿸尸⿱米女".aggregate) + assert_equal(3, IDS_Tree.new("醤".decompose_all).depth) + end + def test_compose_exact #正確に一致するIDSを検知する + assert_equal("榊", "榊".decompose.compose) + assert_equal("壱", "壱".decompose.compose) + assert_equal("⿰木木", "林".decompose) + assert_equal("林", "⿰木木".compose) + assert_equal("林", "林".decompose.compose) + assert_equal("⿰木木", "⿰木木".compose.decompose) + assert_equal("林".ucs, "⿰木木".compose.ucs) + end + def test_find +# p "日雲".find #"曇" + assert_equal(4, "日雲".find .char_length) #"曇" + end + def test_compose_part +# p de.compose_ar +# p "神".compose_ar +# p "木".compose_ar.join + end + def test_aggregate +# db = IDS_DB.instance +# db.list_aggregate + end + def test_ids_error +# p "実".char.inspect_all +# p "実".char.ids +# assert_equal("contains ques", "実".char.ids_error) + assert_equal("unmatch leaves", "実".char.ids_error) +# p CharDB.instance.get('ascii').keys +# p CharDB.instance.get('no-such-attribute').keys +# p CharDB.instance.get('ids-error').keys + end +end + +#----------------------------------------------------------------------end. diff --git a/t/tc_str.rb b/t/tc_str.rb new file mode 100755 index 0000000..c2076ed --- /dev/null +++ b/t/tc_str.rb @@ -0,0 +1,91 @@ +#!/usr/bin/env ruby +# by eto 2003-0112 + +require 'test/unit' +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +class TC_String < Test::Unit::TestCase + def setup + @str = "文字列" + end + def test_str + ar = [] + @str.each_char {|char| ar << char } + assert_equal(["文","字","列"], ar) + assert_equal(["文","字","列"], @str.to_a) + assert_equal("文", @str.char_at(0)) + assert_equal("字", @str.char_at(1)) + assert_equal("列", @str.char_at(2)) + assert_equal(nil, @str.char_at(3)) + assert_equal("列", @str.char_at(-1)) + end + def test_attributes + assert_equal(23383, "字".ucs) + assert_equal(23383, "字字".ucs) + assert_equal(25991, "文".ucs) + assert_equal(25991, @str.ucs) + end + def test_er + @char = @str.char_at(1) + assert_equal(@char, Character.get("&J90-3B7A;").to_s, "jisx0208") + assert_equal("字", Character.get("&J90-3B7A;").to_s, "jisx0208") + assert_equal("字", "&J90-3B7A;".de_er, "jisx0208") + assert_equal("文字", "文&J90-3B7A;".de_er, "with other character, at the bottom") + assert_equal("字文", "&J90-3B7A;文".de_er, "at the top") + assert_equal("文字字", "文&J90-3B7A;&J90-3B7A;".de_er, "two ERs") + assert_equal("文字文字", "文&J90-3B7A;文&J90-3B7A;".de_er, "two ERs") + assert_equal("文字", "文&MCS-00005B57;".de_er, "mcs") + assert_equal("文字", "文&M-06942;".de_er, "morohashi") + assert_equal("字", "字".de_er) + + str = "文&J90-3B7A;" + str2 = str.de_er #本体に変更無し + assert_equal("文&J90-3B7A;", str) + assert_equal("文字", str2) + str3 = str.de_er! #本体が変わります + assert_equal("文字", str) + assert_equal("文字", str3) + + assert_equal("字", "&MCS-00005B57;".de_er) + assert_equal("字", "&U-5B57;".de_er) + assert_equal("字", "&U+5B57;".de_er) + assert_equal("", "&nosucher;".de_er) + assert_equal("字", "&U-5b57;".de_er) + end + def test_method + str = @str.map_char {|ch| + ch+ch + } + assert_equal("文文字字列列", str) + assert_equal("文字列", @str) + str = @str.map_char! {|ch| + ch+ch + } + assert_equal("文文字字列列", str) + assert_equal("文文字字列列", @str) + assert_equal("文文字字列列", @str) + +# assert_equal("<文,C1-4546>", "文".inspect_x) +# assert_equal("<字,J90-3B7A>", "字".inspect_x) +# assert_equal("<列,J90-4E73>", "列".inspect_x) +# assert_equal("<文,C1-4546><字,J90-3B7A><列,J90-4E73>", "文字列".inspect_x) + + ins = "字".inspect_all +# assert_match(/^<字,J90-3B7A,/, ins) + assert_match(/chinese-big5:42610/, ins) + assert_match(/chinese-cns11643-1:18291/, ins) + assert_match(/chinese-gb2312:22358/, ins) + assert_match(/chinese-isoir165:22358/, ins) + assert_match(/ideograph-daikanwa:6942/, ins) + assert_match(/ideographic-radical:39/, ins) + assert_match(/ideographic-strokes:3/, ins) + assert_match(/korean-ksc5601:27950/, ins) + assert_match(/shinjigen-2:1777/, ins) + assert_match(/total-strokes:6/, ins) + assert_match(/ucs:23383/, ins) + end +end + +#----------------------------------------------------------------------end. diff --git a/t/test1.rb b/t/test1.rb new file mode 100755 index 0000000..20910e3 --- /dev/null +++ b/t/test1.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +# by eto 2003-0117 + +$LOAD_PATH << '../src' +require 'utf2000' +include UTF2000 + +str = "字" #Stringを拡張している。UTF8で与えること。 +p str.ucs #とすると、その文字のucsの値が表示される +p str.total_strokes #画数が表示される +p str.chinese_gb2312 #などなど +str.char.alist.each {|a, v| #こんな感じで全属性を表示できる + print a, ': ', v, "\n" +} +p str.inspect_x #Characterについての情報が表示される。 +p str.inspect_all #持っている属性情報を全て表示する。 + +str = "文字列" #もちろん一文字でなく文字列も扱える。UTF-8で与える。 +p str.inspect_x #各文字の情報が表示される。 +p str.inspect_all #各文字の属性情報を全て表示する。 diff --git a/t/ts_chise.rb b/t/ts_chise.rb new file mode 100755 index 0000000..1c68bed --- /dev/null +++ b/t/ts_chise.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# by eto 2003-0112 + +require 'test/unit' + +require 'tc_char' +require 'tc_str' +require 'tc_db' +require 'tc_ids' + +#----------------------------------------------------------------------end. diff --git a/tools/Makefile b/tools/Makefile new file mode 100755 index 0000000..28d4b3d --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,18 @@ +# by eto 2003-0114 + +#test: ids_db +test: + +tarball: + ./mkdbtarball.rb /usr/local/lib/xemacs-21.4.10/i686-pc-linux/ + +dump: + ./dbdumpall.rb + +ids_db: + ./idsdbdumpall.rb + +check: + ./idscheckintegrity.rb + +#----------------------------------------------------------------------end. diff --git a/tools/README b/tools/README new file mode 100755 index 0000000..ee78b06 --- /dev/null +++ b/tools/README @@ -0,0 +1,27 @@ +¡Ruby/CHISE‚ƈꏏ‚ÉŽg‚¤‚Æ‚¢‚¢‚©‚à‚µ‚ê‚È‚¢“¹‹ïŒQ + +œdbdumball.rb +char-db‚Ì’†‚ÌbdbŒQ‚Ì“à—e‚ð‘S‚Ä.txtƒtƒ@ƒCƒ‹‚Æ‚µ‚Ädump‚µ‚Ü‚·B +‚±‚¤‚·‚é‚ƁAbdb‚Ì“à—e‚ª‚ǂ̂悤‚É‚È‚Á‚Ä‚¢‚é‚©‚ªƒeƒLƒXƒg‚Å‚í‚©‚Á‚Ä•Ö—˜‚Å‚·B +“¯‚¶directory‚ɏ‘‚«o‚µ‚Ü‚·‚̂ŁA‘‚«ž‚Ý‚Ìpermisson‚ª•K—v‚È‚Í‚¸‚Å‚·B + +œmake_ids_db.rb +ƒeƒLƒXƒg‚É‚æ‚éIDSƒtƒ@ƒCƒ‹ŒQ‚ð‘S‚Ä“Ç‚Ý‚±‚݁A +BDB‚Æ‚µ‚Ächar-db/system-char-id/‰º‚ɃZ[ƒu‚µ‚È‚¨‚µ‚Ü‚·B +ŽŸ‰ñ‚©‚ç‚͒ʏí‚Ì‘®«‚̈ê‚‚Ƃµ‚Ä.ids‚ðŽg‚¦‚é‚悤‚É‚È‚è‚Ü‚·B +isolated character‚ðœ‚­‚½‚ß‚É”ñí‚É’x‚­‚È‚Á‚Ä‚¢‚āA1ŽžŠÔ‚­‚ç‚¢‚©‚©‚è‚Ü‚·B + +œmkdbtarball.rb $1 $2 +—á: ./mkdbtarball.rb /usr/local/lib/xemacs-21.4.10/i686-pc-linux/ /var/tmp +char-db‚Ìtarball(char-db.tar.gz)‚ðì‚è‚Ü‚·B +$1‚Échar-db‚Ì‚ ‚è‚©‚ðŽw’肵‚Ü‚·B +‚Ü‚¸$2‚Ì‚Æ‚±‚ë(Žw’è–³‚¯‚ê‚Î/var/tmp)‚Échar-db‚ð‚܂邲‚ƃRƒs[‚µA +ƒtƒ@ƒCƒ‹–¼‚Ì•¶Žš•ÏŠ·‚ðs‚¢Achar-db.tar.gz‚ðì‚è‚Ü‚·B +< > * ? ¨ ( ) + ! +Windows‚̃tƒ@ƒCƒ‹–¼§ŒÀ‚ŁAã‹L‚̍¶‘¤‚Ì•¶Žš‚ªŽg‚¦‚È‚¢‚½‚߁A +‚»‚ê‚ç‚Ì•¶Žš‚ð‰E‘¤‚Ì•¶Žš‚É’u‚«‚©‚¦‚Ä‚©‚çAtar.gz‚ðì‚è‚Ü‚·B + +œtrim_bom.rb +•¶“ª‚É‚ ‚éBOM(byte order mark)‚ðŽæ‚菜‚«‚Ü‚·B + +#----------------------------------------------------------------------I—¹ diff --git a/tools/dbdumball.rb b/tools/dbdumball.rb new file mode 100755 index 0000000..b2258a9 --- /dev/null +++ b/tools/dbdumball.rb @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# by eto 2003-0110 + +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +CharDB.instance.dump_all() +CodesysDB.instance.dump_all() + +#----------------------------------------------------------------------end. diff --git a/tools/make_ids_db.rb b/tools/make_ids_db.rb new file mode 100755 index 0000000..13b346c --- /dev/null +++ b/tools/make_ids_db.rb @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby +# by eto 2003-0110 +# IDSのテキストファイルを読み、bdbとして出力する +# 同時に、各種のIDS正規化作業も行う + +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +db = IDS_DB.instance +db.make_ids_db +db.make_ids_error_check #35分程度かかる +IDS_TEXT_DB.instance.make_ids_error +db.make_ids_reverse +db.dump_ids_duplicated +db.make_ids_aggregated +db.dump_ids_aggregated +db.make_ids_parts +db.make_ids_contained +db.make_ids_decomposed #1分 + +#----------------------------------------------------------------------end. diff --git a/tools/mkdbtarball.rb b/tools/mkdbtarball.rb new file mode 100755 index 0000000..af74f64 --- /dev/null +++ b/tools/mkdbtarball.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +# by eto 2003-0109 + +require 'find' +$LOAD_PATH << '../src' +require 'chise' +include CHISE + +def usage() + print "Usage: mkdbtarball.rb \n" + print "% ./mkdbtarball.rb /usr/local/lib/xemacs-21.4.10/i686-pc-linux/ /var/tmp\n" + exit +end + +usage if ARGV.length < 1 +dir = ARGV[0] +usage if ! FileTest.directory?("#{dir}/char-db") +tmpdir = ARGV[1] +tmpdir = "/var/tmp" if tmpdir == nil + +orgdir = Dir.pwd +Dir.chdir(tmpdir) #“K“–‚Èdir‚Ɉړ®‚µ‚Ä‚©‚ç +system "cp -a #{dir}/char-db ." #‚Ü‚¸‚»‚Ìdirectory‚É–â“š–³—p‚Å‘S•”ƒRƒs[‚·‚éB + +Find.find('.'){|f| + if f =~ /([*?<>])/ #Windows‚É‚¨‚¯‚é‹ÖŽ~•¶Žš‚ªŠÜ‚Ü‚ê‚Ä‚¢‚½‚çA’uŠ·‚·‚é + nf = DB.unix_to_win(f) + cmd = "mv '#{f}' '#{nf}'" + #print cmd, "\n" + system cmd + end +} + +system "tar czf char-db.tar.gz char-db" +system "mv char-db.tar.gz #{orgdir}" +system "rm -rf #{tmpdir}/char-db" + +#----------------------------------------------------------------------end. diff --git a/tools/trim_bom.rb b/tools/trim_bom.rb new file mode 100755 index 0000000..504c768 --- /dev/null +++ b/tools/trim_bom.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# remove BOM at the begining of the file by eto 2002-1203 +STDOUT.binmode +while gets + if /^\M-o\M-;\M-?/ + $_.sub!(/^\M-o\M-;\M-?/, '') + end + print $_ +end -- 1.7.10.4