Highlighting Usage

edit

Allows to highlight search results on one or more fields. The implementation uses either the lucene highlighter, fast-vector-highlighter or postings-highlighter.

See the Elasticsearch documentation on highlighting for more detail.

Fluent DSL example

edit
s => s
.Query(q => q
    .Match(m => m
        .Field(f => f.Name.Suffix("standard"))
        .Query("Upton Sons Shield Rice Rowe Roberts")
    )
)
.Highlight(h => h
    .PreTags("<tag1>")
    .PostTags("</tag1>")
    .Encoder("html")
    .Fields(
        fs => fs
            .Field(p => p.Name.Suffix("standard"))
            .Type("plain")
            .ForceSource()
            .FragmentSize(150)
            .Fragmenter(HighlighterFragmenter.Span)
            .NumberOfFragments(3)
            .NoMatchSize(150),
        fs => fs
            .Field(p => p.LeadDeveloper.FirstName)
            .Type(HighlighterType.Fvh)
            .PreTags("<name>")
            .PostTags("</name>")
            .BoundaryMaxScan(50)
            .PhraseLimit(10)
            .HighlightQuery(q => q
                .Match(m => m
                    .Field(p => p.LeadDeveloper.FirstName)
                    .Query("Kurt Edgardo Naomi Dariana Justice Felton")
                )
            ),
        fs => fs
            .Field(p => p.State.Suffix("offsets"))
            .Type(HighlighterType.Postings)
            .PreTags("<state>")
            .PostTags("</state>")
            .HighlightQuery(q => q
                .Terms(t => t
                    .Field(f => f.State.Suffix("offsets"))
                    .Terms(
                        StateOfBeing.Stable.ToString().ToLowerInvariant(),
                        StateOfBeing.BellyUp.ToString().ToLowerInvariant()
                    )
                )
            )
    )
)

Object Initializer syntax example

edit
new SearchRequest<Project>
{
    Query = new MatchQuery
    {
        Query = "Upton Sons Shield Rice Rowe Roberts",
        Field = "name.standard"
    },
    Highlight = new Highlight
    {
        PreTags = new[] { "<tag1>" },
        PostTags = new[] { "</tag1>" },
        Encoder = "html",
        Fields = new Dictionary<Field, IHighlightField>
        {
            {
                "name.standard", new HighlightField
                {
                    Type = HighlighterType.Plain,
                    ForceSource = true,
                    FragmentSize = 150,
                    Fragmenter = HighlighterFragmenter.Span,
                    NumberOfFragments = 3,
                    NoMatchSize = 150
                }
            },
            {
                "leadDeveloper.firstName", new HighlightField
                {
                    Type = "fvh",
                    PhraseLimit = 10,
                    BoundaryMaxScan = 50,
                    PreTags = new[] { "<name>" },
                    PostTags = new[] { "</name>" },
                    HighlightQuery = new MatchQuery
                    {
                        Field = "leadDeveloper.firstName",
                        Query = "Kurt Edgardo Naomi Dariana Justice Felton"
                    }
                }
            },
            {
                "state.offsets", new HighlightField
                {
                    Type = HighlighterType.Postings,
                    PreTags = new[] { "<state>" },
                    PostTags = new[] { "</state>" },
                    HighlightQuery = new TermsQuery
                    {
                        Field = "state.offsets",
                        Terms = new[] { "stable", "bellyup" }
                    }
                }
            }
        }
    }
}

Example json output.

{
  "query": {
    "match": {
      "name.standard": {
        "query": "Upton Sons Shield Rice Rowe Roberts"
      }
    }
  },
  "highlight": {
    "pre_tags": [
      "<tag1>"
    ],
    "post_tags": [
      "</tag1>"
    ],
    "encoder": "html",
    "fields": {
      "name.standard": {
        "type": "plain",
        "force_source": true,
        "fragment_size": 150,
        "fragmenter": "span",
        "number_of_fragments": 3,
        "no_match_size": 150
      },
      "leadDeveloper.firstName": {
        "type": "fvh",
        "phrase_limit": 10,
        "boundary_max_scan": 50,
        "pre_tags": [
          "<name>"
        ],
        "post_tags": [
          "</name>"
        ],
        "highlight_query": {
          "match": {
            "leadDeveloper.firstName": {
              "query": "Kurt Edgardo Naomi Dariana Justice Felton"
            }
          }
        }
      },
      "state.offsets": {
        "type": "postings",
        "pre_tags": [
          "<state>"
        ],
        "post_tags": [
          "</state>"
        ],
        "highlight_query": {
          "terms": {
            "state.offsets": [
              "stable",
              "bellyup"
            ]
          }
        }
      }
    }
  }
}

Handling Responses

edit
response.ShouldBeValid();

foreach (var highlightsInEachHit in response.Hits.Select(d => d.Highlights))
{
    foreach (var highlightField in highlightsInEachHit)
    {
        if (highlightField.Key == "name.standard")
        {
            foreach (var highlight in highlightField.Value.Highlights)
            {
                highlight.Should().Contain("<tag1>");
                highlight.Should().Contain("</tag1>");
            }
        }
        else if (highlightField.Key == "leadDeveloper.firstName")
        {
            foreach (var highlight in highlightField.Value.Highlights)
            {
                highlight.Should().Contain("<name>");
                highlight.Should().Contain("</name>");
            }
        }
        else if (highlightField.Key == "state.offsets")
        {
            foreach (var highlight in highlightField.Value.Highlights)
            {
                highlight.Should().Contain("<state>");
                highlight.Should().Contain("</state>");
            }
        }
        else
            Assert.True(false, $"highlights contains unexpected key {highlightField.Key}");
    }
}

Unified highlighter

edit

The unified highlighter can extract offsets from either postings, term vectors, or via re-analyzing text. Under the hood it uses Lucene UnifiedHighlighter which picks its strategy depending on the field and the query to highlight.

Unified highlighter is available only in Elasticsearch 5.3.0+

This functionality is experimental and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but experimental features are not subject to the support SLA of official GA features.

Fluent DSL example

edit
s => s
.Query(q => q
    .Match(m => m
        .Field(f => f.Name.Suffix("standard"))
        .Query("Upton Sons Shield Rice Rowe Roberts")
    )
)
.Highlight(h => h
    .Fields(
        fs => fs
            .Field(p => p.LeadDeveloper.LastName)
            .Type(HighlighterType.Unified)
            .PreTags("<name>")
            .PostTags("</name>")
            .HighlightQuery(q => q
                .Match(m => m
                    .Field(p => p.LeadDeveloper.LastName)
                    .Query(LastNameSearch)
                )
            )
    )
)

Object Initializer syntax example

edit
new SearchRequest<Project>
{
    Query = new MatchQuery
    {
        Query = "Upton Sons Shield Rice Rowe Roberts",
        Field = "name.standard"
    },
    Highlight = new Highlight
    {
        Fields = new Dictionary<Field, IHighlightField>
        {
            {
                "leadDeveloper.lastName", new HighlightField
                {
                    Type = HighlighterType.Unified,
                    PreTags = new[] { "<name>" },
                    PostTags = new[] { "</name>" },
                    HighlightQuery = new MatchQuery
                    {
                        Field = "leadDeveloper.lastName",
                        Query = LastNameSearch
                    }
                }
            }
        }
    }
}

Example json output.

{
  "query": {
    "match": {
      "name.standard": {
        "query": "Upton Sons Shield Rice Rowe Roberts"
      }
    }
  },
  "highlight": {
    "fields": {
      "leadDeveloper.lastName": {
        "type": "unified",
        "pre_tags": [
          "<name>"
        ],
        "post_tags": [
          "</name>"
        ],
        "highlight_query": {
          "match": {
            "leadDeveloper.lastName": {
              "query": "Stokes"
            }
          }
        }
      }
    }
  }
}

Handling Responses

edit
response.ShouldBeValid();

foreach (var highlightsInEachHit in response.Hits.Select(d => d.Highlights))
{
    foreach (var highlightField in highlightsInEachHit)
    {
        if (highlightField.Key == "leadDeveloper.lastName")
        {
            foreach (var highlight in highlightField.Value.Highlights)
            {
                highlight.Should().Contain("<name>");
                highlight.Should().Contain("</name>");
            }
        }
        else
            Assert.True(false, $"highlights contains unexpected key {highlightField.Key}");
    }
}