FreePWING プログラミングドキュメント

[前へ] [次へ] [目次]

# FreePWING プログラムの作成例

ここからは、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`
  前方一致、後方一致検索のための検索語の登録
  (`Word2' の `2' は、前方一致、後方一致を同時に扱えることからこの名前が 付いています。)
`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 プログラムの実行

では、この 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 用に 書かれているからに他なりません。)

## fpwmake

`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);

[前へ] [次へ] [目次]