外部インデックスと検索

はじめに

Doxygenはリリース1.8.3から、外部インデックスツールと検索エンジンを使用してHTMLを検索する機能を提供します。これにはいくつかの利点があります。

  • Doxygenはかなり単純なインデックスアルゴリズムを使用しているため、大規模なプロジェクトではDoxygenの内蔵検索エンジンよりも大幅なパフォーマンス上の利点があります。
  • 複数のプロジェクトの検索データを1つのインデックスに結合できるため、複数のDoxygenプロジェクトを横断してグローバル検索を行うことができます。
  • 検索インデックスにDoxygenが生成していない他のWebページなどの追加データを追加できます。
  • 検索エンジンはWebサーバー上で実行する必要がありますが、クライアントは引き続きWebページをローカルで閲覧できます。

誰もが独自のインデクサーと検索エンジンを書き始めるのを避けるため、Doxygenは各アクションの例ツールを提供します:データのインデックス作成にはdoxyindexer、インデックスの検索にはdoxysearch.cgi

データフローは以下の図に示されています。

外部検索データフロー
  • doxygenは生の検索データを生成します。
  • doxyindexerはデータを検索データベースdoxysearch.dbにインデックス化します。
  • ユーザーがDoxygenによって生成されたHTMLページから検索を実行すると、CGIバイナリdoxysearch.cgiが呼び出されます。
  • doxysearch.cgiツールはデータベースにクエリを実行し、結果を返します。
  • ブラウザは検索結果を表示します。

設定

最初のステップは、検索エンジンをWebサーバー経由で利用可能にすることです。doxysearch.cgiを使用する場合、これはCGIバイナリをWebサーバーから利用可能にする(すなわち、http:で始まるURLを介してブラウザから実行できるようにする)ことを意味します。

Webサーバーのセットアップ方法は本ドキュメントの範囲外ですが、例えばApacheがインストールされている場合、Doxygenのbinディレクトリにあるdoxysearch.cgiファイルをApache Webサーバーのcgi-binディレクトリにコピーするだけでよいでしょう。詳細はApacheのドキュメントをお読みください。

doxysearch.cgiがアクセス可能かどうかをテストするには、Webブラウザを起動し、バイナリのURLにアクセスして、最後に?testを追加します。

http://yoursite.com/path/to/cgi/doxysearch.cgi?test

以下のメッセージが表示されるはずです。

Test failed: cannot find search index doxysearch.db

Internet Explorerを使用している場合、ファイルをダウンロードするよう促されることがあり、そのファイルにこのメッセージが含まれています。

doxysearch.dbを作成またはインストールしていないため、この理由でテストが失敗しても問題ありません。これを修正する方法は次のセクションで説明します。

次のセクションに進む前に、上記のURL(?test部分なし)をDoxygenの設定ファイルのSEARCHENGINE_URLタグに追加してください。

SEARCHENGINE_URL = http://yoursite.com/path/to/cgi/doxysearch.cgi

単一プロジェクトインデックス

外部検索オプションを使用するには、Doxygenの設定ファイルで以下のオプションが有効になっていることを確認してください。

SEARCHENGINE           = YES
SERVER_BASED_SEARCH    = YES
EXTERNAL_SEARCH        = YES

これにより、Doxygenは出力ディレクトリ(OUTPUT_DIRECTORYで設定)にsearchdata.xmlというファイルを生成します。SEARCHDATA_FILEオプションを使用してファイル名(および場所)を変更できます。

次のステップは、効率的な検索のために生の検索データをインデックスに格納することです。これにはdoxyindexerを使用できます。コマンドラインから実行するだけです。

doxyindexer searchdata.xml

これにより、いくつかのファイルを含むdoxysearch.dbというディレクトリが作成されます。デフォルトでは、ディレクトリはdoxyindexerが起動された場所に作成されますが、-oオプションを使用してディレクトリを変更できます。

doxysearch.dbディレクトリをdoxysearch.cgiが配置されているのと同じディレクトリにコピーし、ブラウザで以下のURLにアクセスしてブラウザテストを再実行します。

http://yoursite.com/path/to/cgi/doxysearch.cgi?test

これで以下のメッセージが表示されるはずです。

Test successful.

これでHTML出力から単語や記号を検索できるようになります。

複数プロジェクトインデックス

Doxygenプロジェクトが複数あり、これらのプロジェクトが関連している場合、いずれかのプロジェクトのドキュメント内からすべてのプロジェクトの単語を検索できるようにすることが望ましい場合があります。

これを可能にするために必要なのは、すべてのプロジェクトの検索データを単一のインデックスに結合することだけです。例えば、searchdata.xmlがproject_Aとproject_Bディレクトリに生成される2つのプロジェクトAとBの場合、以下を実行します。

doxyindexer project_A/searchdata.xml project_B/searchdata.xml

そして、結果のdoxysearch.dbdoxysearch.cgiも配置されているディレクトリにコピーします。

searchdata.xmlファイルには絶対パスやリンクが含まれていませんが、複数のプロジェクトからの検索結果を正しいドキュメントセットにリンクバックするにはどうすればよいでしょうか?ここでEXTERNAL_SEARCH_IDEXTRA_SEARCH_MAPPINGSオプションが登場します。

異なるプロジェクトを識別できるようにするには、各プロジェクトにEXTERNAL_SEARCH_IDを使用して一意のIDを設定する必要があります。

検索結果を正しいプロジェクトにリンクするには、EXTRA_SEARCH_MAPPINGSタグを使用してプロジェクトごとにマッピングを定義する必要があります。このオプションを使用すると、他のプロジェクトのIDからそれらのプロジェクトのドキュメントの(相対)場所へのマッピングを定義できます。

したがって、プロジェクトAとBの場合、設定ファイルの関連部分は以下のようになります。

project_A/Doxyfile
------------------
EXTERNAL_SEARCH_ID    = A
EXTRA_SEARCH_MAPPINGS = B=../../project_B/html

プロジェクトAの場合、そしてプロジェクトBの場合

project_B/Doxyfile
------------------
EXTERNAL_SEARCH_ID    = B
EXTRA_SEARCH_MAPPINGS = A=../../project_A/html

これらの設定により、プロジェクトAとBは同じ検索データベースを共有でき、検索結果は正しいドキュメントセットにリンクされます。

インデックスの更新

ソースコードを変更した場合、最新のドキュメントを再度取得するためにdoxygenを再実行する必要があります。外部検索を使用している場合は、doxyindexerを再実行して検索インデックスも更新する必要があります。doxygendoxyindexerの呼び出しをスクリプトにまとめて、このプロセスを簡単にすることができます。

プログラミングインターフェース

これまでのセクションでは、インデックス作成と検索にdoxyindexerおよびdoxysearch.cgiツールを使用することを前提としていましたが、必要に応じて独自のインデックスおよび検索ツールを作成することもできます。

このためには3つのインターフェースが重要です。

  • インデックスツールへの入力の形式。
  • 検索エンジンへの入力の形式。
  • 検索エンジンの出力の形式。

次のサブセクションでは、これらのインターフェースについて詳しく説明します。

インデクサー入力形式

Doxygenによって生成される検索データは、Solr XMLインデックスメッセージ形式に従います。

インデクサーへの入力はXMLファイルであり、複数の<doc>タグを含む1つの<add>タグで構成され、それらはさらに複数の<field>タグを含みます。

以下に、1つのメソッドの検索データとメタデータを含む1つのdocノードの例を示します。

<add>
  ...
  <doc>
    <field name="type">function</field>
    <field name="name">QXmlReader::setDTDHandler</field>
    <field name="args">(QXmlDTDHandler *handler)=0</field>
    <field name="tag">qtools.tag</field>
    <field name="url">de/df6/class_q_xml_reader.html#a0b24b1fe26a4c32a8032d68ee14d5dba</field>
    <field name="keywords">setDTDHandler QXmlReader::setDTDHandler QXmlReader</field>
    <field name="text">Sets the DTD handler to handler DTDHandler()</field>
  </doc>
  ...
</add>

各フィールドには名前があります。以下のフィールド名がサポートされています。

  • type: 検索エントリの型。source, function, slot, signal, variable, typedef, enum, enumvalue, property, event, related, friend, define, file, namespace, concept, group, package, page, dir, module, constants, library, type, union, interface, protocol category, exception, class, struct, service, singletonのいずれか。
  • name: 検索エントリの名前。メソッドの場合、メソッドの修飾名、クラスの場合、クラス名など。
  • args: パラメータリスト(関数またはメソッドの場合)
  • tag: このプロジェクトに使用されるタグファイルの名前。
  • url: このエントリのHTMLドキュメントへの(相対)URL。
  • keywords: エントリを代表する重要な単語。これらのキーワードで検索した場合、このエントリは検索結果でより高いランクを得るべきです。
  • text: 項目に関連付けられたドキュメント。単語のみが含まれ、マークアップは含まれません。
XMLファイルのサイズが大きくなる可能性があるため、処理にはSAXベースのパーサーを使用することをお勧めします。

検索URL形式

検索エンジンがDoxygenによって生成されたHTMLページから呼び出される際、いくつかのパラメータがクエリ文字列を介して渡されます。

以下のフィールドが渡されます。

  • q: ユーザーが入力したクエリテキスト
  • n: 要求される検索結果の数。
  • p: 結果を返す検索ページ番号。各ページにはn個の値があります。
  • cb: コールバック関数の名前(パディング付きJSONに使用、次セクション参照)。

検索結果の完全なリストから、範囲[n*p - n*(p+1)-1]が返されるべきです。

以下に、クエリがどのように見えるかを示す例を示します。

http://yoursite.com/path/to/cgi/doxysearch.cgi?q=list&n=20&p=1&cb=dummy

これは「list」という単語(q=list)のクエリで、20件の検索結果(n=20)を要求し、結果番号20から始まり(p=1)、コールバック「dummy」(cb=dummy)を使用することを示しています。

値はURLエンコードされているため、使用する前にデコードする必要があります。

検索結果形式

前のサブセクションで示したように検索エンジンを呼び出すと、結果が返されるはずです。返信の形式はパディング付きJSONであり、基本的に関数呼び出しでラップされたJavaScript構造体です。関数の名前はコールバックの名前(クエリのcbフィールドで渡された名前)である必要があります。

前のサブセクションで示した例のクエリの場合、返信の主要な構造は以下のようになるはずです。

dummy({
  "hits":179,
  "first":20,
  "count":20,
  "page":1,
  "pages":9,
  "query": "list",
  "items":[
  ...
 ]})

各フィールドの意味は以下の通りです。

  • hits: 検索結果の総数(要求された数よりも多い場合がある)。
  • first: 返された最初の結果のインデックス: $\min(n*p,\mbox{\em hits})$
  • count: 実際に返された結果の数: $\min(n,\mbox{\em hits}-\mbox{\em first})$
  • page: 結果のページ番号: $p$
  • pages: 総ページ数: $\left\lceil\frac{\mbox{\em hits}}{n}\right\rceil$
  • items: 結果ごとの検索データを含む配列。

以下に、items配列の要素がどのように見えるべきかの例を示します。

{"type": "function",
 "name": "QDir::entryInfoList(const QString &nameFilter, int filterSpec=DefaultFilter, int sortSpec=DefaultSort) const",
 "tag": "qtools.tag",
 "url": "d5/d8d/class_q_dir.html#a9439ea6b331957f38dbad981c4d050ef",
 "fragments":[
   "Returns a <span class=\"hl\">list</span> of QFileInfo objects for all files and directories...",
   "... pointer to a QFileInfoList The <span class=\"hl\">list</span> is owned by the QDir object...",
   "... to keep the entries of the <span class=\"hl\">list</span> after a subsequent call to this..."
 ]
},

このような項目のフィールドの意味は以下の通りです。

  • type: 項目の型。生の検索データの「type」という名前のフィールドに見られる。
  • name: 項目の名前(パラメータリストを含む)。生の検索データの「name」および「args」という名前のフィールドに見られる。
  • tag: タグファイルの名前。生の検索データの「tag」という名前のフィールドに見られる。
  • url: ドキュメントへの(相対)URLの名前。生の検索データの「url」という名前のフィールドに見られる。
  • "fragments": 検索対象となった単語を含むテキストの0個以上のフラグメントの配列。これらの単語は、出力で強調表示するために<span class="hl"></span>タグで囲む必要があります。