Lucene8:検索ヒット時に元文書該当箇所をハイライトする

というものを作成した。

特に検索時に行う必要はない。このユースケースとしては以下だ。

  • ユーザが検索する
  • ヒットしたいくつかが一覧される
  • ユーザはそのうちの一つを選択する
  • この時に初めて元文書をヒット位置と共に表示する。

他で見られるやり方では、検索ヒットしたすべての文書についてハイライトを行い、そのハイライト済データを保持していなければならない。これは無駄である。

ここでは、以下を決めればハイライト済文書を入手できる。

  • 検索に使用したクエリ
  • ハイライトしたいフィールドのアナライザ
  • ハイライトしたいフィールドのコンテンツ
public class RlHighlighter {

  final Formatter formatter;

  /** 
   * フォーマッタを指定する
   * @param formatter フォーマッタ
   */
  public RlHighlighter(Formatter formatter) {
    this.formatter = formatter;
  }

  public RlHighlighter(String color) {
    this.formatter = new SimpleHTMLFormatter(
      "<span style=\"background-color:" + color + "\">",
      "</span>");
  }

  /**
   * クエリ、アナライザ、コンテンツ文字列を指定してハイライト済テキストを得る
   * @param query クエリ、
   * @param analyzer 該当フィールド用アナライザ
   * @param contents 該当フィールドのコンテンツ文字列
   * @return ハイライト済テキスト
   * @throws Exception
   */
  Fragments highlight(Query query, Analyzer analyzer, String contents) throws Exception  {
    QueryScorer qs =new QueryScorer(query) ;
    Highlighter hg = new Highlighter(formatter, qs);  
    hg.setTextFragmenter(new SimpleSpanFragmenter(qs, -1));
    return new Fragments(hg.getBestTextFragments(
       analyzer.tokenStream(null, contents), 
       contents, 
       false,
       10 
    ));
  }

  public static class Fragments {
    public final TextFragment[]fragments;
    Fragments(TextFragment[]fragments) {
      this.fragments = fragments;
    }
    @Override
    public String toString() {
      return Arrays.stream(fragments).map(f->f.toString()).collect(Collectors.joining());
    }
  }
}