diff --git a/lib/search-query-parser.js b/lib/search-query-parser.js new file mode 100644 index 0000000..d5bd2f1 --- /dev/null +++ b/lib/search-query-parser.js @@ -0,0 +1,138 @@ +/*! + * search-query-parser.js + * Copyright(c) 2014 Julien Buty + * MIT Licensed + */ + +exports.parse = function (string, options) { + + // Set an empty options object when none provided + if (!options) { + options = {}; + } + + // Regularize white spacing + // Make in-between white spaces a unique space + string = string.trim().replace(/\s+/g, ' '); + + // When a simple string, return it + if (-1 === string.indexOf(':')) { + return string; + } + // When no keywords or ranges set, treat as a simple string + else if (!options.keywords && !options.ranges){ + return string; + } + // Otherwise parse the advanced query syntax + else { + // Our object to store the query object + var query = {text: []}; + // Get a list of search term. Reverse to ensure proper order when pop()'ing. + var terms = string.split(' ').reverse(); + // For each search term + while (term = terms.pop()) { + // Advanced search terms syntax has key and value + // separated with a colon + var sepIdx = term.indexOf(':'); + // When just a simple term + if (-1 === sepIdx) { + // We add it as pure text + query.text.push(term); + } + // We got an advanced search syntax + else { + var key = term.slice(0, sepIdx); + // Check if the key is a registered keyword + options.keywords = options.keywords || []; + var isKeyword = !(-1 === options.keywords.indexOf(key)); + // Check if the key is a registered range + options.ranges = options.ranges || []; + var isRange = !(-1 === options.ranges.indexOf(key)); + // When the key matches a keyword + if (isKeyword) { + var value = term.slice(sepIdx + 1); + // When value is a thing + if (value.length) { + // Get an array of values when several are there + var values = value.split(','); + // If we already have seen that keyword... + if (query[key]) { + // ...many times... + if (query[key] instanceof Array) { + // ...and got several values this time... + if (values.length > 1) { + // ... concatenate both arrays. + query[key] = query[key].concat(values); + } + else { + // ... append the current single value. + query[key].push(value); + } + } + // We saw that keyword only once before + else { + // Put both the current value and the new + // value in an array + query[key] = [query[key]] + query[key].push(value); + } + } + // First time we see that keyword + else { + // ...and got several values this time... + if (values.length > 1) { + // ...add all values seen. + query[key] = values; + } + // Got only a single value this time + else { + // Record its value as a string + query[key] = value; + } + } + } + } + // The key allows a range + else if (isRange) { + var value = term.slice(sepIdx + 1); + // Range are separated with a dash + var rangeValues = value.split('-'); + // When both end of the range are specified + // keyword:XXXX-YYYY + query[key] = {}; + if (2 === rangeValues.length) { + query[key].from = rangeValues[0]; + query[key].to = rangeValues[1]; + } + // When pairs of ranges are specified + // keyword:XXXX-YYYY,AAAA-BBBB + else if (!rangeValues.length % 2) { + } + // When only getting a single value, + // or an odd number of values + else { + query[key].from = value; + } + } + else { + // We add it as pure text + query.text.push(term); + } + } + } + + // Concatenate all text terms if any + if (query.text.length) { + query.text = query.text.join(' ').trim(); + } + // Just remove the attribute text when it's empty + else { + delete query.text; + } + + // Return forged query object + return query; + + } + +};