« QueryParser の field の謎 | メイン | システムの負荷チェック (Linux, Fedora core 6) »

tag の field は Analyzer を使わず body の field には CJKAnalyzer を使う

昨日書いた QueryParser の field の謎の件は簡単に解決しそうにない。 多少工夫してみたのだが、 どうあっても Analyzer を呼び出してしまうので、 Analyzer を呼ばないようにするのは諦めた。 要するに Analyzer が何もしなければいいのだから、 何もしない Analyzer を用意して、それを指定すればいいのでは。 という逆転の発想である。

試しに SimpleAnalyzer とか StandardAnalyzer を使ってみたのだが、 殆どうまくいく。 しかし、レアケースではあるが、 例えば文字列に white space が入っているといけない。 space は区切りと解釈してしまうからである。 例えば "DS Lite" は、"DS" と "Lite" の2つの Token に分かれてしまう。 tag に登録されている String は "DS Lite" だからうまくない。

何もしない Analyzer というのはないのか。 なさそうな気がしたので、 作った方が早いだろと思って作ってみた。 まず、何もしない Tokenizer を作る。

package jp.co.crm.jirosearch.lucene;

import java.io.Reader;

import org.apache.lucene.analysis.CharTokenizer;

public class NoActionTokenizer extends CharTokenizer {

	public NoActionTokenizer(Reader input) {
		super(input);
	}

	@Override
	protected boolean isTokenChar(char c) {
		return true;
	}
}

isTokenChar(char c) が必ず true を返す。 ということは、この input には切れ目がなく、 全てが有効な文字ということだから、 CharTokenizer は与えられた文字列をそのまま単独の Token にしてくれるはずだ。 次に、これを使った Analyzer を作る。

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;

public class NoAnalyzer extends Analyzer {

	@Override
	public TokenStream tokenStream(String fieldName, Reader reader) {
		return new NoActionTokenizer(reader);
	}
}

この NoAnalyzer を Analyzer として指定すれば、 与えた文字列を空白等も全部ひっくるめてそのまま1つの Token にするから、 結果的には Analyzer を呼ばなかったのと同じことになる。

これでタグに対して何もしない Analyzer を指定する準備が整った。 さて、タグに対してこの NoAnalyzer を指定し、 body には CJKAnalyzer を指定したい、 というような場合はどうすればよいか。

複数の Field に対して検索をかけたい場合は、 org.apache.lucene.queryParser.MultiFieldQueryParser を使う。 このクラスには、いくつか static な method が用意されている。 例えば、

static Query parse(String[] queries, String[] fields, Analyzer analyzer);

このように、 フィールドごとに query を指定することはできるのだが、 analyzer は一つしか指定できない。 Field によって異なる Analyzer を使い分けたい場合は、 org.apache.lucene.analysis.PerFieldAnalyzerWrapper を使ってラップすればいい。 このクラスには、 次のようなメソッドが用意されている。

void addAnalyzer(String fieldName, Analyzer analyzer);

手順としては、 PerFieldAnalyzerWrapper のインスタンスをまず作成しておき、 その後、 addAnalyzer メソッドを呼び出して、 デフォルトではない Analyzer を使いたいフィールドを指定することになる。

さて、これはこれでいいのだが、 JiroSearch は Spring Framework を使っているから、 フィールドに対する Analyzer の指定は applicationContext.xml に書きたいところだ。 ところが、 PerFieldAnalyzerWrapper には setter も getter もないから、 Analyzer を指定するのが面倒である。 ということで、 もともと小さいクラスだし、 勝手に setter、getter を追加した。

public class PerFieldAnalyzerWrapper extends Analyzer {
    private Analyzer defaultAnalyzer;

    private Map<String, Analyzer> analyzerMap = new HashMap<String, Analyzer>();

    public PerFieldAnalyzerWrapper() {
        super();
        this.defaultAnalyzer = new SimpleAnalyzer();
    }
    
    /**
     * Constructs with default analyzer.
     * 
     * @param defaultAnalyzer
     *            Any fields not specifically defined to use a different
     *            analyzer will use the one provided here.
     */
    public PerFieldAnalyzerWrapper(Analyzer defaultAnalyzer) {
        this.defaultAnalyzer = defaultAnalyzer;
    }

    /**
     * Defines an analyzer to use for the specified field.
     * 
     * @param fieldName
     *            field name requiring a non-default analyzer
     * @param analyzer
     *            non-default analyzer to use for field
     */
    public void addAnalyzer(String fieldName, Analyzer analyzer) {
        analyzerMap.put(fieldName, analyzer);
    }

    public TokenStream tokenStream(String fieldName, Reader reader) {
        Analyzer analyzer = analyzerMap.get(fieldName);
        if (analyzer == null) {
            analyzer = defaultAnalyzer;
        }

        return analyzer.tokenStream(fieldName, reader);
    }

    public String toString() {
        return "PerFieldAnalyzerWrapper(" + analyzerMap + ", default="
                + defaultAnalyzer + ")";
    }

    public Map<String, Analyzer> getAnalyzerMap() {
        return analyzerMap;
    }

    public void setAnalyzerMap(Map<String, Analyzer> analyzerMap) {
        this.analyzerMap = analyzerMap;
    }

    public void setDefaultAnalyzer(Analyzer defaultAnalyzer) {
        this.defaultAnalyzer = defaultAnalyzer;
    }
}

このように手を入れた class を使えば、 Spring の設定を次のように書くことができる。

  <bean id="analyzer"
    class="jp.co.crm.jirosearch.lucene.PerFieldAnalyzerWrapper">
    <property name="defaultAnalyzer"><ref local="simpleAnalyzer"/></property>
    <property name="analyzerMap">
      <map>
        <entry key="body"><ref local="cjkAnalyzer"/></entry>
        <entry key="tag"><ref local="simpleAnalyzer"/></entry>
      </map>
    </property>
  </bean>

このようにして、 tag を割り当てた field は Analyzer を通さずに検索し、 body の field に対しては、CJKAnalyzer を使う、 という処理になった。

トラックバック

このエントリーのトラックバックURL:
http://blog.crm.co.jp/mt-cgi/mt-tb.cgi/152

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

開発製品

jirologos.gif

About

2006年12月19日 15:08に投稿されたエントリのページです。

ひとつ前の投稿は「QueryParser の field の謎」です。

次の投稿は「システムの負荷チェック (Linux, Fedora core 6)」です。

他にも多くのエントリがあります。メインページアーカイブページも見てください。

Powered by
Movable Type