ここからは、FreePWING の実際のプログラミングについて解説します。 JIS X 4081 は、辞典検索を得意とするデータ形式になっていますので、本章 でも辞典を対象にして、JIS X 4081 形式のデータを生成するまでの過程を 記すことにします。
すでに JIS X 4081 形式になった国語辞典があるとしましょう。 EPWING の検索ソフトウェアを用いて、この辞典で「たいさく」という語を 引いてみると、おそらく「大作」「対策」という語が見つかり、以下のような 感じで見出しが表示されると思います。
たいさく 【大作】 たいさく 【対策】
次に、このうちの「対策」の方を選ぶと、「対策」という語についての本文が 表示されます。
たいさく 【対策】 状況に応じてとる手段や策略。
通常、紙の辞典では、この本文の部分が「対策」という語に関する全データに なるでしょう。 それに対して、JIS X 4081 形式の辞典では、本文というデータに加えて、 「たいさく」が「対策」の検索語であるという情報や、検索したときに表示 する見出しをデータとして保持しています。 つまり、JIS X 4081 形式の辞書データでは、「検索語」、「見出し」、 「本文」の三つの成分に分けて保持するのです。
(JIS X 4081 形式は漢字を含んだ語も検索できますので、「たいさく」に 加えて「対策」も検索語として挙げています。 つまり、見出し、本文は一つであるのに対し、検索語は複数個用意することが できます。)
あなたが紙の辞典と似た書式の辞典データを JIS X 4081 形式に変換しようと 思ったら、このようにデータを検索語、見出し、本文に分類する処理を行う ことになります。 逆に分類さえ行えば、残る JIS X 4081 形式への変換処理の殆んどは FreePWING が自動的に行います。
では、前節の国語辞典のようなデータを JIS X 4081 形式に変換する FreePWING のプログラムを実際に作ることにしましょう。 今回の辞典では簡略化のため、前方一致、後方一致検索だけを用意すること にします。 条件検索や外字は使いません。
まず、変換前の辞典データの形式について若干補足しておきます。 辞典データの形式は、次のように見出しと説明が一行ごとに交互に現れる という単純なものであるとします。
たいさく 【大作】 (← 見出し) 大掛かりな作品。 (← 説明) たいさく 【対策】 (← 見出し) 状況に応じてとる手段や策略。 (← 説明)
見出し部分の正確な書式は、次のいずれかの形式であるものとします。
形式 1: <かな検索語> 形式 2: <かな検索語> <空白> "【" <漢字を含んだ検索語> "】"
さらに、元のデータは EUC-JP で書かれているもとします。
これは、FreePWING の取り扱う文字コードが EUC-JP だからです。
別のコードで書かれている場合は、jcode.pl
や
Jcode.pm
などで、EUC-JP に変換してから処理する必要が
あります。
この国語辞典データを処理する FreePWING プログラム例は、以下の通りです。 プログラムの細部の説明は、次節以降で行います。 なお、データと同様に、本プログラムも EUC-JP で書かれているものとします。
use FreePWING::FPWUtils::FPWParser; ## インスタンスを生成する。 $fpwword2 = FreePWING::FPWUtils::Word2->new(); $fpwheading = FreePWING::FPWUtils::Heading->new(); $fpwtext = FreePWING::FPWUtils::Text->new(); ## 書き込み用の作業ファイルを開く。 $fpwword2->open() || die $fpwword2->error_message() . "\n"; $fpwheading->open() || die $fpwheading->error_message() . "\n"; $fpwtext->open() || die $fpwtext->error_message() . "\n"; for (;;) { ## 次の一行 (見出し) を読み込む。 last if (!defined($_ = <>)); chomp; ## 本文と見出しを新しいエントリに切り替える。 $fpwtext->new_entry() || die $fpwtext->error_message() . "\n"; $fpwheading->new_entry() || die $fpwheading->error_message() . "\n"; ## 見出しを書き込む。 $fpwheading->add_text($_) || die $fpwheading->error_message() . "\n"; ## 本文を書き込む。 if (!$fpwtext->add_keyword_start() || !$fpwtext->add_text($_) || !$fpwtext->add_keyword_end() || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; } ## かなの検索語を登録する。 ($kanaword = $_) =~ s/ 【.*$//; $heading_position = $fpwheading->entry_position(); $text_position = $fpwtext->entry_position(); if (!$fpwword2->add_entry($kanaword, $heading_position, 'head', $text_position, 'text')) { die $fpwword2->error_message() . "\n"; } ## 漢字の検索語があれば、それも登録する。 if (/ 【(.*)】$/) { $kanjiword = $1; if (!$fpwword2->add_entry($kanjiword, $heading_position, 'head', $text_position, 'text')) { die $fpwword2->error_message() . "\n"; } } ## 次の一行 (説明) を読み込む。 last if (!defined($_ = <>)); ## 本文を書き込む。 if (!$fpwtext->add_text($_) || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; } } ## 書き込み用の作業ファイルを閉じる。 $fpwword2->close() || die $fpwword2->error_message() . "\n"; $fpwheading->close() || die $fpwheading->error_message() . "\n"; $fpwtext->close() || die $fpwtext->error_message() . "\n";
本節から数節に分けて、前節に示した FreePWING プログラムの解説を 行います。
今回の国語辞典データの例では、FreePWING が用意している以下の 3 つの クラスを使います。
FreePWING::FPWUtils::Word2
FreePWING::FPWUtils::Heading
FreePWING::FPWUtils::Text
これらのクラスを使うには、モジュールを直接読み込むのではなく、
FreePWING::FPWUtils::FPWParser
というモジュールを
読み込みます。
このモジュールが、上記の 3 つのクラスと同名のモジュールを読み込みます。
use FreePWING::FPWUtils::FPWParser;
次に各クラスのインスタンスを一つずつ生成します。
## インスタンスを生成する。 $fpwword2 = FreePWING::FPWUtils::Word2->new(); $fpwheading = FreePWING::FPWUtils::Heading->new(); $fpwtext = FreePWING::FPWUtils::Text->new();
さらに、それぞれのオブジェクトに対して open()
メソッドを
呼び出して、作業用ファイルを開いておきます。
ファイル名は、あらかじめモジュール内部で決めてあるものが使用されます。
## 書き込み用の作業ファイルを開く。 $fpwword2->open() || die $fpwword2->error_message() . "\n"; $fpwheading->open() || die $fpwheading->error_message() . "\n"; $fpwtext->open() || die $fpwtext->error_message() . "\n";
これより後はしばらく、for
文を用いた無限の繰り返しに
なっています。
この for
ブロックが処理の中心部分です。
元の国語辞典データの読み込み方は自由ですが、今回は Perl の
ファイルハンドル <>
から読み込むことにしました。
for
ブロックの中で見出しと説明を一行ずつ交互に
読んでいます。
ファイルの終端まで呼んだら for
ループから抜けます。
処理の概要を記すと、次のようになります。
for (;;) { ## 次の一行 (見出し) を読み込む。 last if (!defined($_ = <>)); (読み込んだ見出しの処理...) ## 次の一行 (説明) を読み込む。 last if (!defined($_ = <>)); (読み込んだ説明の処理...) }
続いて、for
ブロックの中の処理について見ていきます。
見出し分のデータを一行読み込んだら、まず本文と見出しオブジェクトに
対してそれぞれ new_entry()
を呼び、次に新しい
「エントリ (entry)」に切り替える処理を行ってから、本文
($fpwtext
)、見出し($fpwheading
) に
書き込みます。
## 次の一行 (見出し) を読み込む。 last if (!defined($_ = <>)); chomp; ## 本文と見出しを新しいエントリに切り替える。 $fpwtext->new_entry() || die $fpwtext->error_message() . "\n"; $fpwheading->new_entry() || die $fpwheading->error_message() . "\n"; ## 読み込んだ見出しを「本文部」に書き込む。 if (!$fpwtext->add_keyword_start() || !$fpwtext->add_text($_) || !$fpwtext->add_keyword_end() || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; } ## 読み込んだ見出しを「見出し部」に書き込む。 $fpwheading->add_text($_) || die $fpwheading->error_message() . "\n";
本文と見出しのエントリの切り替えとは、現在の検索語の見出し、本文の 終端を宣言し、新たな検索語に切り替えることを意味します。 たとえば、「大作」という検索語に対する書き込みが終わったら、そこで エントリを切り替え、次に「対策」という検索語に対する書き込みを行う、 といった手順で処理を行います。
辞書の元データの見出し分の行は、JIS X 4081 の見出し部と本文部の両方に 書き込んでいますが、これは辞書のデータの分類の仕方を思い出してみれば、 理解できると思います。
ただし、本文部では add_keyword_start()
と
add_keyword_end()
で囲っいることに注意して下さい。
これによって、囲った部分が「検索キー」であることを記す印が
埋め込まれます。
EPWING の検索ソフトウェアによっては、この印を本文の切れ目を意味する印
として扱っているものもありますので、必ず埋め込むようにして下さい
また、見出しは必ず一行で完結するものなので、改行の書き込み
(add_newline()
の呼び出し) は行いませんが、本文に対しては
明示的に書き込む必要があります。
読み込んだデータ側に含まれている改行文字 ("\r"
と
"\n"
) は add_text()
に渡しても無視されます
ので、本文中で改行を行う
には必ず add_newline()
を呼び出す必要があります。
プログラムでは、次に検索語を登録しています。
## かなの検索語を登録する。 ($kanaword = $_) =~ s/ 【.*$//; $heading_position = $fpwheading->entry_position(); $text_position = $fpwtext->entry_position(); if (!$fpwword2->add_entry($kanaword, $heading_position, $text_position)) { die $fpwword2->error_message() . "\n"; }
検索語の登録は、検索用のオブジェクト ($fpwword2
) に対して
add_entry()
メソッドを呼び出すことによって行います。
本文、見出しとは異なり、検索語の登録前に new_entry()
を
呼ぶ必要はありません。
add_entry()
には、次の 3 つの引数が必要です。
$fpwheading->entry_position()
で得られる位置)
$fpwtext->entry_position()
で得られる位置)
漢字の検索語の登録処理は、かなの検索語と変わりませんので、説明は 省略します。
ループ内の最後の部分です。 説明分の行を一行読み、それを本文として書き込みます。
## 次の一行 (説明) を読み込む。 last if (!defined($_ = <>)); chomp; ## 本文を書き込む。 if (!$fpwtext->add_text($_) || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; }
ループを抜けた後の部分の処理です。
生成したインスタンスの最終処理として、それぞれのインスタンスで開いて
いた書き込み用の作業ファイルを閉じます。
これには、close()
メソッドを用います。
## 書き込み用の作業ファイルを閉じる。 $fpwword2->close() || die $fpwword2->error_message() . "\n"; $fpwheading->close() || die $fpwheading->error_message() . "\n"; $fpwtext->close() || die $fpwtext->error_message() . "\n";
以上でプログラムは終りです。
では、この FreePWING プログラムを実行して、JIS X 4081 形式のデータを
生成してみます。
プログラム名が fpwkokugo
、データファイル名が
kokugo.dat
だとすると、次のように実行します。
% perl fpwkokugo kokugo.dat
処理が正常に終了すると、カレントディレクトリには word
、
eword
、head
、text
、
textref
、texttag
というファイルが生成
されます。
これらのファイルは JIS X 4081 形式のデータファイルではなく、
中間生成ファイルです。
JIS X 4081 形式のデータファイルを作成するには、さらに次のコマンドを
実行する必要があります。
% perl /usr/local/libexec/freepwing/fpwsort % perl /usr/local/libexec/freepwing/fpwindex % perl /usr/local/libexec/freepwing/fpwcontrol % perl /usr/local/libexec/freepwing/fpwlink
(ここでは FreePWING を /usr/local
以下にインストールし、
libexecdir
の位置を変更していないものとします。
他の場所にインストールしているときは、適宜読み替えて下さい。)
これらの処理をすべて行うと、カレントディレクトリにはさらにいくつかの
中間生成ファイルに加えて、honmon
というファイルが生成
される筈です。この honmon
というのが、JIS X 4081 形式の
ファイルになります。
しかし、毎回これらのコマンドを一つ一つ手で実行するのは大変ですので、
自動化を考えましょう。
それにはもちろん、コマンド行を書き並べた sh スクリプトを作って実行する
ようにしても良いのですが、FreePWING では make に対応するための仕掛けを
用意しています。
これを使えば、データファイルの最終修正時刻が honmon
よりも後の場合だけ honmon
を生成し直すといったことが
できます。
Makefile
の作成
fpwmake 用の Makefile
の記し方を説明していきます。
まず、この Makefile
の文法は、必ず GNU make 向けのもので
なければならないので注意して下さい。
つまり、make は必ず GNU make を用いる必要があります。
今回のプログラム用の Makefile
は次のようになります。
FPWPARSER = fpwkokugo FPWPARSERFLAGS = kokugo.txt include fpwutils.mk
変数 FPWPARSER
には作成した FreePWING プログラム名を、
変数 FPWPARSERFLAGS
には FreePWING プログラムに渡す
コマンド行引数を設定します。
Makefile
の末尾では include
コマンドを用いて
fpwutils.mk
というファイルを読み込みます。
(このファイルの中に生成規則が書かれています。
また、GNU make を用いる必要があるのは、このファイルが GNU make 用に
書かれているからに他なりません。)
Makefile
が準備できたら、make を実行します。
これには、専用の sh スクリプト fpwmake コマンドを使うと便利です。
fpwmake は GNU make に適切なオプションを与えて呼び出します。
カレントディレクトリを fpwkokugo のあるディレクトリに移して、 次のようにして fpwmake を実行します。
% fpwmake
fpwmake を実行すると、カレントディレクトリには work
というディレクトリができ、その下にいくつかのファイルが生成されます。
% ls control.dep eidxref1 idxref0 sort.dep ctrl esort idxref1 text ctrlref eword index.dep textref eidx0 head link.dep texttag eidx1 idx0 parse.dep word eidxref0 idx1 sort
正常に終了すれば、カレントディレクトリに honmon
ファイル
が生成されます。
fpwmake を実行すると、カレントディレクトリには work
というディレクトリができ、その下にいくつかの中間ファイルが生成されます。
無事に honmon
ファイルを生成し終り、中間ファイルおよび
honmon
ファイルが必要なくなった場合は、fpwmake clean を
実行すると、fpwmake 時に生成されたファイルがすべて消去されます。
% fpwmake clean
くれぐれも消去する前に honmon
ファイルを別の場所に移す
ことを忘れないで下さい。
そうしないと honmon
も消えてしまい、もう一度 fpwmake で
生成し直す羽目になってしまいます。
ALLDEPS
と CLEANEXTRA
ターゲット
もし fpwmake を実行するにあたって前処理を行いことがあるときは、
ALLDEPS
変数を利用します。
いま、変換スクリプトを読み込むべき辞書データが、もとは roff の ms
マクロを使用して書かれたファイル kokugo.ms
であると
しましょう。
そして、nroff コマンドを使って平文ファイル kokugo.txt
を
生成し、変換スクリプトは平文のほうを読み込んで処理をするとしたら、次の
ように書いておきます。
FPWPARSER = fpwkokugo FPWPARSERFLAGS = kokugo.txt ALLDEPS = kokugo.txt CLEANEXTRA = kokugo.txt kokugo.txt: kokugo.ms rm -f $@ tbl kokugo.ms | nroff -ms | col -b > kokugo.txt include fpwutils.mk
ただし、ALLDEPS
の仕組みを使って生成したファイル (ここ
では kokugo.txt
) は fpwmake clean を実行しても消去
されません。
そこで、CLEANEXTRA
という変数に fpwmake clean 実行時に
消して欲しいファイルを書いておきます。
以上で作業は完了なのですが、ここで再びプログラムの記述に焦点を戻します。
変換プログラムの先頭では、必ず次のようにインスタンスの生成と書き込み用 の作業ファイルを開く処理を行います。 これはどの変換プログラムでも同じです。
## インスタンスを生成する。 $fpwword2 = FreePWING::FPWUtils::Word2->new(); $fpwheading = FreePWING::FPWUtils::Heading->new(); $fpwtext = FreePWING::FPWUtils::Text->new(); ## 書き込み用の作業ファイルを開く。 $fpwword2->open() || die $fpwword2->error_message() . "\n"; $fpwheading->open() || die $fpwheading->error_message() . "\n"; $fpwtext->open() || die $fpwtext->error_message() . "\n";
実は、FreePWING にはこの部分と等価な処理をもう少し簡単に書くために、
initialize_fpwparser()
というサブルーチンが
FreePWING::FPWUtils::FPWParser
パッケージに用意されてい
ます。
initialize_fpwparser()
を使うと、上のプログラムは次のよう
に簡略化できます。
initialize_fpwparser('text' => \$fpwtext, 'heading' => \$fpwheading, 'word2' => \$fpwword2);
ファイルを閉じる部分についても同様です。
これまでは、各オブジェクトに対して close()
メソッドを呼び
出していました。
## 書き込み用の作業ファイルを閉じる。 $fpwword2->close() || die $fpwword2->error_message() . "\n"; $fpwheading->close() || die $fpwheading->error_message() . "\n"; $fpwtext->close() || die $fpwtext->error_message() . "\n";
この部分は、次のように finalize_fpwparser()
メソッドで置き
換えることができます。
finalize_fpwparser('text' => \$fpwtext, 'heading' => \$fpwheading, 'word2' => \$fpwword2);
前節に記した修正事項を反映すると、プログラムは次のようになります。 これで、本章で扱った変換プログラムは完成です。
use FreePWING::FPWUtils::FPWParser; ## インスタンスを生成する。 initialize_fpwparser('text' => \$fpwtext, 'heading' => \$fpwheading, 'word2' => \$fpwword2); for (;;) { ## 次の一行 (見出し) を読み込む。 last if (!defined($_ = <>)); chomp; ## 本文と見出しを新しいエントリに切り替える。 $fpwtext->new_entry() || die $fpwtext->error_message() . "\n"; $fpwheading->new_entry() || die $fpwheading->error_message() . "\n"; ## 見出しを書き込む。 $fpwheading->add_text($_) || die $fpwheading->error_message() . "\n"; ## 本文を書き込む。 if (!$fpwtext->add_keyword_start() || !$fpwtext->add_text($_) || !$fpwtext->add_keyword_end() || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; } ## かなの検索語を登録する。 ($kanaword = $_) =~ s/ 【.*$//; $heading_position = $fpwheading->entry_position(); $text_position = $fpwtext->entry_position(); if (!$fpwword2->add_entry($kanaword, $heading_position, $text_position)) { die $fpwword2->error_message() . "\n"; } ## 漢字の検索語があれば、それも登録する。 if (/ 【(.*)】$/) { $kanjiword = $1; if (!$fpwword2->add_entry($kanjiword, $heading_position, $text_position)) { die $fpwword2->error_message() . "\n"; } } ## 次の一行 (説明) を読み込む。 last if (!defined($_ = <>)); ## 本文を書き込む。 if (!$fpwtext->add_text($_) || !$fpwtext->add_newline()) { die $fpwtext->error_message() . "\n"; } } ## 書き込み用の作業ファイルを閉じる。 finalize_fpwparser('text' => \$fpwtext, 'heading' => \$fpwheading, 'word2' => \$fpwword2);