You are currently browsing legacy 3.0 version of documentation. Click here to switch to the newest 5.0 version.

We can help you with migration to the latest RavenDB

Contact Us Now
see on GitHub

Highlights

Another feature called highlights has been added to RavenDB to enhance the search UX.

Usage

Lets consider a class and index as follows:

@QueryEntity
public class BlogPost {

  private String id;
  private String title;
  private String category;
  private String content;
  private Date publishedAt;
  private String[] tags;
  private List<BlogComment> comments;

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getCategory() {
    return category;
  }

  public void setCategory(String category) {
    this.category = category;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  public Date getPublishedAt() {
    return publishedAt;
  }

  public void setPublishedAt(Date publishedAt) {
    this.publishedAt = publishedAt;
  }

  public String[] getTags() {
    return tags;
  }

  public void setTags(String[] tags) {
    this.tags = tags;
  }

  public List<BlogComment> getComments() {
    return comments;
  }

  public void setComments(List<BlogComment> comments) {
    this.comments = comments;
  }
}

public class BlogComment {
  private String title;
  private String content;

  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getContent() {
    return content;
  }
  public void setContent(String content) {
    this.content = content;
  }
}

public static class BlogPosts_ByContent extends AbstractIndexCreationTask {
  public BlogPosts_ByContent() {
    QBlogPost b = QBlogPost.blogPost;
    map =
     " from post in posts " +
     " select new         " +
     "  {                 " +
     "      post.Content  " +
     "  };";
    index(b.content, FieldIndexing.ANALYZED);
    store(b.content, FieldStorage.YES);
    termVector(b.content, FieldTermVector.WITH_POSITIONS_AND_OFFSETS);
  }
}

Now to use Highlights we just need to use one of the highlight query methods. The basic usage can be as simple as:

Reference<FieldHighlightings> highlightingsRef = new Reference<>();

List<BlogPost> results = session
  .advanced()
  .documentQuery(BlogPost.class, BlogPosts_ByContent.class)
  .highlight("Content", 128, 1, highlightingsRef)
  .search("Content", "raven")
  .toList();

StringBuilder builder = new StringBuilder();
builder.append("<ul>\n");
for (BlogPost result : results) {
  String[] fragments = highlightingsRef.value.getFragments(result.getId());
  builder.append("<li>");
  builder.append(fragments[0]);
  builder.append("</li>");
}

String ul = builder.append("</ul>").toString();

This will return the list of results and for each result we will be displaying first found fragment with the length up to 128 characters.

Highlights + Projections

Highlights can also be accessed when projections are performed.

Reference<FieldHighlightings> highlightingsRef = new Reference<>();

List<BlogPosts_ByCategory_Content.Result> results = session
  .advanced()
  .documentQuery(BlogPost.class, BlogPosts_ByCategory_Content.class)
  .highlight("Content", 128, 1, highlightingsRef)
  .setHighlighterTags("**", "**")
  .search("Content", "raven")
  .selectFields(BlogPosts_ByCategory_Content.Result.class)
  .toList();

Highlights + Map-Reduce

Highlights can be accessed when performing queries on map-reduce indexes.

public static class BlogPosts_ByCategory_Content extends AbstractIndexCreationTask {

  public static class Result {
    private String category;
    private String content;

    public String getCategory() {
      return category;
    }
    public void setCategory(String category) {
      this.category = category;
    }
    public String getContent() {
      return content;
    }
    public void setContent(String content) {
      this.content = content;
    }
  }

  public BlogPosts_ByCategory_Content() {
    QBlogPost b = QBlogPost.blogPost;
    map =
     " from post in posts " +
     " select new         " +
     "  {                 " +
     "      post.Category, " +
     "      post.Content  " +
     "  };";

    reduce =
      " from result in results " +
      " group result by result.Category into g " +
      " select new " +
      " { " +
      "    Category = g.Key, " +
      "    Content = string.Join(\" \", g.Select(r => r.Content " +
      " }";
    index(b.content, FieldIndexing.ANALYZED);
    store(b.content, FieldStorage.YES);
    termVector(b.content, FieldTermVector.WITH_POSITIONS_AND_OFFSETS);
  }
}

Reference<FieldHighlightings> highlightingsRef = new Reference<>();

List<BlogPosts_ByCategory_Content.Result> results = session
  .advanced()
  .documentQuery(BlogPosts_ByCategory_Content.Result.class, BlogPosts_ByCategory_Content.class)
  .highlight("Content", "Category", 128, 1, highlightingsRef) // highlighting 'Content', but marking 'Category' as key
  .setHighlighterTags("**", "**")
  .search("Content", "raven")
  .selectFields(BlogPosts_ByCategory_Content.Result.class)
  .toList();

String[] newsHighlightings = highlightingsRef.value.getFragments("News"); // get fragments for 'News' category

Customization

public TSelf highlight(String fieldName, int fragmentLength, int fragmentCount, String fragmentsField);

public TSelf highlight(String fieldName, int fragmentLength, int fragmentCount, Reference<FieldHighlightings> highlightings);

public <TValue> TSelf highlight(Expression<?> propertySelector, int fragmentLength, int fragmentCount, ListPath<?, ?> fragmentsPropertySelector);

public <TValue> TSelf highlight(Expression<?> propertySelector, int fragmentLength, int fragmentCount, Reference<FieldHighlightings> highlightings);

where:
* fieldName or propertySelector is used to mark a field/property for highlight.
* fragmentLength this is the maximum length of text fragments that will be returned.
* fragmentCount this is the maximum number of fragments that will be returned.
* highlightings this will return an instance of a FieldHighlightings that contains the highlight fragments for each returned result.

By default, the highlighted text is wrapped with <b></b> tags, to change this behavior the setHighlighterTags method was introduced.

public TSelf setHighlighterTags(String preTag, String postTag);

public TSelf setHighlighterTags(String[] preTags, String[] postTags);

Example. To wrap highlighted text with ** we just need to execute following query:

Reference<FieldHighlightings> highlightingsRef = new Reference<>();

List<BlogPost> results = session
  .advanced()
  .documentQuery(BlogPost.class, BlogPosts_ByContent.class)
  .highlight("Content", 128, 1, highlightingsRef)
  .setHighlighterTags("**", "**")
  .search("Content", "raven")
  .toList();

Note

Default <b></b> tags are coloured and colours are returned in following order:

  •  yellow,
  •  lawngreen,
  •  aquamarine,
  •  magenta,
  •  palegreen,
  •  coral,
  •  wheat,
  •  khaki,
  •  lime,
  •  deepskyblue,
  •  deeppink,
  •  salmon,
  •  peachpuff,
  •  violet,
  •  mediumpurple,
  •  palegoldenrod,
  •  darkkhaki,
  •  springgreen,
  •  turquoise,
  •  powderblue