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


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.plJcode.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 つの引数が必要です。

漢字の検索語の登録処理は、かなの検索語と変わりませんので、説明は 省略します。

読み込んだ本文の処理

ループ内の最後の部分です。 説明分の行を一行読み、それを本文として書き込みます。

   ## 次の一行 (説明) を読み込む。
   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

処理が正常に終了すると、カレントディレクトリには wordewordheadtexttextreftexttag というファイルが生成 されます。 これらのファイルは 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 で 生成し直す羽目になってしまいます。

ALLDEPSCLEANEXTRAターゲット

もし 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);

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