diff --git a/lib/utils.js b/lib/utils.js index e61ea34..78876fa 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -243,67 +243,80 @@ export function applyLocalSearchCriteria(data, options) { if (options.filter && options.filter.length > 0) { options.filter.forEach(group => { - const orFilters = group.split(/\s*\|\|\s*/); - result = result.filter(item => { - return orFilters.some(f => { - const match = f.match(/^([\w\.]+)(?::([a-z!]+))?(?:(<=|>=|<|>|=|!=|!~|~|\?|!|@@)(.*))?$/); - if (!match) return false; + const orFiltersStrings = group.split(/\s*\|\|\s*/); + const parsedFilters = orFiltersStrings.map(f => { + const match = f.match(/^([\w\.]+)(?::([a-z!]+))?(?:(<=|>=|<|>|=|!=|!~|~|\?|!|@@)(.*))?$/); + if (!match) return null; + + const field = match[1]; + const opString = match[2]; + const shorthand = match[3]; + let value = match[4] || ''; + + let condition = 'eq'; + if (opString) { + condition = opString === '!in' ? 'nin' : opString; + } else if (shorthand) { + switch (shorthand) { + case '>': condition = 'gt'; break; + case '<': condition = 'lt'; break; + case '>=': condition = 'gteq'; break; + case '<=': condition = 'lteq'; break; + case '!=': condition = 'neq'; break; + case '~': condition = 'like'; break; + case '!~': condition = 'nlike'; break; + case '@@': condition = 'finset'; break; + case '?': condition = 'null'; value = '1'; break; + case '!': condition = 'notnull'; value = '1'; break; + default: condition = 'eq'; + } + } - const field = match[1]; - const opString = match[2]; - const shorthand = match[3]; - let value = match[4] || ''; + if ((condition === 'like' || condition === 'nlike') && value.includes('*')) { + value = value.replaceAll('*', '%'); + } - let condition = 'eq'; - if (opString) { - condition = opString === '!in' ? 'nin' : opString; - } else if (shorthand) { - switch (shorthand) { - case '>': condition = 'gt'; break; - case '<': condition = 'lt'; break; - case '>=': condition = 'gteq'; break; - case '<=': condition = 'lteq'; break; - case '!=': condition = 'neq'; break; - case '~': condition = 'like'; break; - case '!~': condition = 'nlike'; break; - case '@@': condition = 'finset'; break; - case '?': condition = 'null'; value = '1'; break; - case '!': condition = 'notnull'; value = '1'; break; - default: condition = 'eq'; - } - } + const strVal = String(value).toLowerCase(); + const numVal = Number(value); + const stringValue = String(value); - if ((condition === 'like' || condition === 'nlike') && value.includes('*')) { - value = value.replaceAll('*', '%'); - } + let inValues = null; + if (condition === 'in' || condition === 'nin') { + inValues = stringValue.split(',').map(s => s.trim()); + } - const itemValue = item[field]; - const strItemVal = String(itemValue || '').toLowerCase(); - const strVal = String(value).toLowerCase(); + let regex = null; + if (condition === 'like' || condition === 'nlike') { + regex = new RegExp('^' + strVal.replace(/%/g, '.*') + '$', 'i'); + } + + return { field, condition, value, strVal, numVal, stringValue, inValues, regex }; + }).filter(f => f !== null); + + result = result.filter(item => { + return parsedFilters.some(f => { + const itemValue = item[f.field]; const numItemVal = Number(itemValue); - const numVal = Number(value); - - switch (condition) { - case 'eq': return String(itemValue) === String(value); - case 'neq': return String(itemValue) !== String(value); - case 'gt': return numItemVal > numVal; - case 'gteq': return numItemVal >= numVal; - case 'lt': return numItemVal < numVal; - case 'lteq': return numItemVal <= numVal; + + switch (f.condition) { + case 'eq': return String(itemValue) === f.stringValue; + case 'neq': return String(itemValue) !== f.stringValue; + case 'gt': return numItemVal > f.numVal; + case 'gteq': return numItemVal >= f.numVal; + case 'lt': return numItemVal < f.numVal; + case 'lteq': return numItemVal <= f.numVal; case 'like': - const regexFromLike = new RegExp('^' + strVal.replace(/%/g, '.*') + '$', 'i'); - return regexFromLike.test(strItemVal); + return f.regex.test(String(itemValue || '').toLowerCase()); case 'nlike': - const nregexFromLike = new RegExp('^' + strVal.replace(/%/g, '.*') + '$', 'i'); - return !nregexFromLike.test(strItemVal); + return !f.regex.test(String(itemValue || '').toLowerCase()); case 'in': - return String(value).split(',').map(s => s.trim()).includes(String(itemValue)); + return f.inValues.includes(String(itemValue)); case 'nin': - return !String(value).split(',').map(s => s.trim()).includes(String(itemValue)); + return !f.inValues.includes(String(itemValue)); case 'null': return itemValue === null || itemValue === undefined; case 'notnull': return itemValue !== null && itemValue !== undefined; case 'finset': - return String(itemValue).split(',').map(s => s.trim()).includes(String(value)); + return String(itemValue).split(',').map(s => s.trim()).includes(f.stringValue); default: return false; } });