検索ボックスの代用表記を自動的に字上符付き文字に変換する処理など、辞書引き機能を改版しました。
上記の処理はとても簡単だったのですが、見出し語インクリメンタル検索の強調表示マークアップ誤りという障害の是正には難儀しました。JavaScriptの辛さに堪えかねて、マークアップ処理をサーバ側で実装する、即ち「ワニる」ことすら考えましたが、何とか想定通りに処理するよう書けましたので、久しぶりの処理概説を兼ねて改版内容を詳細にご紹介します。
改版点のおさらい
今回の改版点は、以下の通りです。
versio 2.1.0 (36-a publikigo, en 2008/02/07)
- 【機能追加】検索ボックスで、代用表記で入力した語を自動的に字上符付き文字に変換するようにしました。この機能はJavaScriptが有効な環境でのみ動作します。JavaScriptが無効な場合はこれまで通り代用表記のままサーバへ検索語を送信しますが、その場合でも(これまで通り)サーバ側でも字上符付き文字への変換を行っているので、結果に影響はありません。
- 【性能向上】見出し語インクリメンタル検索結果で、結果の各見出し語の検索語部分を強調する処理を高速化しました。単に正規表現をコンパイルしただけです。
- 【障害対応】見出し語インクリメンタル検索結果で、検索語のパターンが2箇所以上マッチする見出し語の強調を誤っていた問題を是正しました。例えば「vid」の前方一致絞り込みを行った場合、vidalvideの2つ目の「vid」を強調していましたが、1つ目の「vid」を正しく強調出来るようにしました。
字上符付き文字への自動変換が特長
目玉は1点目の機能追加です。字上符付き文字へ自動変換するという使い勝手の向上を図りました。
これまでも検索ボックス内に代用表記で記述した場合であっても強調表示は正しく行えるようにしていました。例えばcxaと記述していれば、見出し語インクリメンタル検索結果(絞り込み結果)は正書法(字上符付き文字)ですが、ĉabrakoやĉadoなどと強調表示出来ていました。この処理は、内部的に検索語の内容を字上符付き文字に変換していたので実現出来ていました。今回はこれに加えて、検索語入力時に自動で字上符付き文字に置換するという機能を実装したという次第です。
「ヘルプ」では代用表記に対応していると書いていますが、ユーザはそうしたヘルプを読む義理もないわけで、「代用表記を書いても検索出来るのだろうか」と不安を感じたまま入力することが少なくないでしょう。検索結果を見て初めて安心するのですが、特にウェブシステムではユーザに不安を与えることは御法度です。自動的に字上符付き文字へ(画面遷移無しに即座に)変換すれば、「試しにcxと入力してみようか」と入力した次の瞬間には字上符付き文字ĉに書き換わるので、とても安心するのではないかと考えています。
といっても、内部的に変換した内容で検索語ボックスの内容を書き換えるという処理だけですので、労少なくして得る物が多いお得な機能追加だったと、開発者側も満足しています。
今回一番労が多かったのは、3点目の障害対応です。見出し語インクリメンタル検索で絞り込んだ結果について検索語で強調表示するという処理ですが、思いの外難儀しました。
検索語とそれ以外にsplitする方法論
Perlの場合
見出し語検索結果(実際の結果画面)で表組みを実現しているように、サーバ側(Perlモジュール側)でもこの処理を使っています。例えばPerlならば以下のようになるでしょう(実際にサーバ上で稼働させているPerlモジュールの記述は若干異なりますが、読み易くするために変えています)。
# $dividiloは予めマッチ用の正規表現を格納しており、
# $kapvortoには見出し語が入っている状態である
my @inkluzivitaj_radikaroj = split $dividilo, $kapvorto, 2;
これで、0個目の要素は常に左辺・1個目は検索語・2個目は右辺となります。なお、@inkluzivitaj_radikarojの要素数は先頭で一致した場合には3個ですが、パターンが末尾で一致した場合には2個となります。実証すると、perl -e "print scalar (split /a/, 'abc');"では3ですが、perl -e "print scalar (split /c/, 'abc');"では2ですね。そこで、$inkluzivitaj_radikaroj[2] = ” unless defined $inkluzivitaj_radikaroj[2]‘;などとしています。
後は簡単で、左辺と右辺のそれぞれで語根にリンクを張ったり、各要素をtd要素にすべく<td class=”xxx”>と</td>で括るだけです。
JavaScriptの場合(キャプチャする括弧の解釈揺れに難儀)
インクリメンタル検索結果の見出し語を強調表示するというのも、要は各見出し語で検索語部分をem要素とするべく<em>と</em>で括るだけという処理です。
ところが、世がAJAXだと騒がれているこの期に及んでも、ブラウザによりsplitの解釈が異なると来ました。
alert ("abc".split(/(a)/));
この場合、Firefoxではa, bcと表示されますが、IEではbcとしか表示されません。私の環境のIE7でも然りでした。物の記述によると前者の方が正しい解釈のようですが、かといってIEを捨てる訳には当然参りません。
IEでは、正規表現パターンが先頭ないし末尾に一致すると、var inkluzivitajRadikaroj.split(re);の結果の要素数は1となります。このため、先の”abc”を”a”でマッチさせる場合、RegExp.$1などにはaが入っているのでem要素にすることは出来るのですが、inkluzivitajRadikaroj[0]の”bc”が、”a”の左辺(末尾一致時)なのか右辺(先頭一致時)なのか分からなくなります。
先頭でも末尾でもない場所で一致するならinkluzivitajRadikaroj[0] + “<em>” + RegExp.$1 + “</em>” + inkluzivitajRadikaroj[1]で済みますが、そうでないならinkluzivitajRadikaroj[0] + RegExp.$1とすれば良いか、RegExp.$1 + inkluzivitajRadikaroj[0]とすれば良いのかが分からないという状態に陥るわけです。
様々なリファレンスに当たったり生コードを拝見したりしましたが、どうにもJavaScriptのsplitはそれほど人に優しくない様子です。
そこで、これまでは以下のようにやっつけ仕事で記述していました。
vorto = "(^.+)?" + vorto + "(.+$)?";
var re = new RegExp(vorto);
for (var i = 0; i < listo.length; i++) {
listo[i].match(re);
listo[i]
= RegExp.$1
+ "<em>" + RegExp.$2 + "</em>"
+ RegExp.$3;
……書いていて恥ずかしい程のいい加減な仕事ですね。問題自体は判っていたのですが、悩ましいままひとまずそのままとしていました。
マッチ前後の変数を使用して回避する
流石にこれはないだろう、ということで書き換えの方法論を調べていたのですが、JavaScriptにもPerlでいうところの$`と$'があることを知り、今般に以下のように書き換えました。
var re = new RegExp("");
re.compile(vorto);
for (var i = 0; i < listo.length; i++) {
listo[i].match(re);
listo[i]
= RegExp.leftContext
+ "<em>" + RegExp.$1 + "</em>"
+ RegExp.rightContext;
これで、例えば先頭にマッチしてもRegExp.leftContextが空文字であるため(末尾にマッチしてもRegExp.rightContextが空文字であるため)、どのような場合でも対応出来るようになりました。
なお、上記の通り、正規表現をコンパイルし、処理速度を僅かながらに高速化しています。これが改版点の2点目です。
まとめ ~ 思わずワニりたくなる歯痒さ
キャッシュをご覧になれば判りますが、このAJAXでサーバから受信する内容は、XMLでもJSONでもなく、単なる「;」区切りの文字列です。「;」でsplitして見出し語の配列を得て、各々上記のように強調表示やらリンク付与やらの処理をしているのですが、leftContext/rightContextを知るまでは、よっぽどJavaScript側での処理を諦め、サーバ側でem要素を括った状態でクライアント側に返すようにしようかとも考えていました。
AJAXのデザインの王道は、プレゼンテーション層の処理は基本的にクライアント側(JavaScript側)に落とすことにあります。em要素を与えるというマークアップ処理をサーバ側で実装するとなると、これは正に、エスペラントで話すべき場であるのに自国語で話す、いわゆる「ワニる(krokodili)」こととそっくりです。JavaScript自体はPerl程慣れていないこともあって、コード自体はまだ汚いのですが、それでもワニることだけは避けられて良かったです。
Perlに慣れていると、Perlの文字列処理が如何に優れているかを忘れがちになりますが、AJAXのようにJavaScriptとバックエンド側のシステム(今回はPerl)を一緒に書いていると、Perlの有り難さにつくづく頭が下がるようになります。
なお、Perlではマッチ変数である$`(マッチした部分の左辺であり、use Englishスコープでは$PREMATCH)や$'(同、右辺であり、$POSTMATCH)等は、グローバルスコープである上に処理が遅くなるため使っていません。JavaScriptでも避けた方が良さそうな予感もしますが、やむなしということで落としどころをこの辺りに見つけてみたわけです。
また、実際には語根検索時、つまり検索語を半角スラッシュ「/」で括っている場合の対応等を(Perlモジュール側とJavaScript側の両方で)講じていますが、今回の話題からは逸れるために省きます。興味がある方はJavaScriptそれ自体を開けて見てみてください。
蛇足ついでに書いておくと、変数名はエスペラント書きなので、listではなくlistoだったりします。