Highlights

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

Usage

Lets consider a class and index as follows:

public class BlogPost
{
	public string Id { get; set; }

	public string Title { get; set; }

	public string Category { get; set; }

	public string Content { get; set; }

	public DateTime PublishedAt { get; set; }

	public string[] Tags { get; set; }

	public BlogComment[] Comments { get; set; }
}

public class BlogComment
{
	public string Title { get; set; }

	public string Content { get; set; }
}

public class BlogPosts_ByContent : AbstractIndexCreationTask<BlogPost>
{
	public class Result
	{
		public string Content { get; set; }
	}

	public BlogPosts_ByContent()
	{
		Map = posts => from post in posts
					   select new
						{
							post.Content
						};

		Index(x => x.Content, FieldIndexing.Analyzed);
		Store(x => x.Content, FieldStorage.Yes);
		TermVector(x => x.Content, FieldTermVector.WithPositionsAndOffsets);
	}
}

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

FieldHighlightings highlightings;

BlogPost[] results = session
	.Advanced
	.DocumentQuery<BlogPost, BlogPosts_ByContent>()
	.Highlight("Content", 128, 1, out highlightings)
	.Search("Content", "raven")
	.ToArray();

StringBuilder builder = new StringBuilder()
	.AppendLine("<ul>");

foreach (BlogPost result in results)
{
	string[] fragments = highlightings.GetFragments(result.Id);
	builder.AppendLine(string.Format("<li>{0}</li>", fragments.First()));
}

string ul = builder
	.AppendLine("</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.

FieldHighlightings highlightings;

BlogPosts_ByContent.Result[] results = session
	.Advanced
	.DocumentQuery<BlogPost, BlogPosts_ByContent>()
	.Highlight("Content", 128, 1, out highlightings)
	.SetHighlighterTags("**", "**")
	.Search("Content", "raven")
	.SelectFields<BlogPosts_ByContent.Result>()		// projecting
	.ToArray();

Highlights + Map-Reduce

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

public class BlogPosts_ByCategory_Content : AbstractIndexCreationTask<BlogPost, BlogPosts_ByCategory_Content.Result>
{
	public class Result
	{
		public string Category { get; set; }

		public string Content { get; set; }
	}

	public BlogPosts_ByCategory_Content()
	{
		Map = posts => from post in posts
					   select new
					   {
						   post.Category,
						   post.Content
					   };

		Reduce = results => 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(x => x.Content, FieldIndexing.Analyzed);
		Store(x => x.Content, FieldStorage.Yes);
		TermVector(x => x.Content, FieldTermVector.WithPositionsAndOffsets);
	}
}

FieldHighlightings highlightings;

BlogPosts_ByCategory_Content.Result[] results = session
	.Advanced
	.DocumentQuery<BlogPosts_ByCategory_Content.Result, BlogPosts_ByCategory_Content>()
	.Highlight("Content", "Category", 128, 1, out highlightings) // highlighting 'Content', but marking 'Category' as key
	.SetHighlighterTags("**", "**")
	.Search("Content", "raven")
	.ToArray();

var newsHighlightings = highlightings.GetFragments("News"); // get fragments for 'News' category

Customization

IDocumentQuery<T> Highlight(
	string fieldName,
	int fragmentLength,
	int fragmentCount,
	out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight(
	string fieldName,
	string fieldKeyName,
	int fragmentLength,
	int fragmentCount,
	out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight<TValue>(
	Expression<Func<T, TValue>> propertySelector,
	int fragmentLength,
	int fragmentCount,
	out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight<TValue>(
	Expression<Func<T, TValue>> propertySelector,
	Expression<Func<T, TValue>> keyPropertySelector,
	int fragmentLength,
	int fragmentCount,
	out FieldHighlightings highlightings);

where:
* fieldName or propertySelector is used to mark a field/property for highlight.
* fieldKeyName or keyPropertySelector is used to mark a field/property key 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.

IDocumentQuery<T> SetHighlighterTags(string preTag, string postTag);

IDocumentQuery<T> SetHighlighterTags(string[] preTags, string[] postTags);

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

FieldHighlightings highlightings;

BlogPost[] results = session
	.Advanced
	.DocumentQuery<BlogPost, BlogPosts_ByContent>()
	.Highlight("Content", 128, 1, out highlightings)
	.SetHighlighterTags("**", "**")
	.Search("Content", "raven")
	.ToArray();

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