diff --git a/README.md b/README.md index afda2ad..f3e187b 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/index.d.ts b/index.d.ts index 180b9d8..6d95cb9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,7 @@ export interface SearchParserOptions { keywords?: string[]; ranges?: string[]; alwaysArray?: boolean; + negatePrefix?: string; } export interface ISearchParserDictionary { diff --git a/lib/search-query-parser.js b/lib/search-query-parser.js index 9bcea7d..ef1f6a7 100644 --- a/lib/search-query-parser.js +++ b/lib/search-query-parser.js @@ -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) { @@ -69,9 +71,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 @@ -132,15 +134,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 @@ -314,7 +316,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 @@ -414,7 +419,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)); } } diff --git a/test/test.js b/test/test.js index 11a9085..8f3653d 100644 --- a/test/test.js +++ b/test/test.js @@ -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 }; @@ -788,4 +803,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'); + }); });