Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to customize negate prefix #54

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ var searchQueryObj = searchQuery.parse(query, options);
```

You can configure what keywords and ranges the parser should accept with the options argument.
It accepts 5 values:
It accepts 6 values:
* `keywords`, that can be separated by commas (,). Accepts an array of strings.
* `ranges`, that can be separated by a hyphen (-). Accepts an array of strings.
* `tokenize`, that controls the behaviour of text search terms. If set to `true`, non-keyword text terms are returned as an array of strings where each term in the array is a whitespace-separated word, or a multi-word term surrounded by single- or double-quotes.
* `alwaysArray`, a boolean that controls the behaviour of the returned query. If set to `true`, all matched keywords would always be arrays instead of strings. If set to `false` they will be strings if matched a single value. Defaults to `false`.
* `offsets`, a boolean that controls the behaviour of the returned query. If set to `true`, the query will contain the offsets object. If set to `false`, the query will not contain the offsets object. Defaults to `true`.
* `negatePrefix`, a string that controls the behaviour of the returned query. It defines what prefix is used to mark a term as excluded. Defaults to `'-'`.

If no keywords or ranges are specified, or if none are present in the given search query, then `searchQuery.parse` will return a string if `tokenize` is false, or an array of strings under the key `text` if `tokenize` is true.

Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface SearchParserOptions {
keywords?: string[];
ranges?: string[];
alwaysArray?: boolean;
negatePrefix?: string;
}

export interface ISearchParserDictionary {
Expand Down
43 changes: 26 additions & 17 deletions lib/search-query-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ exports.parse = function (string, options) {

// Set a default options object when none is provided
if (!options) {
options = {offsets: true};
options = {offsets: true, negatePrefix: '-'};
} else {
// If options offsets was't passed, set it to true
options.offsets = (typeof options.offsets === 'undefined' ? true : options.offsets)
// If options negatePrefix was't passed, set it to '-'
options.negatePrefix = (typeof options.negatePrefix === 'undefined' ? '-' : options.negatePrefix)
}

if (!string) {
Expand Down Expand Up @@ -43,8 +45,7 @@ exports.parse = function (string, options) {
var term = match[0];
var sepIndex = term.indexOf(':');
if (sepIndex !== -1) {
var split = term.split(':'),
key = term.slice(0, sepIndex),
var key = term.slice(0, sepIndex),
val = term.slice(sepIndex + 1);
// Strip surrounding quotes
val = val.replace(/^\"|\"$|^\'|\'$/g, '');
Expand All @@ -69,9 +70,9 @@ exports.parse = function (string, options) {
});
} else {
var isExcludedTerm = false;
if (term[0] === '-') {
if (term.startsWith(options.negatePrefix)) {
isExcludedTerm = true;
term = term.slice(1);
term = term.slice(options.negatePrefix.length);
}

// Strip surrounding quotes
Expand All @@ -90,6 +91,11 @@ exports.parse = function (string, options) {
}
});

if (term.length === 0) {
// Ignore empty strings after cleanup
continue
}

if (isExcludedTerm) {
if (exclusion['text']) {
if (exclusion['text'] instanceof Array) {
Expand Down Expand Up @@ -117,7 +123,7 @@ exports.parse = function (string, options) {
var term;
while (term = terms.pop()) {
// When just a simple term
if (term.text) {
if (term.text !== undefined) {
// We add it as pure text
query.text.push(term.text);
// When offsets is true, push a new offset
Expand All @@ -132,15 +138,15 @@ exports.parse = function (string, options) {
options.keywords = options.keywords || [];
var isKeyword = false;
var isExclusion = false;
if (!/^-/.test(key)) {
isKeyword = !(-1 === options.keywords.indexOf(key));
} else if (key[0] === '-') {
var _key = key.slice(1);
isKeyword = !(-1 === options.keywords.indexOf(_key))
if (isKeyword) {
key = _key;
isExclusion = true;
}
if (key.startsWith(options.negatePrefix)) {
var _key = key.slice(options.negatePrefix.length);
isKeyword = !(-1 === options.keywords.indexOf(_key))
if (isKeyword) {
key = _key;
isExclusion = true;
}
} else {
isKeyword = !(-1 === options.keywords.indexOf(key));
}

// Check if the key is a registered range
Expand Down Expand Up @@ -314,7 +320,10 @@ exports.stringify = function (queryObject, options, prefix) {

// Set a default options object when none is provided
if (!options) {
options = {offsets: true};
options = {offsets: true, negatePrefix: '-'};
} else {
// If options negatePrefix was't passed, set it to '-'
options.negatePrefix = (typeof options.negatePrefix === 'undefined' ? '-' : options.negatePrefix)
}

// If the query object is falsy we can just return an empty string
Expand Down Expand Up @@ -414,7 +423,7 @@ exports.stringify = function (queryObject, options, prefix) {
// Exclude
if (queryObject.exclude) {
if (Object.keys(queryObject.exclude).length > 0) {
parts.push(exports.stringify(queryObject.exclude, options, '-'));
parts.push(exports.stringify(queryObject.exclude, options, options.negatePrefix));
}
}

Expand Down
56 changes: 56 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ describe('Search query syntax parser', function () {
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should return a tokenized string with custom negation prefix', function () {
var searchQuery = "fancy !pyjama !wear";
var options = { tokenize: true, negatePrefix: '!' };
var parsedSearchQuery = searchquery.parse(searchQuery, options);

parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.should.have.property('text', ['fancy']);
parsedSearchQuery.should.have.property('exclude', {text: ['pyjama', 'wear']});

var parsedAfterStringifySearchQuery = searchquery.parse(searchquery.stringify(parsedSearchQuery, options), options);
parsedAfterStringifySearchQuery.offsets = undefined;
parsedSearchQuery.offsets = undefined;
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should return a tokenized string with negation of single-quoted terms', function () {
var searchQuery = "fancy -'pyjama -wear'";
var options = { tokenize: true };
Expand Down Expand Up @@ -103,6 +118,35 @@ describe('Search query syntax parser', function () {
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should return a tokenized string without empty text terms', function () {
var searchQuery = "fancy pyjama wear ''";
var options = { tokenize: true };
var parsedSearchQuery = searchquery.parse(searchQuery, options);

parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.should.have.property('text', ['fancy', 'pyjama', 'wear']);

var parsedAfterStringifySearchQuery = searchquery.parse(searchquery.stringify(parsedSearchQuery, options), options);
parsedAfterStringifySearchQuery.offsets = undefined;
parsedSearchQuery.offsets = undefined;
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should return a simple string without empty text terms', function () {
var searchQuery = "key:value fancy pyjama wear ''";
var options = { keywords: ['key'] };
var parsedSearchQuery = searchquery.parse(searchQuery, options);

parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.should.have.property('text', 'fancy pyjama wear');
parsedSearchQuery.should.have.property('key', 'value');

var parsedAfterStringifySearchQuery = searchquery.parse(searchquery.stringify(parsedSearchQuery, options), options);
parsedAfterStringifySearchQuery.offsets = undefined;
parsedSearchQuery.offsets = undefined;
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should parse a single keyword with no text', function () {
var searchQuery = 'from:[email protected]';
var options = {keywords: ['from']};
Expand Down Expand Up @@ -788,4 +832,16 @@ describe('Search query syntax parser', function () {
parsedSearchQuery.offsets = undefined;
parsedAfterStringifySearchQuery.should.be.eql(parsedSearchQuery);
});

it('should stringify properly with default negate prefix', function () {
var searchQueryObject = {
text: [ 'fancy' ],
exclude: { text: [ 'pyjama', 'wear' ] }
};
var options = { tokenize: true };
stringifiedSearchQuery = searchquery.stringify(searchQueryObject, options);

stringifiedSearchQuery.should.be.a.string;
stringifiedSearchQuery.should.be.eql('fancy -pyjama -wear');
});
});