Skip to content

Commit

Permalink
feat: return query that identifies all highlights
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Nov 10, 2021
1 parent c2a7e3d commit 5ae5fa9
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 24 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Lightweight and performant Lucene-like parser and search engine.
* [Wildcard matching](#wildcard-matching)
* [Logical Operators](#logical-operators)
* [Compatibility with Lucene](#compatibility-with-lucene)
* [Recipes](#recipes)
* [Highlighting matches](#highlighting-matches)
* [Development](#development)

## Usage
Expand Down Expand Up @@ -76,8 +78,8 @@ Highlight matching fields and substrings:
test(highlight('name:john'), persons[0]);
// [
// {
// keyword: 'John',
// path: 'name',
// query: /(John)/,
// }
// ]
test(highlight('height:180'), persons[0]);
Expand Down Expand Up @@ -242,6 +244,12 @@ The following Lucene abilities are not supported:
* [Proximity Searches](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Proximity%20Searches)
* [Boosting a Term](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Boosting%20a%20Term)

## Recipes

### Highlighting matches

Consider using [`highlight-words`](https://github.com/tricinel/highlight-words) package to highlight Liqe matches.

## Development

### Compiling Parser
Expand Down
47 changes: 45 additions & 2 deletions src/highlight.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import {
escapeRegexString,
} from './escapeRegexString';
import {
internalFilter,
} from './internalFilter';
import type {
Ast,
Highlight,
InternalHighlight,
} from './types';

type AggregatedHighlight = {
keywords: string[],
path: string,
};

export const highlight = <T extends Object>(
ast: Ast,
data: T,
): Highlight[] => {
const highlights = [];
const highlights: InternalHighlight[] = [];

internalFilter(
ast,
Expand All @@ -20,5 +29,39 @@ export const highlight = <T extends Object>(
highlights,
);

return highlights;
const aggregatedHighlights: AggregatedHighlight[] = [];

for (const highlightNode of highlights) {
let aggregatedHighlight = aggregatedHighlights.find((maybeTarget) => {
return maybeTarget.path === highlightNode.path;
});

if (!aggregatedHighlight) {
aggregatedHighlight = {
keywords: [],
path: highlightNode.path,
};

aggregatedHighlights.push(aggregatedHighlight);
}

if (highlightNode.keyword) {
aggregatedHighlight.keywords.push(highlightNode.keyword);
}
}

return aggregatedHighlights.map((aggregatedHighlight) => {
if (aggregatedHighlight.keywords.length > 0) {
return {
path: aggregatedHighlight.path,
query: new RegExp('(' + aggregatedHighlight.keywords.map((keyword) => {
return escapeRegexString(keyword.trim());
}).join('|') + ')'),
};
}

return {
path: aggregatedHighlight.path,
};
});
};
8 changes: 4 additions & 4 deletions src/internalFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from './parseRegex';
import type {
Ast,
Highlight,
InternalHighlight,
InternalTest,
Range,
RelationalOperator,
Expand Down Expand Up @@ -121,7 +121,7 @@ const testValue = (
value: unknown,
resultFast: boolean,
path: string[],
highlights: Highlight[],
highlights: InternalHighlight[],
) => {
if (Array.isArray(value)) {
let foundMatch = false;
Expand Down Expand Up @@ -181,7 +181,7 @@ const testField = <T extends Object>(
ast: Ast,
resultFast: boolean,
path: string[],
highlights: Highlight[],
highlights: InternalHighlight[],
): boolean => {
if (!ast.test) {
ast.test = createValueTest(ast);
Expand Down Expand Up @@ -251,7 +251,7 @@ export const internalFilter = <T extends Object>(
rows: readonly T[],
resultFast: boolean = true,
path: string[] = [],
highlights: Highlight[] = [],
highlights: InternalHighlight[] = [],
): readonly T[] => {
if (ast.field) {
return rows.filter((row) => {
Expand Down
7 changes: 6 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ export type Ast = {
test?: InternalTest,
};

export type Highlight = {
export type InternalHighlight = {
keyword?: string,
path: string,
};

export type Highlight = {
path: string,
query?: RegExp,
};

export type InternalTest = (value: unknown) => boolean | string;
77 changes: 61 additions & 16 deletions test/liqe/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ test.skip(
},
[
{
keyword: 'foo@bar.com',
path: 'email',
query: /(foo@bar.com)/,
},
{
keyword: 'foo bar',
keyword: /(foo bar)/,
path: 'name',
},
],
Expand All @@ -43,12 +43,12 @@ test(
},
[
{
keyword: 'foo',
path: 'email',
query: /(foo)/,
},
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);
Expand All @@ -62,8 +62,8 @@ test(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);
Expand All @@ -77,8 +77,8 @@ test(
},
[
{
keyword: 'Foo',
path: 'name',
query: /(Foo)/,
},
],
);
Expand All @@ -93,8 +93,8 @@ test(
},
[
{
keyword: 'bar',
path: 'name',
query: /(bar)/,
},
{
path: 'height',
Expand All @@ -111,8 +111,8 @@ test(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);
Expand All @@ -126,8 +126,8 @@ test(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);
Expand All @@ -141,8 +141,8 @@ test(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);
Expand All @@ -156,11 +156,11 @@ test.skip(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
{
keyword: 'bar',
keyword: /(bar)/,
path: 'name',
},
],
Expand Down Expand Up @@ -221,8 +221,8 @@ test(
},
[
{
keyword: 'bar',
path: 'tags.1',
query: /(bar)/,
},
],
);
Expand All @@ -240,12 +240,12 @@ test(
},
[
{
keyword: 'ba',
path: 'tags.1',
query: /(ba)/,
},
{
keyword: 'ba',
path: 'tags.2',
query: /(ba)/,
},
],
);
Expand Down Expand Up @@ -279,8 +279,53 @@ test(
},
[
{
keyword: 'foo',
path: 'name',
query: /(foo)/,
},
],
);

test(
'aggregates multiple highlights',
testQuery,
'foo AND bar AND baz',
{
name: 'foo bar baz',
},
[
{
path: 'name',
query: /(foo|bar|baz)/,
},
],
);

test(
'aggregates multiple highlights (phrases)',
testQuery,
'"foo bar" AND baz',
{
name: 'foo bar baz',
},
[
{
path: 'name',
query: /(foo bar|baz)/,
},
],
);

test(
'aggregates multiple highlights (escaping)',
testQuery,
'"(foo bar)" AND baz',
{
name: '(foo bar) baz',
},
[
{
path: 'name',
query: /(\(foo bar\)|baz)/,
},
],
);

0 comments on commit 5ae5fa9

Please sign in to comment.