Solr3.1のHighlighterで数値文字参照にならないようにする

たまたま#SolrJPのハッシュタグを見ていたら、exabugsさんのつぶやきを見つけて、Solrのソースを追いかけてみたりしました。折角良いネタを頂いたので、しっかりblogのネタにさせてもらいますw

この問題はSolr3.1からなのかどうかは調べていませんが、確かにhighlightingの結果として返される文字列は、非ASCII文字が数値文字参照になっています。

試しにSolr3.1で「日本語テスト」という文字列に対して「日本語」で検索してみます。
ブラウザで見ると

と、問題無く表示されているように見えますが、実際に返されているXMLを見てみると

このように非ASCII文字が数値文字参照になっている事が分かります。

この事には気付いていたのですが、私には実害があまり思い当たらなかったので放置していました。
英語圏で作られたライブラリのコードで何度か見かけた事があります。
むかーしむかし(5年ぐらい前だったか)に、私の持ってたケータイで数値文字参照が解釈できずそのまま表示されてしまう、のがあって対処した事があるのですが、もしかしたらexabugsさんもこの問題を解決したいのかなと。
もしくは更に文字列として何か加工する際に、数値文字参照だと扱いづらいとか、そんな所でしょうか。

さて、問題も分かった(?)所で早速解決方法を探してみましょう。

Solrで検索にヒットした箇所を強調表示しているのはHighlighting Componentというコンポーネントで、強調表示以外の非ASCII文字は数値文字参照になっていない事から、このコンポーネントのどれかで変換が行われているはずです。
このコンポーネントが設定できるパラメータはwikiに書かれていますが、これらの一覧を見てみても、数値文字参照に関するパラメータは無さそうです。また、solrconfig.xmlThe Highlighter plugin configuration sectionを見てみても、関連する事は書かれて無さそうです。
しょうがないのでSolr3.1のsolrconfig.xmlを見ることにしましょう。

HighlightComponentの設定の中にencoderというそれっぽい要素がある事が分かりますね。これのclass属性からorg.apache.solr.highlight.HtmlEncoderのソースを見てみる事にしましょう。Solrのソースはこちらからダウンロード出来ます。


public class HtmlEncoder extends HighlightingPluginBase implements SolrEncoder {

public Encoder getEncoder(String fieldName, SolrParams params) {
return new SimpleHTMLEncoder();
}
 ・・・以下略・・・

内容は非常に簡単で、Encoderを実装したSimpleHTMLEncoderというクラスをnewして返しているだけです。
そこで、org.apache.lucene.search.highlight.SimpleHTMLEncoderも見てみましょう。今度はLuceneのソースが必要で、こちらからダウンロード出来ます。対象のソースはcontribのhighlighterの中にあるので注意して下さい。


public class SimpleHTMLEncoder implements Encoder
{
public SimpleHTMLEncoder()
{
}

public String encodeText(String originalText)
{
return htmlEncode(originalText);
}
 ・・・続きます・・・

上記のencodeTextメソッドはhtmlEncodeを呼び出していますね。ちょっと長いですが中身を見てみましょう。


public final static String htmlEncode(String plainText)
{
if (plainText == null || plainText.length() == 0)
{
return "";
}

StringBuilder result = new StringBuilder(plainText.length());

for (int index=0; index':
result.append(">");
break;

default:
if (ch < 128)
{
result.append(ch);
}
else
{
result.append("&#").append((int)ch).append(";");
}
}
}

return result.toString();
}
}

最後の方にありましたね。StringBuilderにappend((int)ch)とやっている箇所で数値文字参照は作られています。defaultの部分でASCII文字と非ASCII文字を分岐するのをやめて、全てASCII文字と同じ扱いにすれば解決できそうです。このSimpleHTMLEncoderを修正してビルドしてjarを差し替える、という方法でも解決できますが、Solrは折角コンポーネントを差し替えやすい柔軟な設計になっているので、新しくクラスを作成してsolrconfigで切り替えるようにしましょう。

今回はHtmlEncoderを継承して作ってみました。


package org.apache.solr.highlight;

import org.apache.lucene.search.highlight.Encoder;
import org.apache.solr.common.params.SolrParams;

public class HtmlEncoder2 extends HtmlEncoder {
public Encoder getEncoder(String fieldName, SolrParams params) {
return new Encoder() {
public String encodeText(String originalText) {
if (originalText == null || originalText.length() == 0) {
return "";
}
StringBuilder result = new StringBuilder(originalText.length());
for (int index=0; index':
result.append(">");
break;
default:
result.append(ch);
}
}
return result.toString();
}
};
}
}

encodeTextの中身はSimpleHTMLEncoderからそのまま持ってきて、数値文字参照の部分だけを修正しています。githubにコードとjarを公開していますので、そちらをご利用下さい。

まずダウンロードしたjarを任意の場所に配置します。ここではcoreの直下にlibディレクトリを作成してその中に配置するものとします。
次にsolrconfig.xmlを修正します。

最初の方にlib属性が並んでいる箇所がありますので、そこに


<lib dir="lib/" />

を追記します。
そして、先ほどそれっぽい要素として紹介したencoder属性のclass要素を新しいクラスに差し替えます。



これで完成です。
solrconfigをリロードして、先ほどと同じ検索で試してみます。

うまくいくようになりました♪
用途が非常に限定的ですが、同じような問題のある方は是非使ってみて下さい。
https://github.com/shinobu-aoki/Solr-CustomHtmlEncoder