UNPKG

@schukai/monster

Version:

Monster is a simple library for creating fast, robust and lightweight websites.

192 lines (176 loc) 7.87 kB
/** * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact Volker Schukai. * * SPDX-License-Identifier: AGPL-3.0 */ export { generateRangeComparisonExpression }; /** * The `generateRangeComparisonExpression()` function is a function that generates a string representation * of a comparison expression based on a range of values. It takes three arguments: * * - expression (required): a string representation of a range of values in the format of start1-end1,start2-end2,value3.... * - valueName (required): a string representing the name of the value that is being compared to the range of values. * - options (optional): an object containing additional options to customize the comparison expression. * * The generateRangeComparisonExpression() function returns a string representation of the comparison expression. * * ## Options * The option parameter is an object that can have the following properties: * * urlEncode (boolean, default: false): if set to true, URL encodes the comparison operators. * andOp (string, default: '&&'): the logical AND operator to use in the expression. * orOp (string, default: '||'): the logical OR operator to use in the expression. * eqOp (string, default: '=='): the equality operator to use in the expression. * geOp (string, default: '>='): the greater than or equal to operator to use in the expression. * leOp (string, default: '<='): the less than or equal to operator to use in the expression. * gtOp (string, default: '>'): the greater than operator to use in the expression. * ltOp (string, default: '<'): the less than operator to use in the expression. * * Examples * * ```javascript * const expression = '0-10,20-30'; * const valueName = 'age'; * const options = { urlEncode: true, andOp: 'and', orOp: 'or', eqOp: '=', geOp: '>=', leOp: '<=' }; * const comparisonExpression = generateRangeComparisonExpression(expression, valueName, options); * * console.log(comparisonExpression); // age%3E%3D0%20and%20age%3C%3D10%20or%20age%3E%3D20%20and%20age%3C%3D30 * ``` * * In this example, the generateRangeComparisonExpression() function generates a string representation of the comparison * expression for the expression and valueName parameters with the specified options. The resulting comparison * expression is 'age>=0 and age<=10 or age>=20 and age<=30', URL encoded according to the urlEncode option. * * @param {string} expression - The string expression to generate the comparison for. * @param {string} valueName - The name of the value to compare against. * @param {Object} [options] - The optional parameters. * @param {boolean} [options.urlEncode=false] - Whether to encode comparison operators for use in a URL. * @param {string} [options.andOp='&&'] - The logical AND operator to use. * @param {string} [options.orOp='||'] - The logical OR operator to use. * @param {string} [options.eqOp='=='] - The comparison operator for equality to use. * @param {string} [options.geOp='>='] - The comparison operator for greater than or equal to to use. * @param {string} [options.leOp='<='] - The comparison operator for less than or equal to to use. * @param {string} [options.gtOp='>'] - The comparison operator for greater than to use. * @param {string} [options.ltOp='<'] - The comparison operator for less than to use. * @return {string} The generated comparison expression. * @throws {Error} If the input is invalid. * @summary Generates a comparison expression based on a range of values. */ /** * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved. * SPDX-License-Identifier: AGPL-3.0 */ function generateRangeComparisonExpression( expression, valueName, options = {}, ) { const { urlEncode = false, andOp = "&&", orOp = "||", eqOp = "==", geOp = ">=", leOp = "<=", gtOp = ">", ltOp = "<", } = options; if ( typeof expression !== "string" || typeof valueName !== "string" || !expression.trim() ) { throw new Error("Invalid input"); } const encode = (s) => (urlEncode ? encodeURIComponent(s) : s); const and = encode(andOp); const or = encode(orOp); const space = urlEncode ? "%20" : " "; function parseRange(range) { range = range.trim(); if (!range) throw new Error("Empty range"); // Sonderfall: nur Bindestriche wie "--" if (/^-+$/.test(range)) { throw new Error(`Invalid range '${range}'`); } // Bereich: z.B. -10--5, 4-6 const rangeMatch = range.match( /^(-?\d+(?:\.\d+)?)\s*-\s*(-?\d+(?:\.\d+)?)/, ); if (rangeMatch) { const sNum = parseFloat(rangeMatch[1]); const eNum = parseFloat(rangeMatch[2]); if (isNaN(sNum) || isNaN(eNum)) throw new Error(`Invalid value '${range}'`); if (sNum > eNum) throw new Error(`Invalid range '${range}'`); return `${valueName}${encode(geOp)}${sNum}${space}${and}${space}${valueName}${encode(leOp)}${eNum}`; } // Exklusiver Bereich: 4~6 → x>4 && x<6 const exclMatch = range.match(/^(-?\d+(?:\.\d+)?)\s*~\s*(-?\d+(?:\.\d+)?)/); if (exclMatch) { const sNum = parseFloat(exclMatch[1]); const eNum = parseFloat(exclMatch[2]); if (isNaN(sNum) || isNaN(eNum)) throw new Error(`Invalid value '${range}'`); return `${valueName}${encode(gtOp)}${sNum}${space}${and}${space}${valueName}${encode(ltOp)}${eNum}`; } // Offene Intervalle exklusiv: 4~ → x>4, ~6 → x<6 const openRightExclMatch = range.match(/^(-?\d+(?:\.\d+)?)~$/); if (openRightExclMatch) { const sNum = parseFloat(openRightExclMatch[1]); if (isNaN(sNum)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(gtOp)}${sNum}`; } const openLeftExclMatch = range.match(/^~(-?\d+(?:\.\d+)?)$/); if (openLeftExclMatch) { const eNum = parseFloat(openLeftExclMatch[1]); if (isNaN(eNum)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(ltOp)}${eNum}`; } // Offene Intervalle inklusiv: 4- → x>=4, -6 → x<=6 const openRightInclMatch = range.match(/^(-?\d+(?:\.\d+)?)\-$/); if (openRightInclMatch) { const sNum = parseFloat(openRightInclMatch[1]); if (isNaN(sNum)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(geOp)}${sNum}`; } const openLeftInclMatch = range.match(/^\-(-?\d+(?:\.\d+)?)$/); if (openLeftInclMatch) { const eNum = parseFloat(openLeftInclMatch[1]); if (isNaN(eNum)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(leOp)}${eNum}`; } // <n oder >n if (range.startsWith("<")) { const n = parseFloat(range.slice(1)); if (isNaN(n)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(ltOp)}${n}`; } if (range.startsWith(">")) { const n = parseFloat(range.slice(1)); if (isNaN(n)) throw new Error(`Invalid value in '${range}'`); return `${valueName}${encode(gtOp)}${n}`; } // Einzelwert NUR, wenn wirklich reine Zahl if (/^-?\d+(\.\d+)?$/.test(range)) { const value = parseFloat(range); return `${valueName}${encode(eqOp)}${value}`; } throw new Error(`Invalid value '${range}'`); } const parts = expression .split(",") .map((r) => r.trim()) .filter(Boolean) .map(parseRange); const sep = space + or + space; return parts.length > 1 ? parts.map((p) => `(${p})`).join(sep) : parts[0]; }