Luceneで日本語ハイライト (その2)

日本語のハイライト処理を行うと TokenGroup.addToken で ArrayIndexOutOfBoundsException がスローされるのを調べてみた。これが発生するのは、1つの TokenGroup に沢山の Token が入ることが原因だ。デフォルトでは50 で変更不可だ。そして、Token を別の TokenGroup で処理させるには TokenGroup.isDistinct で true を返せれば良い。色々考えて、最初は次のように、間のテキストを抜くように良いと考えた(ただし、ヒットした単語は除かないようにする必要はある)。

token[0] : テス(0, 2)
token[1] : スト(1, 3)
token[2] : ト構(2, 4)
token[3] : 構造(3, 5)
token[4] : 造体(4, 6)
...

変換後

token[0] : テス(0, 2)
token[2] : ト構(2, 4)
token[4] : 造体(4, 6)
...

つまり、token をつなげたら1つの文になる感じだ。これで、テストコードでは、うまく行ったのだが、実際のインデックスに対してはどうもだめだ。同じエラーが出る。予定では、1つのTokenで、1回のハイライトチェックを行うように進むはずだったのだが、うまく行かない。

そして、TokenGroup.isDistinct のコードをよくよく読むと、

TokenGroup内の末尾のOffset < 引数Tokenの最初のOffset

が true か false かを返していた... orz。最初から勘違いをしていたのだが、つまり直前の Token の末尾と 次の Token の先頭のOffsetが同じではだめということだ。テストがうまく行ったのは、単にTokenの数が少なくなって連続の許容範囲が広がっただけだ。また長くすれば再現した。なので、先ほどの方法ではだめだ。ここがBugなのでは?と考えなくもないが、

hogehogefoobar

という単語が、何かの理由で hogehoge foobar の2つの Token に分かれた時に、TokenGroup では1つに扱いたいという事なのかもしれない。(つまり、あいまい検索のような場合や、ワイルドカードを使う場合にここが、生きるのかもしれない。その辺は、確認してないのでわからない)。そもそも、ここがだめな理由が説明しにくいから、Bug登録も出来ないだろう。


ここに来て、最初の方法ではだめだということがわかった(あたりまえだ...。前提が間違っているんだから。)。色々悩んだが、ちょっと発想を変えてみた。

今やりたいのは、検索結果のサマリで、キーワードをハイライトにさせたい。つまり、Indexを作成するわけではなく、極端な話、キーワードだけ取り出せれば良い。また、Highlighter.getBestFragment のコードを見ると、TokenGroup は、見つかった1つのグループの最初と最後のOffsetの位置が重要であって、文字列の切り出しは、その辺の情報を元に別途行っている。また、TokenGroup に含まれない文字(空白とか) もちゃんと追加している。ということは、ハイライトされている単語を含んだ文字を Formatter にうまいこと渡せれば、その他の文字はてきとーでも良い(ハイライトはされないが、文字列としてはちゃんと切り出される)はずだ。

この辺を考えて、TokenStream という Token の切り出しを行うクラスのラッパークラスを作成し、そいつが スコア がゼロのやつは適当に捨てていく。スコアがあるやつに関しては、今までどおりの処理をしておく。そして、 TokenGroup のバッファがあふれないように TokenGroup.isDistinct で true を返すことができるように Token を返す。さっきの場合で、「構造」がハイライトされる場合は、次のようなものを返せればよいはずだ。

token[0] : テス(0, 2)
token[3] : 構造(3, 5)
...

こんな風な Token を渡していけば、「構造」という単語はうまくハイライトされ他に削った文字は、Highlighter の方で自動的に補完してくれる。そして、こんな動作をする TokenStream を実装してテストしたところ、予定どおりちゃんと動いているようだ。また、別のエラーが出たときはそのときに考えるとしよう。何にしても、ライブラリが、そのまま利用できるのが嬉しい。