2010-12-30 2 views
1

Я пытаюсь получить класс Highlighter от Lucene для правильной работы с токенами, поступающими из WordDelimiterFilter от Solr. Он работает 90% времени, но если текст соответствия содержит «» такие, как „1500“ выход неправилен:Solr WordDelimiterFilter + Lucene Highlighter

Ожидаемое: „тест это“ Наблюдаемые

: ' тест 1 это

Я в настоящее время не уверен, является ли он Highlighter портя рекомбинации или WordDelimiterFilter портя лексемизацию но что-то не устраивает. Вот соответствующие зависимости от моего ПОМ:

org.apache.lucene Lucene-жильный 2.9.3 баночка компилировать org.apache.lucene Lucene-фломастер 2.9.3 баночка составить org.apache.solr ГУМЗ-ядро 1.4.0 банку составить

А вот простой JUnit тестовый класс, демонстрирующий проблему:

package test.lucene; 


import static org.junit.Assert.assertEquals; 
import static org.junit.Assert.assertTrue; 


import java.io.IOException; 
import java.io.Reader; 
import java.util.HashMap; 


import org.apache.lucene.analysis.Analyzer; 
import org.apache.lucene.analysis.TokenStream; 
import org.apache.lucene.queryParser.ParseException; 
import org.apache.lucene.queryParser.QueryParser; 
import org.apache.lucene.search.Query; 
import org.apache.lucene.search.highlight.Highlighter; 
import org.apache.lucene.search.highlight.InvalidTokenOffsetsException; 
import org.apache.lucene.search.highlight.QueryScorer; 
import org.apache.lucene.search.highlight.SimpleFragmenter; 
import org.apache.lucene.search.highlight.SimpleHTMLFormatter; 
import org.apache.lucene.util.Version; 
import org.apache.solr.analysis.StandardTokenizerFactory; 
import org.apache.solr.analysis.WordDelimiterFilterFactory; 
import org.junit.Test; 


public class HighlighterTester { 
    private static final String PRE_TAG = "<b>"; 
    private static final String POST_TAG = "</b>"; 

    private static String[] highlightField(Query query, String fieldName, String text) 
      throws IOException, InvalidTokenOffsetsException { 
     SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(PRE_TAG, POST_TAG); 
     Highlighter highlighter = new Highlighter(formatter, new QueryScorer(query, fieldName)); 
     highlighter.setTextFragmenter(new SimpleFragmenter(Integer.MAX_VALUE)); 
     return highlighter.getBestFragments(getAnalyzer(), fieldName, text, 10); 
    } 

    private static Analyzer getAnalyzer() { 
     return new Analyzer() { 
      @Override 
      public TokenStream tokenStream(String fieldName, Reader reader) { 
       // Start with a StandardTokenizer 
       TokenStream stream = new StandardTokenizerFactory().create(reader); 

       // Chain on a WordDelimiterFilter 
       WordDelimiterFilterFactory wordDelimiterFilterFactory = new WordDelimiterFilterFactory(); 
       HashMap<String, String> arguments = new HashMap<String, String>(); 
       arguments.put("generateWordParts", "1"); 
       arguments.put("generateNumberParts", "1"); 
       arguments.put("catenateWords", "1"); 
       arguments.put("catenateNumbers", "1"); 
       arguments.put("catenateAll", "0"); 
       wordDelimiterFilterFactory.init(arguments); 

       return wordDelimiterFilterFactory.create(stream); 
      } 
     }; 
    } 

    @Test 
    public void TestHighlighter() throws ParseException, IOException, InvalidTokenOffsetsException { 
     String fieldName = "text"; 
     String text = "test 1,500 this"; 
     String queryString = "1500"; 
     String expected = "test " + PRE_TAG + "1,500" + POST_TAG + " this"; 

     QueryParser parser = new QueryParser(Version.LUCENE_29, fieldName, getAnalyzer()); 
     Query q = parser.parse(queryString); 
     String[] observed = highlightField(q, fieldName, text); 
     for (int i = 0; i < observed.length; i++) { 
      System.out.println("\t" + i + ": '" + observed[i] + "'"); 
     } 
     if (observed.length > 0) { 
      System.out.println("Expected: '" + expected + "'\n" + "Observed: '" + observed[0] + "'"); 
      assertEquals(expected, observed[0]); 
     } 
     else { 
      assertTrue("No matches found", false); 
     } 
    } 
} 

Кто-нибудь есть какие-либо идеи или предложения?

ответ

2

После дальнейшего изучения это, похоже, является ошибкой в ​​коде Lucene Highlighter. Как вы можете увидеть здесь:

public class TokenGroup { 

    ... 

    protected boolean isDistinct() { 
     return offsetAtt.startOffset() >= endOffset; 
    } 

    ... 

код пытается определить, является ли группа маркеров отличается от проверки, чтобы увидеть, если начальное смещение больше, чем в предыдущем смещение конца. Проблема с этим подходом иллюстрируется этой проблемой. Если вы должны были пройти через лексемы, вы бы увидели, что они заключаются в следующем:

0-4: 'test', 'test' 
5-6: '1', '1' 
7-10: '500', '500' 
5-10: '1500', '1,500' 
11-15: 'this', 'this' 

Из этого вы можете видеть, что третий маркер начинается после того, как в конце второго, но четвёртая начинается то же самое место, как второй. Предполагаемый результат должен состоять в том, чтобы группировать токены 2, 3 и 4, но в соответствии с этой реализацией маркер 3 рассматривается как отдельный от 2, поэтому 2 появляется сам по себе, затем 3 и 4 группируются, оставляя этот результат:

Expected: 'test <b>1,500</b> this' 
Observed: 'test 1<b>1,500</b> this' 

Я не уверен, что это может быть выполнено без 2-х проходов, чтобы получить все индексы и секунду, чтобы их объединить. Кроме того, я не уверен, что последствия будут за пределами этого конкретного случая. Кто-нибудь есть идеи здесь?

EDIT

Вот окончательный исходный код я придумал. Он будет правильно группировать вещи. Он также кажется намного проще, чем реализация Lucene Highlighter, но, по общему признанию, не обрабатывает разные уровни подсчета очков, так как моему приложению требуется только «да/нет» относительно того, выделяется ли фрагмент текста. Его также стоит отметить, что я использую свой QueryScorer для оценки фрагментов текста, который имеет слабость от ориентированного на термины, а не от фраз-ориентированного, что означает, что строка поиска «грамматическая или орфографическая» в конечном итоге будет выделяться, что выглядит примерно так »грамматический или орфографический ", так как он или скорее всего будет удален вашим анализатором. В любом случае, вот мой источник:

public TextFragments<E> getTextFragments(TokenStream tokenStream, 
     String text, 
     Scorer scorer) 
     throws IOException, InvalidTokenOffsetsException { 
    OffsetAttribute offsetAtt = (OffsetAttribute) tokenStream.addAttribute(OffsetAttribute.class); 
    TermAttribute termAtt = (TermAttribute) tokenStream.addAttribute(TermAttribute.class); 
    TokenStream newStream = scorer.init(tokenStream); 
    if (newStream != null) { 
     tokenStream = newStream; 
    } 

    TokenGroups tgs = new TokenGroups(); 
    scorer.startFragment(null); 
    while (tokenStream.incrementToken()) { 
     tgs.add(offsetAtt.startOffset(), offsetAtt.endOffset(), scorer.getTokenScore()); 
     if (log.isTraceEnabled()) { 
      log.trace(new StringBuilder() 
        .append(scorer.getTokenScore()) 
        .append(" ") 
        .append(offsetAtt.startOffset()) 
        .append("-") 
        .append(offsetAtt.endOffset()) 
        .append(": '") 
        .append(termAtt.term()) 
        .append("', '") 
        .append(text.substring(offsetAtt.startOffset(), offsetAtt.endOffset())) 
        .append("'") 
        .toString()); 
     } 
    } 

    return tgs.fragment(text); 
} 

private class TokenGroup { 
    private int startIndex; 
    private int endIndex; 
    private float score; 

    public TokenGroup(int startIndex, int endIndex, float score) { 
     this.startIndex = startIndex; 
     this.endIndex = endIndex; 
     this.score = score; 
    } 
} 

private class TokenGroups implements Iterable<TokenGroup> { 
    private List<TokenGroup> tgs; 

    public TokenGroups() { 
     tgs = new ArrayList<TokenGroup>(); 
    } 

    public void add(int startIndex, int endIndex, float score) { 
     add(new TokenGroup(startIndex, endIndex, score)); 
    } 

    public void add(TokenGroup tg) { 
     for (int i = tgs.size() - 1; i >= 0; i--) { 
      if (tg.startIndex < tgs.get(i).endIndex) { 
       tg = merge(tg, tgs.remove(i)); 
      } 
      else { 
       break; 
      } 
     } 
     tgs.add(tg); 
    } 

    private TokenGroup merge(TokenGroup tg1, TokenGroup tg2) { 
     return new TokenGroup(Math.min(tg1.startIndex, tg2.startIndex), 
       Math.max(tg1.endIndex, tg2.endIndex), 
       Math.max(tg1.score, tg2.score)); 
    } 

    private TextFragments<E> fragment(String text) { 
     TextFragments<E> fragments = new TextFragments<E>(); 

     int lastEndIndex = 0; 
     for (TokenGroup tg : this) { 
      if (tg.startIndex > lastEndIndex) { 
       fragments.add(text.substring(lastEndIndex, tg.startIndex), textModeNormal); 
      } 
      fragments.add( 
        text.substring(tg.startIndex, tg.endIndex), 
        tg.score > 0 ? textModeHighlighted : textModeNormal); 
      lastEndIndex = tg.endIndex; 
     } 

     if (lastEndIndex < (text.length() - 1)) { 
      fragments.add(text.substring(lastEndIndex), textModeNormal); 
     } 

     return fragments; 
    } 

    @Override 
    public Iterator<TokenGroup> iterator() { 
     return tgs.iterator(); 
    } 
} 
+0

Вы отправили патч в список рассылки? Я просто столкнулся с этой ошибкой и не был уверен, что это. Было бы замечательно, чтобы это было исправлено. –

+0

Извините, Дэн, нет. Причина в том, что я не могу понять, где. Я даже не могу найти bugzilla для Lucene, не говоря уже о Lucene Highlighter. У вас есть адрес списка рассылки? Пожалуйста, напишите здесь, и когда я увижу его дальше, я увижу, могу ли я представить это предложение. – Lucas

+0

https://issues.apache.org/jira/browse/Lucene и https://issues.apache.org/jira/browse/SOLR - похоже, что это было рассмотрено три недели назад: https: //issues.apache. орг/JIRA/просмотр/Lucene-2874 –

0

Вот возможная причина. Ваш маркер должен использовать тот же анализатор, который используется для поиска. IIUC. В вашем коде используется анализатор по умолчанию для выделения, хотя он использует специализированный анализатор для синтаксического анализа запроса. Я считаю, что вам нужно изменить Fragmenter для работы с вашим TokenStream.

+0

Код использует тот же анализатор в обоих случаях. Вы можете видеть выше, что он фактически создан тем же вспомогательным методом (getAnalyzer) как в конструкторе QueryParser (для токенизатора запроса), так и в файле highlighter.getBestFragments (для текстового токенизатора). Это работает, за исключением случая с, как указывает этот вопрос. Я действительно думаю, что нашел проблему, и это ошибка в Lucene Highlighter. Я отправлю ответ ниже. – Lucas

Смежные вопросы