diff --git a/application/source/text/util.mjs b/application/source/text/util.mjs new file mode 100644 index 0000000000000000000000000000000000000000..5c71e7d3b566a72c7cf7c3803b811a5950d9752e --- /dev/null +++ b/application/source/text/util.mjs @@ -0,0 +1,57 @@ +export {generateRangeComparisonExpression} + +/** + * Generates a comparison expression for a comma-separated string of ranges and single values. + * @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. + * @returns {string} The generated comparison expression. + * @throws {Error} If the input is invalid. + */ +function generateRangeComparisonExpression(expression, valueName, options = {}) { + const { + urlEncode = false, + andOp = '&&', + orOp = '||', + eqOp = '==', + geOp = '>=', + leOp = '<=', + } = options; + const ranges = expression.split(','); + let comparison = ''; + for (let i = 0; i < ranges.length; i++) { + const range = ranges[i].trim(); + if (range === '') { + throw new Error(`Invalid range '${range}'`); + } else if (range.includes('-')) { + const [start, end] = range.split('-').map(s => (s === '' ? null : parseFloat(s))); + if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) { + throw new Error(`Invalid value in range '${range}'`); + } + if (start !== null && end !== null && start > end) { + throw new Error(`Invalid range '${range}'`); + } + const compStart = start !== null ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` : ''; + const compEnd = end !== null ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` : ''; + const compRange = `${compStart}${compStart && compEnd ? ` ${andOp} ` : ''}${compEnd}`; + comparison += ranges.length > 1 ? `(${compRange})` : compRange; + } else { + const value = parseFloat(range); + if (isNaN(value)) { + throw new Error(`Invalid value '${range}'`); + } + const compValue = `${valueName}${urlEncode ? encodeURIComponent(eqOp) : eqOp}${value}`; + comparison += ranges.length > 1 ? `(${compValue})` : compValue; + } + if (i < ranges.length - 1) { + comparison += ` ${orOp} `; + } + } + return comparison; +} diff --git a/development/package.json b/development/package.json index a7e23c32a4fd62359383c8c0e010c96b8760527a..37545862ba008ecc322c63c0a5c43bc22802eb02 100644 --- a/development/package.json +++ b/development/package.json @@ -32,7 +32,7 @@ "create-polyfill-service-url": "^2.2.6", "crypt": "^0.0.2", "cssnano": "^5.1.15", - "esbuild": "^0.17.11", + "esbuild": "^0.17.12", "flow-bin": "^0.202.0", "fs": "0.0.1-security", "glob": "^9.3.0", diff --git a/development/pnpm-lock.yaml b/development/pnpm-lock.yaml index 88fdf43bbb9ffa2a969178b4cf2eb61ce1729af5..d1159362e999a9e9a9f61d7614cfc1eb7a8eee8f 100644 --- a/development/pnpm-lock.yaml +++ b/development/pnpm-lock.yaml @@ -13,7 +13,7 @@ specifiers: create-polyfill-service-url: ^2.2.6 crypt: ^0.0.2 cssnano: ^5.1.15 - esbuild: ^0.17.11 + esbuild: ^0.17.12 flow-bin: ^0.202.0 fs: 0.0.1-security glob: ^9.3.0 @@ -62,7 +62,7 @@ devDependencies: create-polyfill-service-url: 2.2.6 crypt: 0.0.2 cssnano: 5.1.15_postcss@8.4.21 - esbuild: 0.17.11 + esbuild: 0.17.12 flow-bin: 0.202.0 fs: 0.0.1-security glob: 9.3.0 @@ -322,8 +322,8 @@ packages: postcss-selector-parser: 6.0.11 dev: true - /@esbuild/android-arm/0.17.11: - resolution: {integrity: sha512-CdyX6sRVh1NzFCsf5vw3kULwlAhfy9wVt8SZlrhQ7eL2qBjGbFhRBWkkAzuZm9IIEOCKJw4DXA6R85g+qc8RDw==} + /@esbuild/android-arm/0.17.12: + resolution: {integrity: sha512-E/sgkvwoIfj4aMAPL2e35VnUJspzVYl7+M1B2cqeubdBhADV4uPon0KCc8p2G+LqSJ6i8ocYPCqY3A4GGq0zkQ==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -331,8 +331,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64/0.17.11: - resolution: {integrity: sha512-QnK4d/zhVTuV4/pRM4HUjcsbl43POALU2zvBynmrrqZt9LPcLA3x1fTZPBg2RRguBQnJcnU059yKr+bydkntjg==} + /@esbuild/android-arm64/0.17.12: + resolution: {integrity: sha512-WQ9p5oiXXYJ33F2EkE3r0FRDFVpEdcDiwNX3u7Xaibxfx6vQE0Sb8ytrfQsA5WO6kDn6mDfKLh6KrPBjvkk7xA==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -340,8 +340,8 @@ packages: dev: true optional: true - /@esbuild/android-x64/0.17.11: - resolution: {integrity: sha512-3PL3HKtsDIXGQcSCKtWD/dy+mgc4p2Tvo2qKgKHj9Yf+eniwFnuoQ0OUhlSfAEpKAFzF9N21Nwgnap6zy3L3MQ==} + /@esbuild/android-x64/0.17.12: + resolution: {integrity: sha512-m4OsaCr5gT+se25rFPHKQXARMyAehHTQAz4XX1Vk3d27VtqiX0ALMBPoXZsGaB6JYryCLfgGwUslMqTfqeLU0w==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -349,8 +349,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64/0.17.11: - resolution: {integrity: sha512-pJ950bNKgzhkGNO3Z9TeHzIFtEyC2GDQL3wxkMApDEghYx5Qers84UTNc1bAxWbRkuJOgmOha5V0WUeh8G+YGw==} + /@esbuild/darwin-arm64/0.17.12: + resolution: {integrity: sha512-O3GCZghRIx+RAN0NDPhyyhRgwa19MoKlzGonIb5hgTj78krqp9XZbYCvFr9N1eUxg0ZQEpiiZ4QvsOQwBpP+lg==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -358,8 +358,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64/0.17.11: - resolution: {integrity: sha512-iB0dQkIHXyczK3BZtzw1tqegf0F0Ab5texX2TvMQjiJIWXAfM4FQl7D909YfXWnB92OQz4ivBYQ2RlxBJrMJOw==} + /@esbuild/darwin-x64/0.17.12: + resolution: {integrity: sha512-5D48jM3tW27h1qjaD9UNRuN+4v0zvksqZSPZqeSWggfMlsVdAhH3pwSfQIFJwcs9QJ9BRibPS4ViZgs3d2wsCA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -367,8 +367,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64/0.17.11: - resolution: {integrity: sha512-7EFzUADmI1jCHeDRGKgbnF5sDIceZsQGapoO6dmw7r/ZBEKX7CCDnIz8m9yEclzr7mFsd+DyasHzpjfJnmBB1Q==} + /@esbuild/freebsd-arm64/0.17.12: + resolution: {integrity: sha512-OWvHzmLNTdF1erSvrfoEBGlN94IE6vCEaGEkEH29uo/VoONqPnoDFfShi41Ew+yKimx4vrmmAJEGNoyyP+OgOQ==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -376,8 +376,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64/0.17.11: - resolution: {integrity: sha512-iPgenptC8i8pdvkHQvXJFzc1eVMR7W2lBPrTE6GbhR54sLcF42mk3zBOjKPOodezzuAz/KSu8CPyFSjcBMkE9g==} + /@esbuild/freebsd-x64/0.17.12: + resolution: {integrity: sha512-A0Xg5CZv8MU9xh4a+7NUpi5VHBKh1RaGJKqjxe4KG87X+mTjDE6ZvlJqpWoeJxgfXHT7IMP9tDFu7IZ03OtJAw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -385,8 +385,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm/0.17.11: - resolution: {integrity: sha512-M9iK/d4lgZH0U5M1R2p2gqhPV/7JPJcRz+8O8GBKVgqndTzydQ7B2XGDbxtbvFkvIs53uXTobOhv+RyaqhUiMg==} + /@esbuild/linux-arm/0.17.12: + resolution: {integrity: sha512-WsHyJ7b7vzHdJ1fv67Yf++2dz3D726oO3QCu8iNYik4fb5YuuReOI9OtA+n7Mk0xyQivNTPbl181s+5oZ38gyA==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -394,8 +394,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64/0.17.11: - resolution: {integrity: sha512-Qxth3gsWWGKz2/qG2d5DsW/57SeA2AmpSMhdg9TSB5Svn2KDob3qxfQSkdnWjSd42kqoxIPy3EJFs+6w1+6Qjg==} + /@esbuild/linux-arm64/0.17.12: + resolution: {integrity: sha512-cK3AjkEc+8v8YG02hYLQIQlOznW+v9N+OI9BAFuyqkfQFR+DnDLhEM5N8QRxAUz99cJTo1rLNXqRrvY15gbQUg==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -403,8 +403,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32/0.17.11: - resolution: {integrity: sha512-dB1nGaVWtUlb/rRDHmuDQhfqazWE0LMro/AIbT2lWM3CDMHJNpLckH+gCddQyhhcLac2OYw69ikUMO34JLt3wA==} + /@esbuild/linux-ia32/0.17.12: + resolution: {integrity: sha512-jdOBXJqcgHlah/nYHnj3Hrnl9l63RjtQ4vn9+bohjQPI2QafASB5MtHAoEv0JQHVb/xYQTFOeuHnNYE1zF7tYw==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -412,8 +412,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.17.11: - resolution: {integrity: sha512-aCWlq70Q7Nc9WDnormntGS1ar6ZFvUpqr8gXtO+HRejRYPweAFQN615PcgaSJkZjhHp61+MNLhzyVALSF2/Q0g==} + /@esbuild/linux-loong64/0.17.12: + resolution: {integrity: sha512-GTOEtj8h9qPKXCyiBBnHconSCV9LwFyx/gv3Phw0pa25qPYjVuuGZ4Dk14bGCfGX3qKF0+ceeQvwmtI+aYBbVA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -421,8 +421,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el/0.17.11: - resolution: {integrity: sha512-cGeGNdQxqY8qJwlYH1BP6rjIIiEcrM05H7k3tR7WxOLmD1ZxRMd6/QIOWMb8mD2s2YJFNRuNQ+wjMhgEL2oCEw==} + /@esbuild/linux-mips64el/0.17.12: + resolution: {integrity: sha512-o8CIhfBwKcxmEENOH9RwmUejs5jFiNoDw7YgS0EJTF6kgPgcqLFjgoc5kDey5cMHRVCIWc6kK2ShUePOcc7RbA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -430,8 +430,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64/0.17.11: - resolution: {integrity: sha512-BdlziJQPW/bNe0E8eYsHB40mYOluS+jULPCjlWiHzDgr+ZBRXPtgMV1nkLEGdpjrwgmtkZHEGEPaKdS/8faLDA==} + /@esbuild/linux-ppc64/0.17.12: + resolution: {integrity: sha512-biMLH6NR/GR4z+ap0oJYb877LdBpGac8KfZoEnDiBKd7MD/xt8eaw1SFfYRUeMVx519kVkAOL2GExdFmYnZx3A==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -439,8 +439,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64/0.17.11: - resolution: {integrity: sha512-MDLwQbtF+83oJCI1Cixn68Et/ME6gelmhssPebC40RdJaect+IM+l7o/CuG0ZlDs6tZTEIoxUe53H3GmMn8oMA==} + /@esbuild/linux-riscv64/0.17.12: + resolution: {integrity: sha512-jkphYUiO38wZGeWlfIBMB72auOllNA2sLfiZPGDtOBb1ELN8lmqBrlMiucgL8awBw1zBXN69PmZM6g4yTX84TA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -448,8 +448,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x/0.17.11: - resolution: {integrity: sha512-4N5EMESvws0Ozr2J94VoUD8HIRi7X0uvUv4c0wpTHZyZY9qpaaN7THjosdiW56irQ4qnJ6Lsc+i+5zGWnyqWqQ==} + /@esbuild/linux-s390x/0.17.12: + resolution: {integrity: sha512-j3ucLdeY9HBcvODhCY4b+Ds3hWGO8t+SAidtmWu/ukfLLG/oYDMaA+dnugTVAg5fnUOGNbIYL9TOjhWgQB8W5g==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -457,8 +457,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64/0.17.11: - resolution: {integrity: sha512-rM/v8UlluxpytFSmVdbCe1yyKQd/e+FmIJE2oPJvbBo+D0XVWi1y/NQ4iTNx+436WmDHQBjVLrbnAQLQ6U7wlw==} + /@esbuild/linux-x64/0.17.12: + resolution: {integrity: sha512-uo5JL3cgaEGotaqSaJdRfFNSCUJOIliKLnDGWaVCgIKkHxwhYMm95pfMbWZ9l7GeW9kDg0tSxcy9NYdEtjwwmA==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -466,8 +466,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64/0.17.11: - resolution: {integrity: sha512-4WaAhuz5f91h3/g43VBGdto1Q+X7VEZfpcWGtOFXnggEuLvjV+cP6DyLRU15IjiU9fKLLk41OoJfBFN5DhPvag==} + /@esbuild/netbsd-x64/0.17.12: + resolution: {integrity: sha512-DNdoRg8JX+gGsbqt2gPgkgb00mqOgOO27KnrWZtdABl6yWTST30aibGJ6geBq3WM2TIeW6COs5AScnC7GwtGPg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -475,8 +475,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64/0.17.11: - resolution: {integrity: sha512-UBj135Nx4FpnvtE+C8TWGp98oUgBcmNmdYgl5ToKc0mBHxVVqVE7FUS5/ELMImOp205qDAittL6Ezhasc2Ev/w==} + /@esbuild/openbsd-x64/0.17.12: + resolution: {integrity: sha512-aVsENlr7B64w8I1lhHShND5o8cW6sB9n9MUtLumFlPhG3elhNWtE7M1TFpj3m7lT3sKQUMkGFjTQBrvDDO1YWA==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -484,8 +484,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64/0.17.11: - resolution: {integrity: sha512-1/gxTifDC9aXbV2xOfCbOceh5AlIidUrPsMpivgzo8P8zUtczlq1ncFpeN1ZyQJ9lVs2hILy1PG5KPp+w8QPPg==} + /@esbuild/sunos-x64/0.17.12: + resolution: {integrity: sha512-qbHGVQdKSwi0JQJuZznS4SyY27tYXYF0mrgthbxXrZI3AHKuRvU+Eqbg/F0rmLDpW/jkIZBlCO1XfHUBMNJ1pg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -493,8 +493,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64/0.17.11: - resolution: {integrity: sha512-vtSfyx5yRdpiOW9yp6Ax0zyNOv9HjOAw8WaZg3dF5djEHKKm3UnoohftVvIJtRh0Ec7Hso0RIdTqZvPXJ7FdvQ==} + /@esbuild/win32-arm64/0.17.12: + resolution: {integrity: sha512-zsCp8Ql+96xXTVTmm6ffvoTSZSV2B/LzzkUXAY33F/76EajNw1m+jZ9zPfNJlJ3Rh4EzOszNDHsmG/fZOhtqDg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -502,8 +502,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32/0.17.11: - resolution: {integrity: sha512-GFPSLEGQr4wHFTiIUJQrnJKZhZjjq4Sphf+mM76nQR6WkQn73vm7IsacmBRPkALfpOCHsopSvLgqdd4iUW2mYw==} + /@esbuild/win32-ia32/0.17.12: + resolution: {integrity: sha512-FfrFjR4id7wcFYOdqbDfDET3tjxCozUgbqdkOABsSFzoZGFC92UK7mg4JKRc/B3NNEf1s2WHxJ7VfTdVDPN3ng==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -511,8 +511,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64/0.17.11: - resolution: {integrity: sha512-N9vXqLP3eRL8BqSy8yn4Y98cZI2pZ8fyuHx6lKjiG2WABpT2l01TXdzq5Ma2ZUBzfB7tx5dXVhge8X9u0S70ZQ==} + /@esbuild/win32-x64/0.17.12: + resolution: {integrity: sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -1225,7 +1225,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.5 - caniuse-lite: 1.0.30001466 + caniuse-lite: 1.0.30001468 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -1302,8 +1302,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001466 - electron-to-chromium: 1.4.332 + caniuse-lite: 1.0.30001468 + electron-to-chromium: 1.4.333 node-releases: 2.0.10 update-browserslist-db: 1.0.10_browserslist@4.21.5 dev: true @@ -1377,13 +1377,13 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.21.5 - caniuse-lite: 1.0.30001466 + caniuse-lite: 1.0.30001468 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true - /caniuse-lite/1.0.30001466: - resolution: {integrity: sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==} + /caniuse-lite/1.0.30001468: + resolution: {integrity: sha512-zgAo8D5kbOyUcRAgSmgyuvBkjrGk5CGYG5TYgFdpQv+ywcyEpo1LOWoG8YmoflGnh+V+UsNuKYedsoYs0hzV5A==} dev: true /catharsis/0.9.0: @@ -1604,7 +1604,7 @@ packages: execa: 4.1.0 polyfill-library: 3.111.0 semver: 7.3.8 - snyk: 1.1119.0 + snyk: 1.1121.0 yargs: 15.4.1 transitivePeerDependencies: - supports-color @@ -1870,8 +1870,8 @@ packages: tslib: 2.5.0 dev: true - /electron-to-chromium/1.4.332: - resolution: {integrity: sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==} + /electron-to-chromium/1.4.333: + resolution: {integrity: sha512-YyE8+GKyGtPEP1/kpvqsdhD6rA/TP1DUFDN4uiU/YI52NzDxmwHkEb3qjId8hLBa5siJvG0sfC3O66501jMruQ==} dev: true /emoji-regex/7.0.3: @@ -1905,34 +1905,34 @@ packages: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} dev: true - /esbuild/0.17.11: - resolution: {integrity: sha512-pAMImyokbWDtnA/ufPxjQg0fYo2DDuzAlqwnDvbXqHLphe+m80eF++perYKVm8LeTuj2zUuFXC+xgSVxyoHUdg==} + /esbuild/0.17.12: + resolution: {integrity: sha512-bX/zHl7Gn2CpQwcMtRogTTBf9l1nl+H6R8nUbjk+RuKqAE3+8FDulLA+pHvX7aA7Xe07Iwa+CWvy9I8Y2qqPKQ==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.17.11 - '@esbuild/android-arm64': 0.17.11 - '@esbuild/android-x64': 0.17.11 - '@esbuild/darwin-arm64': 0.17.11 - '@esbuild/darwin-x64': 0.17.11 - '@esbuild/freebsd-arm64': 0.17.11 - '@esbuild/freebsd-x64': 0.17.11 - '@esbuild/linux-arm': 0.17.11 - '@esbuild/linux-arm64': 0.17.11 - '@esbuild/linux-ia32': 0.17.11 - '@esbuild/linux-loong64': 0.17.11 - '@esbuild/linux-mips64el': 0.17.11 - '@esbuild/linux-ppc64': 0.17.11 - '@esbuild/linux-riscv64': 0.17.11 - '@esbuild/linux-s390x': 0.17.11 - '@esbuild/linux-x64': 0.17.11 - '@esbuild/netbsd-x64': 0.17.11 - '@esbuild/openbsd-x64': 0.17.11 - '@esbuild/sunos-x64': 0.17.11 - '@esbuild/win32-arm64': 0.17.11 - '@esbuild/win32-ia32': 0.17.11 - '@esbuild/win32-x64': 0.17.11 + '@esbuild/android-arm': 0.17.12 + '@esbuild/android-arm64': 0.17.12 + '@esbuild/android-x64': 0.17.12 + '@esbuild/darwin-arm64': 0.17.12 + '@esbuild/darwin-x64': 0.17.12 + '@esbuild/freebsd-arm64': 0.17.12 + '@esbuild/freebsd-x64': 0.17.12 + '@esbuild/linux-arm': 0.17.12 + '@esbuild/linux-arm64': 0.17.12 + '@esbuild/linux-ia32': 0.17.12 + '@esbuild/linux-loong64': 0.17.12 + '@esbuild/linux-mips64el': 0.17.12 + '@esbuild/linux-ppc64': 0.17.12 + '@esbuild/linux-riscv64': 0.17.12 + '@esbuild/linux-s390x': 0.17.12 + '@esbuild/linux-x64': 0.17.12 + '@esbuild/netbsd-x64': 0.17.12 + '@esbuild/openbsd-x64': 0.17.12 + '@esbuild/sunos-x64': 0.17.12 + '@esbuild/win32-arm64': 0.17.12 + '@esbuild/win32-ia32': 0.17.12 + '@esbuild/win32-x64': 0.17.12 dev: true /escalade/3.1.1: @@ -4095,8 +4095,8 @@ packages: supports-color: 7.2.0 dev: true - /snyk/1.1119.0: - resolution: {integrity: sha512-kcc3TmAwpA1p4gz7Myf1DcoM6e1FWc/1iBuAEPOzXaGmuy6j8lvy6g00Fj/fHKK+ZRBkdqun6lBxEqfSM9utsQ==} + /snyk/1.1121.0: + resolution: {integrity: sha512-0uGoG/8xOWopWB5OdJYGDx2h1JcNbWCDS19CHy59QfDuQNXcAPXUffxCfmyDD8KzDeCv9HfLF66lNcCqSjr+gA==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -4610,7 +4610,7 @@ packages: terser: optional: true dependencies: - esbuild: 0.17.11 + esbuild: 0.17.12 postcss: 8.4.21 resolve: 1.22.1 rollup: 3.19.1 @@ -4644,7 +4644,7 @@ packages: optional: true dependencies: '@types/node': 18.15.3 - esbuild: 0.17.11 + esbuild: 0.17.12 postcss: 8.4.21 resolve: 1.22.1 rollup: 3.19.1 diff --git a/development/test/cases/text/formatter.mjs b/development/test/cases/text/formatter.mjs index 874bb328ce4e2f768d6206dc1428dc4fd68f6192..6a00fa2fc777c30975b492ae26ec5a2f6cefd47f 100644 --- a/development/test/cases/text/formatter.mjs +++ b/development/test/cases/text/formatter.mjs @@ -204,4 +204,84 @@ describe('Formatter', function () { }); + + + describe('Formatter', () => { + it('should format a basic string with object values', () => { + const formatter = new Formatter({name: 'John', age: 30}); + const result = formatter.format('My name is ${name} and I am ${age | tostring} years old.'); + + expect(result).to.equal('My name is John and I am 30 years old.'); + }); + + it('should format a string with nested markers', () => { + const text = '${mykey${subkey}}'; + const obj = {mykey2: '1', subkey: '2'}; + const formatter = new Formatter(obj); + + expect(formatter.format(text)).to.equal('1'); + }); + + it('should format a string with custom markers', () => { + const formatter = new Formatter({name: 'John', age: 30}); + formatter.setMarker('[', ']'); + const result = formatter.format('My name is [name] and I am [age | tostring] years old.'); + + expect(result).to.equal('My name is John and I am 30 years old.'); + }); + + it('should format a string using callback', () => { + const formatter = new Formatter({x: '1'}, { + callbacks: { + quote: (value) => { + return '"' + value + '"'; + }, + }, + }); + + expect(formatter.format('${x | call:quote}')).to.equal('"1"'); + }); + + it('should format a string with parameters', () => { + const obj = { + a: { + b: { + c: 'Hello', + }, + d: 'world', + }, + }; + const formatter = new Formatter(obj); + const result = formatter.format('${a.b.c} ${a.d | ucfirst}!'); + + expect(result).to.equal('Hello World!'); + }); + + it('should throw a too deep nesting error', () => { + const formatter = new Formatter({name: 'John'}); + const nestedText = '${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name}}}}}}}}}}}}}}}}}}'; + expect(() => formatter.format(nestedText)).to.throw('syntax error in formatter template'); + }); + + it('should throw a too deep nesting error', () => { + const inputObj = { + mykey: '${mykey}', + }; + + const formatter = new Formatter(inputObj); + + const text = '${mykey}'; + let formattedText = text; + + // Create a string with 21 levels of nesting + for (let i = 0; i < 21; i++) { + formattedText = '${' + formattedText + '}'; + } + + expect(() => formatter.format(formattedText)).to.throw('too deep nesting'); + }); + + }); + + }); \ No newline at end of file diff --git a/development/test/cases/text/util.mjs b/development/test/cases/text/util.mjs new file mode 100644 index 0000000000000000000000000000000000000000..4cef64c1fdc020c48a359c924affb1a8e15038dd --- /dev/null +++ b/development/test/cases/text/util.mjs @@ -0,0 +1,109 @@ +import {expect} from "chai" +import {generateRangeComparisonExpression} from "../../../../application/source/text/util.mjs"; + +describe('generateRangeComparisonExpression', () => { + it('should generate correct comparison expression for single values', () => { + const expression = '1,3,5'; + const valueName = 'x'; + const result = generateRangeComparisonExpression(expression, valueName); + expect(result).to.equal('(x==1) || (x==3) || (x==5)'); + }); + + it('should generate correct comparison expression for ranges', () => { + const expression = '1-3,6-8'; + const valueName = 'x'; + const result = generateRangeComparisonExpression(expression, valueName); + expect(result).to.equal('(x>=1 && x<=3) || (x>=6 && x<=8)'); + }); + + it('should generate correct comparison expression for mixed ranges and single values', () => { + const expression = '1-3,5,7-9'; + const valueName = 'x'; + const result = generateRangeComparisonExpression(expression, valueName); + expect(result).to.equal('(x>=1 && x<=3) || (x==5) || (x>=7 && x<=9)'); + }); + + it('should throw an error for invalid range', () => { + const expression = '1-3,5-4'; + const valueName = 'x'; + expect(() => generateRangeComparisonExpression(expression, valueName)).to.throw(`Invalid range '5-4'`); + }); + + + it('should throw an error for invalid value', () => { + const expression = '1-3,a'; + const valueName = 'x'; + expect(() => generateRangeComparisonExpression(expression, valueName)).to.throw('Invalid value'); + }); + + it('should generate correct comparison expression with custom operators', () => { + const expression = '1-3,5'; + const valueName = 'x'; + const options = { + andOp: 'AND', + orOp: 'OR', + eqOp: '===', + geOp: '>=', + leOp: '<=', + }; + const result = generateRangeComparisonExpression(expression, valueName, options); + expect(result).to.equal('(x>=1 AND x<=3) OR (x===5)'); + }); + + it('should generate correct comparison expression with urlEncode option', () => { + const testCases = [ + { + expression: '1,3,5', + valueName: 'x', + expected: '(x%3D%3D1) || (x%3D%3D3) || (x%3D%3D5)', + }, + { + expression: '-10', + valueName: 'x', + expected: 'x%3C%3D10', + }, + { + expression: '10-', + valueName: 'x', + expected: 'x%3E%3D10', + }, + { + expression: '1-3,6-8', + valueName: 'y', + expected: '(y%3E%3D1 && y%3C%3D3) || (y%3E%3D6 && y%3C%3D8)', + }, + { + expression: '1-3,5,7-9', + valueName: 'z', + expected: '(z%3E%3D1 && z%3C%3D3) || (z%3D%3D5) || (z%3E%3D7 && z%3C%3D9)', + }, + ]; + + testCases.forEach(({expression, valueName, expected}) => { + const result = generateRangeComparisonExpression(expression, valueName, {urlEncode: true}); + expect(result).to.equal(expected); + }); + }); + + it('should generate correct comparison expression for open-ended ranges with urlEncode option', () => { + const testCases = [ + { + expression: '10-', + valueName: 'x', + expected: 'x%3E%3D10', + }, + { + expression: '-10', + valueName: 'y', + expected: 'y%3C%3D10', + }, + ]; + + testCases.forEach(({expression, valueName, expected}) => { + const result = generateRangeComparisonExpression(expression, valueName, {urlEncode: true}); + expect(result).to.equal(expected); + }); + }); + + +});