2010-12-30 7 views
1

Ich versuche, die Textmarker-Klasse von Lucene korrekt mit Token aus Solrs WordDelimiterFilter zu arbeiten. Es funktioniert 90% der Zeit, aber wenn der passende Text enthält ein ‚‘ wie ‚1500‘, der Ausgang ist falsch:Solr WordDelimiterFilter + Lucene Textmarker

Erwartet: ‚test dieser‘

Beobachtet: ' Test 1 dieses

ich bin derzeit nicht sicher, ob es Highlighter verwirren die tokenization die Rekombination oder WordDelimiterFilter vermasselt ist, aber etwas ist unglücklich. Hier sind die entsprechenden Abhängigkeiten von meinem pom:

org.apache.lucene lucene-Kern 2.9.3 jar org.apache.lucene lucene-Highlighter 2.9.3 jar kompilieren kompilieren org.apache.solr solr-Kern 1.4.0 jar kompilieren

Und hier ist eine einfache JUnit-Testklasse demonstriert das Problem:

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); 
     } 
    } 
} 

Wer irgendwelche Ideen oder Anregungen?

Antwort

2

Nach weiteren Untersuchungen scheint dies ein Fehler im Lucene Highlighter-Code zu sein. Wie Sie hier sehen können:

public class TokenGroup { 

    ... 

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

    ... 

Der Code versucht, wenn eine Gruppe von Token unterscheidet indem überprüft wird, um festzustellen, ob der Offset-Start größer als das vorherige Ende ist ausgeglichen. Das Problem mit diesem Ansatz wird durch dieses Problem veranschaulicht. Wenn Sie durch die Token zu dem Schritt, würden Sie sehen, dass sie sich wie folgt dar:

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

Hieraus kann man sehen, dass das dritte Token nach dem Ende des zweiten beginnt, aber der vierte beginnt am selben Ort wie der Zweite. Das beabsichtigte Ergebnis wäre, die Tokens 2, 3 und 4 zu gruppieren, aber bei dieser Implementierung wird Token 3 als von 2 getrennt gesehen, so dass 2 von selbst erscheint, dann 3 und 4 gruppiert werden, wobei dieses Ergebnis zurückbleibt:

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

Ich bin mir nicht sicher, dass dies ohne 2 Durchgänge erreicht werden kann, eine, um alle Indizes zu erhalten, und eine zweite, um sie zu kombinieren. Ich bin mir auch nicht sicher, was die Implikationen außerhalb dieses speziellen Falles wären. Hat jemand hier irgendwelche Ideen?

EDIT

Hier ist die letzte Quellcode kam ich mit. Es wird die Dinge richtig gruppieren. Es scheint auch viel einfacher als die Lucene Highlighter-Implementierung, aber zugegebenermaßen nicht mit verschiedenen Ebenen des Scoring, da meine Anwendung nur ein Ja/Nein, ob ein Fragment des Textes markiert wird benötigt. Es ist auch erwähnenswert, dass ich ihre QueryScorer verwenden, um die Textfragmente zu bewerten, die die Schwäche hat, Term-orientierte statt Phrase orientiert zu sein, was bedeutet, dass die Suchzeichenfolge "Grammatik oder Rechtschreibung" mit einer Hervorhebung enden würde, die ungefähr so ​​aussieht: grammatikalische oder Rechtschreibung "als die oder würde höchstwahrscheinlich von Ihrem Analysator fallen gelassen werden. Wie auch immer, hier ist meine Quelle:

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

Haben Sie einen Patch an die Mailingliste gesendet? Ich habe gerade diesen Fehler auch gesehen und war mir nicht sicher, was es war. Wäre super, um es zu reparieren. –

+0

Entschuldigung Dan, nein, habe ich nicht. Der Grund ist, ich kann nicht herausfinden, wo. Ich kann nicht einmal das Bugzilla für Lucene finden, geschweige denn Lucene Highlighter. Hast du die Adresse der Mailingliste? Bitte poste hier und wenn ich es als nächstes sehe, werde ich sehen, ob ich diesen Vorschlag einreichen kann. – Lucas

+0

https://issues.apache.org/jira/browse/Lucene und https://issues.apache.org/jira/browse/SOLR - sieht so aus als wäre dies vor drei Wochen adressiert worden: https: //issues.apache. org/jira/browse/LUCENE-2874 –

0

Hier ist eine mögliche Ursache. Ihr Textmarker muss denselben für die Suche verwendeten Analyzer verwenden. IIUC, Ihr Code verwendet einen Standardanalysator für die Hervorhebung, obwohl er einen speziellen Analysator zum Analysieren der Abfrage verwendet. Ich glaube, du musst den Fragmentierer ändern, um mit deinem spezifischen TokenStream zu arbeiten.

+0

Der Code verwendet den gleichen Analysator in beiden Fällen. Sie können oben sehen, dass es tatsächlich von der gleichen Hilfsmethode (getAnalyzer) sowohl im QueryParser-Konstruktor (für den Abfrage-Tokenizer) als auch in der textmarker.getBestFragments (für den Text-Tokenizer) erstellt wird. Dies funktioniert außer im Fall mit dem, wie diese Frage anzeigt. Ich denke, ich habe das Problem gefunden und es ist ein Fehler in Lucene Highlighter. Ich werde die Antwort unten posten. – Lucas

Verwandte Themen