パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ソースコード(Doxygen検索ボックスの問題)

DoxygenのHTML出力検索ボックスの問題を示し、ソースコード解析以外では非表示とすることを推奨する。

DoxyfileのSEARCHENGINEオプション(デフォルトはYES)でHTML出力に検索ボックスを追加できる。これはソースコード解析ならクラス名、ファイル名、関数名を検索して有益だが、ソースコード以外ではページタイトルしか検索できない。加えて以下の不具合がある。

  • 英数字以外で始まる検索文字列が機能しない。
  • HTMLファイル挿入スクリプトが複雑でページロード高速化処理が面倒くさい。

検索文字列の問題

DoxyfileのSEARCHENGINEオプションをYES、SERVER_BASED_SEARCHオプションをNOとして、クライアント側検索を前提に検討する。

検索処理の詳細

DoxygenはHTML出力ディレクトリにsearchサブディレクトリを作成し検索スクリプトと検索テーブルを配置する。検索ボックスに文字列を入力すると、検索スクリプト(search.js)はフィルター(検索ボックス<Shift>+<cursor down>)と入力文字列先頭文字でファイル検索テーブル(searchdata.js)を検索し、選択候補表示HTML(例えばall_0.html)と候補検索テーブル(例えばall_0.js)を見つけ、入力文字列で候補検索テーブルを検索し表示HTMLに候補文字列を表示する。

ディレクトリ ファイル 内容
[HTML出力ディレクトリ] ... ...
└ search search.js 検索スクリプト
searchdata.js ファイル検索テーブル
all_0.html 最初の"全て"HTLM
all_0.js 最初の"全て"候補検索テーブル
all_1.html 2番目の"全て"HTLM
all_1.js 2番目の"全て"候補検索テーブル
... ...
files_0.html 最初の"ファイル"HTLM
files_0.js 最初の"ファイル"候補検索テーブル
... ...
pages_0.html 最初の"ページ"ファイル
pages_0.js 最初の"ページ"候補検索テーブル
... ...

search\search.js

search.jsはSearchBoxクラス(Javaスクリプト関数)Searchメンバ関数(関数内関数)で選択候補の表示HTML/検索テーブルを見つける。フィルターインデックスthis.searchIndexと小文字に変換された入力文字列先頭文字idxCharでsearchdata.jsが定義するindexSectionsWithContentを検索する。

function SearchBox(name, resultsPath, inFrame, label)
{
...
// Performs a search.
this.Search = function()
{
...
var code = searchValue.toLowerCase().charCodeAt(0);
var idxChar = searchValue.substr(0, 1).toLowerCase();
if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
{
idxChar = searchValue.substr(0, 2);
}
var resultsPage;
var resultsPageWithSearch;
var hasResultsPage;
var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
if (idx!=-1)
{
var hexCode=idx.toString(16);
resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + '.html';
resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
hasResultsPage = true;
}
else // nothing available for this search term
{
...
}
...
}
...
}

search\searchdata.js

以下の原稿ファイルを与えた場合のsearchdata.jsを示す。indexSectionsWithContent[0]はフィルター"全て"、[1]は"ファイル"、[2]は"ページ"で、それぞれ先頭文字を文字列に連結した検索データである。[1]は正しくファイル名先頭文字が連結されているが、[2]は先頭j以外は奇妙なキャラクタになっている。[0]は単に[2]と[1]の連結である。すなわち日本語を検索文字列の先頭に与える場合、ファイル名検索は機能するがページタイトル検索は機能しない。

ファイル名 ページタイトル
Japanese.md Japanese
にほんご.md にほんご
ニホンゴ.md ニホンゴ
日本語.md 日本語
ニホンゴ.md ニホンゴ
var indexSectionsWithContent =
{
0: "jãæïにニ日ニ",
1: "jにニ日ニ",
2: "jãæï"
};
var indexSectionNames =
{
0: "all",
1: "files",
2: "pages"
};
var indexSectionLabels =
{
0: "全て",
1: "ファイル",
2: "ページ"
};

検索スクリプトの修正

英数字以外はファイル名とページタイトルで作成される検索データが異り両方を同時に正しく機能させる事はできない。先の奇妙なキャラクタは入力文字列をUTF-8バイト列とした時の先頭値をユニコード文字番号とする文字(あるいは文字コードISO-8859-1として解釈した文字)であり、これは恐らくDoxygenのバグであろう。"にほんご"と"ニホンゴ"は先頭UTF-8バイトが一致し同じ選択候補の表示HTML/検索テーブルを用いる。

ページタイトル 先頭文字 小文字化 UTF-8バイト列 先頭値をユニコード文字番号とする文字
Japanese J j %6A j (U+006A)
にほんご %E3%81%AB ã (U+00E3)
ニホンゴ %E3%83%8B ã (U+00E3)
日本語 %E6%97%A5 æ (U+00E6)
ニホンゴ %EF%BE%86 ï (U+00EF)

ファイル名を英数字以外で与える方が少ないため、ページタイトル検索を英数字以外でも機能できるようにsearch.jsを修正する。this.searchIndexを用いてファイル名検索とページタイトル検索で処理を切り替えれば完璧だがここでは省略する。これで英数字以外を検索文字列先頭とした場合でもページタイトルが検索可能となる。

search\search.js(修正)

idxCharが英数字以外(code>0x7F)の場合に限りUTF-8符号を表すエスケープシーケンスを取得して、その先頭値をユニコード文字番号とする文字に置き換える。

function SearchBox(name, resultsPath, inFrame, label)
{
...
// Performs a search.
this.Search = function()
{
...
var code = searchValue.toLowerCase().charCodeAt(0);
var idxChar = searchValue.substr(0, 1).toLowerCase();
if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
{
idxChar = searchValue.substr(0, 2);
}
if (code>0x7F)
{
idxChar=String.fromCharCode('0x'+encodeURI(idxChar).substr(1,2));
}
...
}
...
}

ページロード高速化の問題

Doxygen出力HTMLファイル

検索ボックスを表示する場合にDoxygenが出力HTMLファイルに挿入するタグ(スクリプト実行、コントロール定義、ウィンドウ定義)を示す。

  1. 外部スクリプトsearch/searchdata.jsとsearch/search.jsをロード実行する。
  2. search/search.jsのinit_search()をページロード後(DOM読み込み後)実行に登録する。
  3. window.onbeforeunload定義(検索ジャンプ位置の履歴登録と思われる)をページロード後実行に登録する。
  4. SearchBoxインスタンスを作成する。これはページロード後実行でない。
  5. 検索ボックスの定義(MSearchBox)。
  6. フィルター選択ウィンドウの定義(MSearchSelectWindow)。
  7. 検索結果表示ウィンドウの定義(MSearchResultsWindow)。
<!-- HTML header for doxygen 1.8.11-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja">
<head>
...
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="search/searchdata.js"></script>
<script type="text/javascript" src="search/search.js"></script>
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&amp;dn=gpl-2.0.txt GPL-v2 */
$(document).ready(function() { init_search(); });
/* @license-end */
</script>
...
<script type="text/javascript">
$(function () {
var pathName = document.location.pathname;
window.onbeforeunload = function () {
var scrollPosition = $("div#doc-content").scrollTop();
sessionStorage.setItem("scrollPosition_" + pathName, scrollPosition.toString());
}
if (sessionStorage["scrollPosition_" + pathName]) {
$("div#doc-content").scrollTop(sessionStorage.getItem("scrollPosition_" + pathName));
}
});
</script>
</head>
<body>
...
<!-- end header part -->
<!-- 構築: Doxygen 1.8.15 -->
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&amp;dn=gpl-2.0.txt GPL-v2 */
var searchBox = new SearchBox("searchBox", "search",false,'検索');
/* @license-end */
</script>
<div id="navrow1" class="tabs">
<ul class="tablist">
...
<li>
<div id="MSearchBox" class="MSearchBoxInactive">
<span class="left">
<img id="MSearchSelect" src="search/mag_sel.png"
onmouseover="return searchBox.OnSearchSelectShow()"
onmouseout="return searchBox.OnSearchSelectHide()"
alt=""/>
<input type="text" id="MSearchField" value="検索" accesskey="S"
onfocus="searchBox.OnSearchFieldFocus(true)"
onblur="searchBox.OnSearchFieldFocus(false)"
onkeyup="searchBox.OnSearchFieldChange(event)"/>
</span><span class="right">
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
</span>
</div>
</li>
</ul>
</div>
</div><!-- top -->
...
<div id="doc-content">
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
onmouseover="return searchBox.OnSearchSelectShow()"
onmouseout="return searchBox.OnSearchSelectHide()"
onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>
<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<iframe src="javascript:void(0)" frameborder="0"
name="MSearchResults" id="MSearchResults">
</iframe>
</div>
...
</div><!-- doc-content -->
...
</body>
</html>

HTMLの修正

本サイトのページロード高速化処理は検索ボックス非表示を前提とする。検索ボックス表示の場合も、外部スクリプトを並列ロード(defer属性)してインラインスクリプトをページロード後実行(ただし$(document).ready関数ではなくdocument.addEventListner関数)とすれば良いが、各実行の依存関係には注意を払う。例えばinit_search()はSearchBoxインスタンス作成より前に置かれていたが、ページロード後実行でSearchBoxインスタンス作成後の実行が保証されていた。SearchBoxインスタンス作成もページロード後実行とすれば、init_search()はその後に定義を移動しなければならない。他にもwindow.onbeforeunload定義で$(document).ready(function() {...})を$(function() {...})としたり、その関数も複数行にわたるなどで、search.js修正を含めこれらを自動バッチ処理化するのはかなり面倒くさい。

検索ボックスに関する結論

手間をかければ検索ボックスを表示し、英数字以外による検索の問題を回避し、かつウェブサイト公開を前提にしてページロード高速化を図ることは可能である。ただし検索は(ソースコード解析を除けば)ページタイトルに限られ、一般的なドキュメントで得るものは少ない。