Skip to content

Instantly share code, notes, and snippets.

@elw00d
Last active February 5, 2018 10:18
Show Gist options
  • Save elw00d/f27cf9fe6a8aad6b3761 to your computer and use it in GitHub Desktop.
Save elw00d/f27cf9fe6a8aad6b3761 to your computer and use it in GitHub Desktop.
SOLR custom token filter
package ru.dz;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Добавляет к каждому терму дубликат, записанный в другой раскладке. Регистр символов сохраняется.
* Например, на входе термы: Шзрщту, vfvf, папа
* На выходе: Шзрщту, Iphone, vfvf, мама, папа, gfgf
* *
* @author igor.kostromin
* 8/13/2014
*/
public class RusEnKeyboardLayoutFilter extends TokenFilter {
protected final char[] latins = {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']',
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', 'z', 'x', 'c', 'v',
'b', 'n', 'm', ',', '.'};
protected final char[] cyrillics = {'й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х',
'ъ', 'ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э', 'я', 'ч', 'с', 'м',
'и', 'т', 'ь', 'б', 'ю'};
private final Map<Character, Character> latin2cyrMap;
private final Map<Character, Character> cyr2latinMap;
private final PositionIncrementAttribute posIncAttr = addAttribute(PositionIncrementAttribute.class);
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private State state;
public RusEnKeyboardLayoutFilter(TokenStream input) {
super(input);
latin2cyrMap = new HashMap<>();
cyr2latinMap = new HashMap<>();
for (int i = 0; i < latins.length; i++) {
latin2cyrMap.put(latins[i], cyrillics[i]);
latin2cyrMap.put(Character.toUpperCase(latins[i]), Character.toUpperCase(cyrillics[i]));
cyr2latinMap.put(cyrillics[i], latins[i]);
cyr2latinMap.put(Character.toUpperCase(cyrillics[i]), Character.toUpperCase(latins[i]));
}
}
@Override
public boolean incrementToken() throws IOException {
if (state != null) {
restoreState(state);
// Этот вызов нужен для того, чтобы у оригинального терма не увеличивалась позиция
// Т.к. при каждом последующем вызове incrementToken() позиция увеличивается на единицу
// (см код KeywordRepeatFilter)
posIncAttr.setPositionIncrement(0);
state = null;
return true;
}
if (input.incrementToken()) {
state = captureState();
// Переводим строку в другую раскладку
char[] buffer = termAtt.buffer();
if (buffer.length > 0) {
for (int i = 0; i < buffer.length; i++) {
if (latin2cyrMap.containsKey(buffer[i]))
buffer[i] = latin2cyrMap.get(buffer[i]);
else if (cyr2latinMap.containsKey(buffer[i]))
buffer[i] = cyr2latinMap.get(buffer[i]);
}
}
return true;
}
return false;
}
@Override
public void reset() throws IOException {
super.reset();
state = null;
}
}
package ru.dz;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.util.TokenFilterFactory;
import java.util.Map;
/**
* @author igor.kostromin
* 8/13/2014
*/
public class RusEnKeyboardLayoutFilterFactory extends TokenFilterFactory {
public RusEnKeyboardLayoutFilterFactory(Map<String, String> args) {
super(args);
}
@Override
public TokenStream create(TokenStream input) {
return new RusEnKeyboardLayoutFilter(input);
}
}
<!-- piece of schema.xml -->
<fieldType name="text_ru" class="solr.TextField" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<!-- Чтобы оригинальные токены не терялись после стемминга. Например "кровати" будут преобразованы стеммером в "кровать".
Но мы хотим, чтобы и "кровати" тоже были в индексе, и при поиске "кровати" тоже нашлись, а не только "кровать".
Часто бывает нужно, чтобы совпадение в оригинальной форме приводило к более высокому score товара. -->
<filter class="solr.KeywordRepeatFilterFactory"/>
<filter class="solr.HunspellStemFilterFactory" dictionary="ru_RU.dic" affix="ru_RU.aff" ignoreCase="true" />
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ru.txt" format="snowball" />
<!-- Чтобы удалить совпадающие токены (которые появились после работы KeywordRepeatFilterFactory) -->
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="ru.dz.RusEnKeyboardLayoutFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordRepeatFilterFactory"/>
<!-- Дополнительно к HunspellStemFilterFactory используем RussianLightStemFilterFactory для того, чтобы
слова типа "айфона" (которых нет в словаре ru_RU.dic) стеммились к "айфон" -->
<filter class="solr.RussianLightStemFilterFactory"/>
<filter class="solr.HunspellStemFilterFactory" dictionary="ru_RU.dic" affix="ru_RU.aff" ignoreCase="true" />
<filter class="solr.StopFilterFactory" ignoreCase="true" words="lang/stopwords_ru.txt" format="snowball" />
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
</analyzer>
</fieldType>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment