Web辞書検索 [開発情報]

→Web辞書検索の概要、ダウンロードはこちら

Web辞書検索モジュール作成方法

モジュールはDLLで構成され、HTML parserが主な処理となります。
サンプルを見てわかるとおり、音声データのダウンロードのみなど、簡単な機能であれば実装は非常に簡単です。

処理の概要

1. wdEnumerate()を呼び出し、Web辞書検索機能を列挙してどういうものがあるのかPDICが問い合わせします。

2. wdEnumerate()で列挙された検索機能に対して、wdRequest()で実際に検索・取得を行います。

API

下記APIをDLLに実装する必要があります。

※tchar はwchar_t に読み替えてください。

extern "C" int WINAPI wdEnumerate(int index, TWDEnumerate *param);

検索機能の列挙を行います。
初回のポップアップ検索時に一度だけ呼ばれます。
index
ゼロから順番に渡されます。1つのDLLで複数のWeb辞書に対応する場合はそれぞれのindexに対して個別のWeb辞書情報を渡してください。
param
検索機能の詳細を格納する構造体です。すべてのメンバー変数がDLL→PDICへ渡される値です。
struct TWDEnumerate {
const tchar *name; // 検索機能の名称(UTF-16)
const tchar *filekeyword; // 検索機能を機能させる対象の辞書かどうかの判断を行うためのキーワード(UTF-16)
const char *url; // 検索先URL
int expires; // 辞書データの有効期間[秒]
int flags; // WDF_xxx の組み合わせ
};

name : 検索機能の名称(現在未参照、自由な名前で)

filekeyword : キーワード
Web辞書検索を行うかどうかは辞書のファイル名で判断します。【重要】
例えばサンプルにあるWiktionary辞書検索は、辞書ファイル名に !WiktionaryEN が含まれていると検索を行い、その結果をその辞書へ保存します。
大文字・小文字が区別されるので注意してください。
名前に制限はありませんが、!で始まるファイル名はWeb辞書検索用辞書とし、他の通常の辞書と区別しやすいようにします。
url : 検索先のURL
PDIC側では現在参照しません。
expires : 辞書データの有効期間
Webからダウンロードした辞書データの有効期間を指定します。最後にダウンロードした時刻からこの有効期間の間は、同じ単語に対してダウンロードを要求しません。Web辞書の更新頻度、サーバーの負荷を考えて適当な時間をしてしてください。多くのWeb辞書は頻繁に更新されることは無いので、一週間〜一ヶ月程度でいいと思います。
最後にダウンロードした時刻はローカルのファイルにPDIC辞書形式で保存されます。ファイル名は辞書ファイル名の拡張子を .ts にしたもので、辞書と同じフォルダに保存されます。
ヒント デバッグ時はここをゼロにすることで、同じ単語を使って繰り返しテストできます。
flags : WDF_xxxxの組み合わせ
WDF_IMMEDIATE
検索して結果が得られた単語のうち、ポップアップウィンドウでその単語が表示されていたときに表示をすぐに更新するかどうかの指定です。
このフラグが指定されていると表示中の単語であってもすぐに更新します。
すぐに更新すべきかどうかはユーザーの好みに依存するため、本来であればPDICの設定項目とすべきですが、現状DLLに依存しています。
将来的にはPDIC側に設定を持たせるかもしれません。
WDF_RANDOM_WALK - not supported
ポップアップ検索以外にもバックグラウンドでWeb辞書検索を行うかどうか指定するフラグです。
※現在サポートされていません。
WDF_ON_THE_FLY - not supported
辞書に保存せずに検索結果のみを表示する機能を使用するかどうかのフラグです。
※現在サポートされていません。
WDF_RELATED
ポップアップ検索では複数の単語がヒットする場合がありますが、1番最初に現れた単語以外も検索を行うかどうかのフラグです。特別な理由がない限りここは有効にした方が良いでしょう。
Return Value
1 : 該当データ有り
0 : 列挙を終了

extern "C" int WINAPI wdRequest(int index, const wchar_t *srch_word, TWDResponse *resp, const wchar_t *dictionary_path, TWDInetAccess *inet);

実際のWeb辞書検索を行います。
PDICはポップアップ検索で検索に使用した単語、およびヒットしたすべての単語を使ってWeb辞書検索要求を出します。
このAPIはスレッドセーフである必要があります。PDICは一回のポップアップ検索に対して1つのスレッドを起動しますが、ポップアップ検索をキャンセルした後すぐに別の単語のポップアップ検索を行うことがあります。また、一つのDLLで複数のWeb辞書検索を行う場合でも、indexの値を変えて異なるスレッドでこのAPIが同時に呼ばれます。
(スレッドセーフにするためにmutexでAPIを完全にblockするようなことはしないでください。レスポンスが非常に悪くなります)

index

wdEnumerate()のindexに対応します。このindexの値でどのWeb辞書に対して検索を行うのか判断してください。
srch_word
検索語そのものです。この単語と完全一致する単語のみを検索してください。
resp
struct TWDResponse {
BSTR pron;
BSTR japa;
BSTR exp;
BSTR filelinks[16]; // file link filenames, up to 16
int *cancelReq;
HANDLE hEvent; // not supported;
};
検索結果などを格納する構造体です。
pron : 発音記号
japa : 訳語
exp : 用例
filelinks : ファイルリンクファイル名
検索結果のテキストを Windowsの SysAllocString() APIで確保した文字列をセットしてください。
テキストが空である項目については何もせず、NULLのままにしてください。
SysAllocString()で確保したメモリ領域はPDIC側で解放されます。
ファイルリンクファイル名は16個までです。
ファイルリンクに使用するファイル名は特別な理由がない限り絶対パスを指定せず、パスを除いたファイル名のみにしてください。パスは辞書グループの設定で行います。そうしないとユーザーが辞書を移動したとき、リンクエラーとなってしまいます。
辞書のフルパスはdictionary_pathで得られます。
※filelinksでサブフォルダを指定することは現在できません。必要性があれば対応を考えます。
cancelReq : キャンセル要求
PDIC側からのキャンセルリクエストです。値そのものではなくポインターであることに注意してください。
この値がゼロ以外の値になったら現在行っている処理をすぐに中断して0を返してください。
※このフラグは常にポーリングする必要はありません。極力無駄な処理を減らすために設けています。
hEvent : イベントハンドル - not supported
cancelReq でキャンセル要求を受け取るにはポーリングする必要があるため、やや不便です。
そのような場合のためにこのイベントハンドルによるオプションがあります。
このハンドルは CreateEvent()で作成されたハンドルです。
※現状サポートされていません。必要であれば追加します。
dictionary_path
検索結果を保存する辞書のフルパス名です。
ファイルリンクの保存先の判断などに使用してください。
inet
ネットへアクセスするための構造体です。WinInetを使ったHTTPリクエストが可能です。簡単なHTTPリクエストであれば自前でわざわざ処理することなく、この構造体を利用することで簡単に利用できます。
struct TWDInetAccess {
FNWDHttpReq HttpReq;
FNWDGetHttpFile GetHttpFile;
};
HttpReq : int (*FNWDHttpReq)(const char *url, BSTR *res)
GETメソッドによるHTTPリクエストを行います。
resにSysAllocString()で確保された文字列が格納されます。HttpReqが戻り値がゼロ以外である場合は必ずSysFreeString()で解放してください。
※このAPIはthread safeです。
GetHttpFile : int (*FNWDGetHttpFile)(const char *url, const char *param, const tchar *path)
GETメソッドにより、ファイルでデータを保存します。
param は空文字を渡してください。
pathは保存先のファイル名です。
※このAPIはthread safeです。
Return Value
1 : 該当データを正常に取得できた
0 : 該当データがない、キャンセル、エラー

DLLのインストール

  1. <MyDocuments>\Pdic\LangProc\SWD に作成したDLLファイルを置いてください。
  2. filekeyword を含むファイル名の辞書を作成し、辞書グループへ登録します。(大文字・小文字の違いに注意してください)
  3. ファイルリンクを使用している場合は、ファイルリンクのパスを指定してください。File - 辞書設定<詳細>、使用辞書一覧の辞書のプロパティで「ファイルリンク設定」の「デフォルトパス」を設定します。(サンプルプログラムでは VoiceFiles になっています)
以上でポップアップ検索をすれば動作するはずです。
ヒント
ポップアップ検索、インクリメンタルサーチ以外はDLLはunload状態になっています。開発時にDLLを変更する場合は、そのまま上書きをするだけでテストができます。(PDICの再起動は不要)
ただし、wdEnumerate()は初回検索時のみなので、wdEnumerate()のパラメータに変更がある場合はPDICを再起動してください。

DLLの配布方法

DLLのみを配布して、上記の"DLLのインストール"と同様の手順でも構いませんが、辞書設定ファイル(PDI)の形式で配布するとインストール手順が簡単になります。
File - 辞書設定 - 辞書設定ファイルの作成 で作成してください。あとは表示に従ってください。不明な点がある場合はヘルプをご覧ください。
このWizard中、追加モジュールという入力欄があるので、そこに作成したDLLファイルを追加してください。
5.4.18以降は、辞書ファイル一覧で「空の辞書を作成」というオプションがあるのでそれを選択してください。
※5.4.17以前のPDICでこのオプションが設定された辞書をインストールするとエラーになり、インストールができません。
作成された辞書設定ファイル(拡張子pdi)にまとめられるので、それを配布してください。その際、ファイル名にバージョン番号を付加して区別できるようにした方が良いでしょう。
例:Dictionary.pdi → Dictionary-1.03.pdi という感じでファイルをコピー。(renameではなく・・・ここがポイント)

バージョンアップ時のDLLの配布方法

Ver.5.4.18以降のみを対象にした場合

手順は以前に比べて非常に簡略化されています。変更する箇所はバージョン番号のみです。

1. File - 辞書設定で 辞書設定ファイルの作成【辞書提供者向け】 を選択
2. 辞書設定ファイルに、前バージョンで作成したpdiファイルを指定、「次へ」
3. 辞書グループを指定(通常そのままでOK)、「次へ」
4. バージョン番号 を増やす。「次へ」 (配布用キーは変更しない。変更したらバージョンアップになりません)
5. あとは変更する箇所は通常ありません。(辞書ファイル一覧でも「空の辞書を作成」のまま)最後まで進んで作成してください。

作成された辞書設定ファイル(拡張子pdi)は、初回の配布と同じようにバージョン番号を付加して配布した方が良いでしょう。
例:Dictionary.pdi → Dictionary-1.04.pdi という感じでファイルをコピー。(renameではなく・・・ここがポイント・・・次回バージョンアップ時はDictionary.pdiを使います)

※注意事項:Ver.5.4.17以前のユーザーが「空の辞書を作成」オプションを指定した辞書設定ファイルでインストールを行うとエラーが発生し、インストールが出来ません。

この辞書設定ファイルでインストールを行ったユーザー側でも、インストールが完了すればほかにやる作業はありません。

Ver.5.4.17以前を含めたすべてのバージョンを対象にした場合

新たな辞書グループとPDIファイルを作成してください。例えば、辞書グループ名を
モジュール名-バージョン番号
という感じでまったく別の新しい辞書グループとして作成します。
それ以外の辞書ファイル名、DLLのファイル名はそのままでOKです。

※このようにしなければならない理由は、PDIファイルのインストール時に辞書ファイルが上書きされてしまうことと、PDIファイルが古いバージョンのものを上書きして作成されてしまうためです。(後者は手順を追加することで避けられますが)
※ネット共有辞書機能を利用したPDIファイルをバージョンアップ用に作成する場合はさらに手順が必要です。説明が必要な方はご連絡ください。(当分はいないと思い、説明を省略しています)

具体的な手順:
(Ver.5.2.10以降 - それ以前の場合は下記の方法ではうまくいきません)

1.上記の通り、新たな辞書グループを作成する。(File - 辞書設定<詳細>で旧バージョンの辞書グループを"複製"することで簡単にできます)
2.File - 辞書設定で 辞書設定ファイルの作成【辞書提供者向け】 を選択
3.旧バージョンのPDIファイルを新しいバージョンのPDIファイル名に変更する。 (重要)
4.新しいバージョンのPDIファイルを指定して「次へ」、「はい」
5.1.で作成した辞書グループを指定する(旧バージョンの辞書グループではありません
6.辞書の配布用キー を空欄にし、自動生成 する。
7.バージョン番号 を増やす。「次へ」
8.辞書説明ファイル、辞書サイトのURLなどは必要がなければそのまま。「次へ」
9.追加モジュール は変更無ければそのまま。(このファイルは実際にインストールされるパス、つまり <MyDocuments>\Pdic\LangProc\SWD に置いてあるファイルが使用されます) 「次へ」
10.辞書設定ファイルを確認して、問題なければ「次へ」作成。

PDIファイルは3の手順で名前を変更したPDIファイルに上書きされています。
試しに、
File - 辞書設定 - 辞書設定ファイルの作成【辞書提供者向け】
で作成されたPDIファイルを選択して先へ進むと内容が変わっていることがわかります。

※旧バージョンのPDIファイルを変更する作業が特殊なところなのでそれを改善するつもりです。(providerがいればですが)

サンプルソースコード

ソースコード一式

※要求があっても、自分で改良して世の中に貢献する、って言う人はなかなか現れないので、最新に更新はしていません。最新のソースが欲しい方は↓のところへご連絡ください。

HTML parser が独自に実装が必要な部分で、それを除けば非常に簡単なプログラムです。
C++Builder 5で作成されています。
VisualStudio2003ではコンパイルが通るところまで確認していますが、ライブラリのソースコードがないため、リンクでエラーとなります。
各ファイルの説明
WebDicDll.h - DLL APIに関するプロトタイプ、構造体。API等の詳細に関してはこれを参照してください。
Wiktionary.cpp/.h - Wiktionary検索の実装部分。Web辞書毎にコーディングする必要有り。

WiktionaryExp.cpp - DLL APIのexport定義部分。

WiktionaryWD.cpp - DLL entry処理。

lib/HTMLParser.cpp/.h - HTML parserの基本クラス。ここから派生してWeb辞書毎のparserを作成できます。もちろん、すでに独自の(便利な)ライブラリがあればそれを使っても構いません。あくまでサンプルです。

lib/sowUtil.cpp/.h - URLの処理など簡単なユーティリティー関数。

上記以外の関数等は tnclass.lib に入っています。tnclass.libはPDIC用の独自ライブラリなのですが、このために添付するのも大事になるのでC++Builder/Delphi用のlibファイルしか用意していません。(ヘッダーファイルはコンパイルに必要なものはすべて入っている)
C++Builderで作成される場合は問題ないと思いますが、VisualStudioに関してはtnclass.libが対応していないため、tnclass.libのソースコードが必要になります。しかし、そのソースコードはそのままではコンパイルが通りません。
従ってそれよりは、STLなどの汎用ライブラリを使った方が良いでしょう。例えば、tnstr という文字列クラスは STLのstringクラスを改善したものですし、ほとんどの関数はどこにでも転がっている機能で容易に代用できると思います。

著作権

ご自由に使ってください。

FAQ

・サンプルプログラムを流用し、商業的利用しても構わないか?PDIC以外の目的で利用しても問題ないか?
→問題ありません。ただし作者(TaN)は一切の責任を負いません。

・○○○Webの辞書に対応してもらえないか?
→一般性があり、興味があればやってみますが、基本的にやりません。業務委託でやって欲しいのでしたら別途相談ください

・DLLのファイル名は?
→ご自由に。衝突する可能性はありますが、PDICユーザーにプログラマーはほとんどいないようなので大丈夫でしょう。。(予想に反して)増えるようであれば対策を考えます。

・動作しない?
→DLLがloadされているか?
 →されていなければDLLの置き場所が正しいか確認。拡張子がDLLになっているか確認。
→wdEnumerateが呼ばれているか?
 →呼ばれていない場合、DLLのexport名が "wdEnumerate""wdRequest" になっているか?両方のAPIがexportされているか?(片方のみだとエラー扱い)
→wdEnumerateは呼ばれるが、wdRequestが呼ばれない
 →そんなことはないはず。wdEnumerateで渡している値が本当に正しいか、ほかの同名のDLLが無いか、ファイル名の誤りなどを確認。
上記クリアできれば、あとはDLL側でデバッグできるはず。

・Webサイトの許可を得ているのか?
→まったく。User-Agentを見ればPDICということがわかるため、一方的に止められる可能性はあります。そのときはWebサイト運営者と協議の上。。
マイナーアプリでトラフィックも少ないと思いますので見逃して欲しいです。

開発内容に関する問い合わせ

[TechMemo] Web辞書検索DLLのインストーラー