From 3a905c1787275a3aff165d7dacc771be73d6c8fe Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Thu, 23 Jan 2025 19:12:11 +0100 Subject: [PATCH] feat(transformer): new command ellipsize --- source/data/transformer.mjs | 1623 ++++++++++++++++++----------------- 1 file changed, 818 insertions(+), 805 deletions(-) diff --git a/source/data/transformer.mjs b/source/data/transformer.mjs index f2caccab7..d9383d528 100644 --- a/source/data/transformer.mjs +++ b/source/data/transformer.mjs @@ -12,28 +12,28 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { getLocaleOfDocument } from "../dom/locale.mjs"; -import { Base } from "../types/base.mjs"; -import { getGlobal, getGlobalObject } from "../types/global.mjs"; -import { ID } from "../types/id.mjs"; -import { isArray, isObject, isString, isPrimitive } from "../types/is.mjs"; +import {getLocaleOfDocument} from "../dom/locale.mjs"; +import {Base} from "../types/base.mjs"; +import {getGlobal, getGlobalObject} from "../types/global.mjs"; +import {ID} from "../types/id.mjs"; +import {isArray, isObject, isString, isPrimitive} from "../types/is.mjs"; import { - getDocumentTranslations, - Translations, + getDocumentTranslations, + Translations, } from "../i18n/translations.mjs"; import { - validateFunction, - validateInteger, - validateObject, - validatePrimitive, - validateString, - validateBoolean, + validateFunction, + validateInteger, + validateObject, + validatePrimitive, + validateString, + validateBoolean, } from "../types/validate.mjs"; -import { clone } from "../util/clone.mjs"; -import { Pathfinder } from "./pathfinder.mjs"; -import { formatTimeAgo } from "../i18n/time-ago.mjs"; +import {clone} from "../util/clone.mjs"; +import {Pathfinder} from "./pathfinder.mjs"; +import {formatTimeAgo} from "../i18n/time-ago.mjs"; -export { Transformer }; +export {Transformer}; /** * The transformer class is a swiss army knife for manipulating values. @@ -53,53 +53,53 @@ export { Transformer }; * @summary The transformer class is a swiss army knife for manipulating values. especially in combination with the pipe, processing chains can be built up. */ class Transformer extends Base { - /** - * - * @param {string} definition - */ - constructor(definition) { - super(); - this.args = disassemble(definition); - this.command = this.args.shift(); - this.callbacks = new Map(); - } - - /** - * - * @param {string} name - * @param {function} callback - * @param {object} context - * @return {Transformer} - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not a function - */ - setCallback(name, callback, context) { - validateString(name); - validateFunction(callback); - - if (context !== undefined) { - validateObject(context); - } - - this.callbacks.set(name, { - callback: callback, - context: context, - }); - - return this; - } - - /** - * - * @param {*} value - * @return {*} - * @throws {Error} unknown command - * @throws {TypeError} unsupported type - * @throws {Error} type not supported - */ - run(value) { - return transform.apply(this, [value]); - } + /** + * + * @param {string} definition + */ + constructor(definition) { + super(); + this.args = disassemble(definition); + this.command = this.args.shift(); + this.callbacks = new Map(); + } + + /** + * + * @param {string} name + * @param {function} callback + * @param {object} context + * @return {Transformer} + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not a function + */ + setCallback(name, callback, context) { + validateString(name); + validateFunction(callback); + + if (context !== undefined) { + validateObject(context); + } + + this.callbacks.set(name, { + callback: callback, + context: context, + }); + + return this; + } + + /** + * + * @param {*} value + * @return {*} + * @throws {Error} unknown command + * @throws {TypeError} unsupported type + * @throws {Error} type not supported + */ + run(value) { + return transform.apply(this, [value]); + } } /** @@ -109,41 +109,41 @@ class Transformer extends Base { * @private */ function disassemble(command) { - validateString(command); - - const placeholder = new Map(); - const regex = /((?<pattern>\\(?<char>.)){1})/gim; - - // The separator for args must be escaped - // undefined string which should not occur normally and is also not a regex - const result = command.matchAll(regex); - - for (const m of result) { - const g = m?.["groups"]; - if (!isObject(g)) { - continue; - } - - const p = g?.["pattern"]; - const c = g?.["char"]; - - if (p && c) { - const r = `__${new ID().toString()}__`; - placeholder.set(r, c); - command = command.replace(p, r); - } - } - let parts = command.split(":"); - - parts = parts.map(function (value) { - let v = value.trim(); - for (const k of placeholder) { - v = v.replace(k[0], k[1]); - } - return v; - }); - - return parts; + validateString(command); + + const placeholder = new Map(); + const regex = /((?<pattern>\\(?<char>.)){1})/gim; + + // The separator for args must be escaped + // undefined string which should not occur normally and is also not a regex + const result = command.matchAll(regex); + + for (const m of result) { + const g = m?.["groups"]; + if (!isObject(g)) { + continue; + } + + const p = g?.["pattern"]; + const c = g?.["char"]; + + if (p && c) { + const r = `__${new ID().toString()}__`; + placeholder.set(r, c); + command = command.replace(p, r); + } + } + let parts = command.split(":"); + + parts = parts.map(function (value) { + let v = value.trim(); + for (const k of placeholder) { + v = v.replace(k[0], k[1]); + } + return v; + }); + + return parts; } /** @@ -154,12 +154,12 @@ function disassemble(command) { * @private */ function convertToString(value) { - if (isObject(value) && value.hasOwnProperty("toString")) { - value = value.toString(); - } + if (isObject(value) && value.hasOwnProperty("toString")) { + value = value.toString(); + } - validateString(value); - return value; + validateString(value); + return value; } /** @@ -173,682 +173,695 @@ function convertToString(value) { * @throws {Error} missing key parameter */ function transform(value) { - const console = getGlobalObject("console"); - - const args = clone(this.args); - let key; - let defaultValue; - - let translations; - let date; - let locale; - let timestamp; - let map; - let keyValue; - - switch (this.command) { - case "static": - return this.args.join(":"); - - case "tolower": - case "strtolower": - case "tolowercase": - validateString(value); - return value.toLowerCase(); - - case "contains": - if (isString(value)) { - return value.includes(args[0]); - } - - if (isArray(value)) { - return value.includes(args[0]); - } - - if (isObject(value)) { - return value.hasOwnProperty(args[0]); - } - - return false; - - case "has-entries": - case "hasentries": - if (isObject(value)) { - return Object.keys(value).length > 0; - } - - if (isArray(value)) { - return value.length > 0; - } - - return false; - - case "isundefined": - case "is-undefined": - return value === undefined; - - case "isnull": - case "is-null": - return value === null; - - case "isset": - case "is-set": - return value !== undefined && value !== null; - - case "isnumber": - case "is-number": - return isPrimitive(value) && !isNaN(value); - - case "isinteger": - case "is-integer": - return isPrimitive(value) && !isNaN(value) && value % 1 === 0; - - case "isfloat": - case "is-float": - return isPrimitive(value) && !isNaN(value) && value % 1 !== 0; - - case "isobject": - case "is-object": - return isObject(value); - - case "isarray": - case "is-array": - return Array.isArray(value); - - case "not": - validateBoolean(value); - return !value; - - case "toupper": - case "strtoupper": - case "touppercase": - validateString(value); - return value.toUpperCase(); - - case "to-string": - case "tostring": - return `${value}`; - - case "to-integer": - case "tointeger": - const n = parseInt(value); - validateInteger(n); - return n; - - case "to-json": - case "tojson": - return JSON.stringify(value); - - case "from-json": - case "fromjson": - return JSON.parse(value); - - case "trim": - validateString(value); - return value.trim(); - - case "rawurlencode": - validateString(value); - return encodeURIComponent(value) - .replace(/!/g, "%21") - .replace(/'/g, "%27") - .replace(/\(/g, "%28") - .replace(/\)/g, "%29") - .replace(/\*/g, "%2A"); - - case "call": - /** - * callback-definition - * function callback(value, ...args) { - * return value; - * } - */ - - let callback; - const callbackName = args.shift(); - let context = getGlobal(); - - if (isObject(value) && value.hasOwnProperty(callbackName)) { - callback = value[callbackName]; - } else if (this.callbacks.has(callbackName)) { - const s = this.callbacks.get(callbackName); - callback = s?.["callback"]; - context = s?.["context"]; - } else if ( - typeof window === "object" && - window.hasOwnProperty(callbackName) - ) { - callback = window[callbackName]; - } - validateFunction(callback); - - args.unshift(value); - return callback.call(context, ...args); - - case "plain": - case "plaintext": - validateString(value); - const doc = new DOMParser().parseFromString(value, "text/html"); - return doc.body.textContent || ""; - - case "if": - case "?": - validatePrimitive(value); - - let trueStatement = args.shift() || undefined; - let falseStatement = args.shift() || undefined; - - trueStatement = convertSpecialStrings(trueStatement, value); - falseStatement = convertSpecialStrings(falseStatement, value); - - const condition = evaluateCondition(value); - return condition ? trueStatement : falseStatement; - - case "ucfirst": - validateString(value); - - const firstchar = value.charAt(0).toUpperCase(); - return firstchar + value.substr(1); - case "ucwords": - validateString(value); - - return value.replace( - /^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, - function (v) { - return v.toUpperCase(); - }, - ); - - case "count": - case "length": - if ( - (isString(value) || isObject(value) || isArray(value)) && - value.hasOwnProperty("length") - ) { - return value.length; - } - - throw new TypeError(`unsupported type ${typeof value}`); - - case "to-base64": - case "btoa": - case "base64": - return btoa(convertToString(value)); - - case "atob": - case "from-base64": - return atob(convertToString(value)); - - case "empty": - return ""; - - case "undefined": - return undefined; - - case "debug": - if (isObject(console)) { - console.log(value); - } - - return value; - - case "prefix": - validateString(value); - const prefix = args?.[0]; - return prefix + value; - - case "suffix": - validateString(value); - const suffix = args?.[0]; - return value + suffix; - - case "uniqid": - return new ID().toString(); - - case "first-key": - case "last-key": - case "nth-last-key": - case "nth-key": - if (!isObject(value)) { - throw new Error("type not supported"); - } - - const keys = Object.keys(value).sort(); - - if (this.command === "first-key") { - key = 0; - } else if (this.command === "last-key") { - key = keys.length - 1; - } else { - key = validateInteger(parseInt(args.shift())); - - if (this.command === "nth-last-key") { - key = keys.length - key - 1; - } - } - - defaultValue = args.shift() || ""; - - const useKey = keys?.[key]; - - if (value?.[useKey]) { - return value?.[useKey]; - } - - return defaultValue; - - case "key": - case "property": - case "index": - key = args.shift() || undefined; - - if (key === undefined) { - throw new Error("missing key parameter"); - } - - defaultValue = args.shift() || undefined; - - if (value instanceof Map) { - if (!value.has(key)) { - return defaultValue; - } - return value.get(key); - } - - if (isObject(value) || isArray(value)) { - if (value?.[key]) { - return value?.[key]; - } - - return defaultValue; - } - - throw new Error("type not supported"); - - case "path-exists": - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - return new Pathfinder(value).exists(key); - - case "concat": - const pf2 = new Pathfinder(value); - let concat = ""; - while (args.length > 0) { - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - // add empty strings - if (isString(key) && key.trim() === "") { - concat += key; - continue; - } - - if (!pf2.exists(key)) { - concat += key; - continue; - } - const v = pf2.getVia(key); - if (!isPrimitive(v)) { - throw new Error("value is not primitive"); - } - - concat += v; - } - - return concat; - case "path": - key = args.shift(); - if (key === undefined) { - throw new Error("missing key parameter"); - } - - const pf = new Pathfinder(value); - - if (!pf.exists(key)) { - return undefined; - } - - return pf.getVia(key); - - case "substring": - validateString(value); - - const start = parseInt(args[0]) || 0; - const end = (parseInt(args[1]) || 0) + start; - - return value.substring(start, end); - - case "nop": - return value; - - case "??": - case "default": - if (value !== undefined && value !== null) { - return value; - } - - defaultValue = args.shift(); - let defaultType = args.shift(); - if (defaultType === undefined) { - defaultType = "string"; - } - - switch (defaultType) { - case "int": - case "integer": - return parseInt(defaultValue); - case "float": - return parseFloat(defaultValue); - case "undefined": - return undefined; - case "bool": - case "boolean": - defaultValue = defaultValue.toLowerCase(); - return ( - (defaultValue !== "undefined" && - defaultValue !== "" && - defaultValue !== "off" && - defaultValue !== "false" && - defaultValue !== "false") || - defaultValue === "on" || - defaultValue === "true" || - defaultValue === "true" - ); - case "string": - return `${defaultValue}`; - case "object": - return JSON.parse(atob(defaultValue)); - } - - throw new Error("type not supported"); - - case "map": - map = new Map(); - while (args.length > 0) { - keyValue = args.shift(); - if (keyValue === undefined) { - throw new Error("missing key parameter"); - } - - keyValue = keyValue.split("="); - map.set(keyValue[0], keyValue[1]); - } - - return map.get(value); - - case "equals": - if (args.length === 0) { - throw new Error("missing value parameter"); - } - - validatePrimitive(value); - - const equalsValue = args.shift(); - - /** - * The history of “typeof null” - * https://2ality.com/2013/10/typeof-null.html - * In JavaScript, typeof null is 'object', which incorrectly suggests - * that null is an object. - */ - if (value === null) { - return equalsValue === "null"; - } - - const typeOfValue = typeof value; - - switch (typeOfValue) { - case "string": - return value === equalsValue; - case "number": - return value === parseFloat(equalsValue); - case "boolean": - return value === (equalsValue === "true" || equalsValue === "on"); - case "undefined": - return equalsValue === "undefined"; - default: - throw new Error("type not supported"); - } - - case "money": - case "currency": - try { - locale = getLocaleOfDocument(); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - // Verwenden von RegExp, um Währung und Betrag zu extrahieren - const match = value.match(/^([A-Z]{3})[\s-]*(\d+(\.\d+)?)$/); - if (!match) { - throw new Error("invalid currency format"); - } - - const currency = match[1]; - const amount = match[2]; - - const maximumFractionDigits = args?.[0] || 2; - const roundingIncrement = args?.[1] || 5; - - const nf = new Intl.NumberFormat(locale.toString(), { - style: "currency", - currency: currency, - maximumFractionDigits: maximumFractionDigits, - roundingIncrement: roundingIncrement, - }); - - return nf.format(amount); - - case "timestamp": - date = new Date(value); - timestamp = date.getTime(); - if (isNaN(timestamp)) { - throw new Error("invalid date"); - } - return timestamp; - - case "time": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleTimeString(locale.toString(), { - hour12: false, - }); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "datetimeformat": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - const options = { - dateStyle: "medium", - timeStyle: "medium", - hour12: false, - }; - - if (args.length > 0) { - options.dateStyle = args.shift(); - } - - if (args.length > 0) { - options.timeStyle = args.shift(); - } - - try { - locale = getLocaleOfDocument().toString(); - return new Intl.DateTimeFormat(locale, options).format(date); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "datetime": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleString(locale.toString(), { - hour12: false, - }); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "date": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return date.toLocaleDateString(locale.toString(), { - year: "numeric", - month: "2-digit", - day: "2-digit", - }); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "time-ago": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - try { - locale = getLocaleOfDocument(); - return formatTimeAgo(date, locale.toString()); - } catch (e) { - throw new Error(`unsupported locale or missing format (${e.message})`); - } - - case "year": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getFullYear(); - - case "month": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getMonth() + 1; - - case "day": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getDate(); - - case "weekday": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getDay(); - - case "hour": - case "hours": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getHours(); - - case "minute": - case "minutes": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getMinutes(); - - case "second": - case "seconds": - date = new Date(value); - if (isNaN(date.getTime())) { - throw new Error("invalid date"); - } - - return date.getSeconds(); - - case "i18n": - case "translation": - translations = getDocumentTranslations(); - if (!(translations instanceof Translations)) { - throw new Error("missing translations"); - } - - key = args.shift() || undefined; - if (key === undefined) { - key = value; - } - - defaultValue = args.shift() || undefined; - - defaultValue = convertSpecialStrings(defaultValue, value); - - return translations.getText(key, defaultValue); - - case "set-toggle": - case "set-set": - case "set-remove": - const modifier = args.shift(); - let delimiter = args.shift(); - if (delimiter === undefined) { - delimiter = " "; - } - - const set = new Set(value.split(delimiter)); - const toggle = new Set(modifier.split(delimiter)); - if (this.command === "set-toggle") { - for (const t of toggle) { - if (set.has(t)) { - set.delete(t); - } else { - set.add(t); - } - } - } else if (this.command === "set-set") { - for (const t of toggle) { - set.add(t); - } - } else if (this.command === "set-remove") { - for (const t of toggle) { - set.delete(t); - } - } - return Array.from(set).join(delimiter); - - default: - throw new Error(`unknown command ${this.command}`); - } + const console = getGlobalObject("console"); + + const args = clone(this.args); + let key; + let defaultValue; + + let translations; + let date; + let locale; + let timestamp; + let map; + let keyValue; + + switch (this.command) { + case "static": + return this.args.join(":"); + + case "tolower": + case "strtolower": + case "tolowercase": + validateString(value); + return value.toLowerCase(); + + case "contains": + if (isString(value)) { + return value.includes(args[0]); + } + + if (isArray(value)) { + return value.includes(args[0]); + } + + if (isObject(value)) { + return value.hasOwnProperty(args[0]); + } + + return false; + + case "has-entries": + case "hasentries": + if (isObject(value)) { + return Object.keys(value).length > 0; + } + + if (isArray(value)) { + return value.length > 0; + } + + return false; + + case "isundefined": + case "is-undefined": + return value === undefined; + + case "isnull": + case "is-null": + return value === null; + + case "isset": + case "is-set": + return value !== undefined && value !== null; + + case "isnumber": + case "is-number": + return isPrimitive(value) && !isNaN(value); + + case "isinteger": + case "is-integer": + return isPrimitive(value) && !isNaN(value) && value % 1 === 0; + + case "isfloat": + case "is-float": + return isPrimitive(value) && !isNaN(value) && value % 1 !== 0; + + case "isobject": + case "is-object": + return isObject(value); + + case "isarray": + case "is-array": + return Array.isArray(value); + + case "not": + validateBoolean(value); + return !value; + + case "toupper": + case "strtoupper": + case "touppercase": + validateString(value); + return value.toUpperCase(); + + case "to-string": + case "tostring": + return `${value}`; + + case "to-integer": + case "tointeger": + const n = parseInt(value); + validateInteger(n); + return n; + + case "to-json": + case "tojson": + return JSON.stringify(value); + + case "from-json": + case "fromjson": + return JSON.parse(value); + + case "trim": + validateString(value); + return value.trim(); + + case "rawurlencode": + validateString(value); + return encodeURIComponent(value) + .replace(/!/g, "%21") + .replace(/'/g, "%27") + .replace(/\(/g, "%28") + .replace(/\)/g, "%29") + .replace(/\*/g, "%2A"); + + case "call": + /** + * callback-definition + * function callback(value, ...args) { + * return value; + * } + */ + + let callback; + const callbackName = args.shift(); + let context = getGlobal(); + + if (isObject(value) && value.hasOwnProperty(callbackName)) { + callback = value[callbackName]; + } else if (this.callbacks.has(callbackName)) { + const s = this.callbacks.get(callbackName); + callback = s?.["callback"]; + context = s?.["context"]; + } else if ( + typeof window === "object" && + window.hasOwnProperty(callbackName) + ) { + callback = window[callbackName]; + } + validateFunction(callback); + + args.unshift(value); + return callback.call(context, ...args); + + case "plain": + case "plaintext": + validateString(value); + const doc = new DOMParser().parseFromString(value, "text/html"); + return doc.body.textContent || ""; + + case "if": + case "?": + validatePrimitive(value); + + let trueStatement = args.shift() || undefined; + let falseStatement = args.shift() || undefined; + + trueStatement = convertSpecialStrings(trueStatement, value); + falseStatement = convertSpecialStrings(falseStatement, value); + + const condition = evaluateCondition(value); + return condition ? trueStatement : falseStatement; + + case "ucfirst": + validateString(value); + + const firstchar = value.charAt(0).toUpperCase(); + return firstchar + value.substr(1); + case "ucwords": + validateString(value); + + return value.replace( + /^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, + function (v) { + return v.toUpperCase(); + }, + ); + + case "count": + case "length": + if ( + (isString(value) || isObject(value) || isArray(value)) && + value.hasOwnProperty("length") + ) { + return value.length; + } + + throw new TypeError(`unsupported type ${typeof value}`); + + case "to-base64": + case "btoa": + case "base64": + return btoa(convertToString(value)); + + case "atob": + case "from-base64": + return atob(convertToString(value)); + + case "empty": + return ""; + + case "undefined": + return undefined; + + case "debug": + if (isObject(console)) { + console.log(value); + } + + return value; + + case "prefix": + validateString(value); + const prefix = args?.[0]; + return prefix + value; + + case "suffix": + validateString(value); + const suffix = args?.[0]; + return value + suffix; + + case "uniqid": + return new ID().toString(); + + case "first-key": + case "last-key": + case "nth-last-key": + case "nth-key": + if (!isObject(value)) { + throw new Error("type not supported"); + } + + const keys = Object.keys(value).sort(); + + if (this.command === "first-key") { + key = 0; + } else if (this.command === "last-key") { + key = keys.length - 1; + } else { + key = validateInteger(parseInt(args.shift())); + + if (this.command === "nth-last-key") { + key = keys.length - key - 1; + } + } + + defaultValue = args.shift() || ""; + + const useKey = keys?.[key]; + + if (value?.[useKey]) { + return value?.[useKey]; + } + + return defaultValue; + + case "key": + case "property": + case "index": + key = args.shift() || undefined; + + if (key === undefined) { + throw new Error("missing key parameter"); + } + + defaultValue = args.shift() || undefined; + + if (value instanceof Map) { + if (!value.has(key)) { + return defaultValue; + } + return value.get(key); + } + + if (isObject(value) || isArray(value)) { + if (value?.[key]) { + return value?.[key]; + } + + return defaultValue; + } + + throw new Error("type not supported"); + + case "path-exists": + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + return new Pathfinder(value).exists(key); + + case "concat": + const pf2 = new Pathfinder(value); + let concat = ""; + while (args.length > 0) { + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + // add empty strings + if (isString(key) && key.trim() === "") { + concat += key; + continue; + } + + if (!pf2.exists(key)) { + concat += key; + continue; + } + const v = pf2.getVia(key); + if (!isPrimitive(v)) { + throw new Error("value is not primitive"); + } + + concat += v; + } + + return concat; + case "path": + key = args.shift(); + if (key === undefined) { + throw new Error("missing key parameter"); + } + + const pf = new Pathfinder(value); + + if (!pf.exists(key)) { + return undefined; + } + + return pf.getVia(key); + + case 'ellipsize': + case 'ellipsis': + case 'ellipse': + + validateString(value); + const length = parseInt(args[0]) || 0; + const ellipsis = args[1] || '…'; + if (value.length <= length) { + return value; + } + return value.substring(0, length) + ellipsis; + + + case "substring": + validateString(value); + + const start = parseInt(args[0]) || 0; + const end = (parseInt(args[1]) || 0) + start; + + return value.substring(start, end); + + case "nop": + return value; + + case "??": + case "default": + if (value !== undefined && value !== null) { + return value; + } + + defaultValue = args.shift(); + let defaultType = args.shift(); + if (defaultType === undefined) { + defaultType = "string"; + } + + switch (defaultType) { + case "int": + case "integer": + return parseInt(defaultValue); + case "float": + return parseFloat(defaultValue); + case "undefined": + return undefined; + case "bool": + case "boolean": + defaultValue = defaultValue.toLowerCase(); + return ( + (defaultValue !== "undefined" && + defaultValue !== "" && + defaultValue !== "off" && + defaultValue !== "false" && + defaultValue !== "false") || + defaultValue === "on" || + defaultValue === "true" || + defaultValue === "true" + ); + case "string": + return `${defaultValue}`; + case "object": + return JSON.parse(atob(defaultValue)); + } + + throw new Error("type not supported"); + + case "map": + map = new Map(); + while (args.length > 0) { + keyValue = args.shift(); + if (keyValue === undefined) { + throw new Error("missing key parameter"); + } + + keyValue = keyValue.split("="); + map.set(keyValue[0], keyValue[1]); + } + + return map.get(value); + + case "equals": + if (args.length === 0) { + throw new Error("missing value parameter"); + } + + validatePrimitive(value); + + const equalsValue = args.shift(); + + /** + * The history of “typeof null” + * https://2ality.com/2013/10/typeof-null.html + * In JavaScript, typeof null is 'object', which incorrectly suggests + * that null is an object. + */ + if (value === null) { + return equalsValue === "null"; + } + + const typeOfValue = typeof value; + + switch (typeOfValue) { + case "string": + return value === equalsValue; + case "number": + return value === parseFloat(equalsValue); + case "boolean": + return value === (equalsValue === "true" || equalsValue === "on"); + case "undefined": + return equalsValue === "undefined"; + default: + throw new Error("type not supported"); + } + + case "money": + case "currency": + try { + locale = getLocaleOfDocument(); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + // Verwenden von RegExp, um Währung und Betrag zu extrahieren + const match = value.match(/^([A-Z]{3})[\s-]*(\d+(\.\d+)?)$/); + if (!match) { + throw new Error("invalid currency format"); + } + + const currency = match[1]; + const amount = match[2]; + + const maximumFractionDigits = args?.[0] || 2; + const roundingIncrement = args?.[1] || 5; + + const nf = new Intl.NumberFormat(locale.toString(), { + style: "currency", + currency: currency, + maximumFractionDigits: maximumFractionDigits, + roundingIncrement: roundingIncrement, + }); + + return nf.format(amount); + + case "timestamp": + date = new Date(value); + timestamp = date.getTime(); + if (isNaN(timestamp)) { + throw new Error("invalid date"); + } + return timestamp; + + case "time": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleTimeString(locale.toString(), { + hour12: false, + }); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "datetimeformat": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + const options = { + dateStyle: "medium", + timeStyle: "medium", + hour12: false, + }; + + if (args.length > 0) { + options.dateStyle = args.shift(); + } + + if (args.length > 0) { + options.timeStyle = args.shift(); + } + + try { + locale = getLocaleOfDocument().toString(); + return new Intl.DateTimeFormat(locale, options).format(date); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "datetime": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleString(locale.toString(), { + hour12: false, + }); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "date": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return date.toLocaleDateString(locale.toString(), { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "time-ago": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + try { + locale = getLocaleOfDocument(); + return formatTimeAgo(date, locale.toString()); + } catch (e) { + throw new Error(`unsupported locale or missing format (${e.message})`); + } + + case "year": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getFullYear(); + + case "month": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getMonth() + 1; + + case "day": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getDate(); + + case "weekday": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getDay(); + + case "hour": + case "hours": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getHours(); + + case "minute": + case "minutes": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getMinutes(); + + case "second": + case "seconds": + date = new Date(value); + if (isNaN(date.getTime())) { + throw new Error("invalid date"); + } + + return date.getSeconds(); + + case "i18n": + case "translation": + translations = getDocumentTranslations(); + if (!(translations instanceof Translations)) { + throw new Error("missing translations"); + } + + key = args.shift() || undefined; + if (key === undefined) { + key = value; + } + + defaultValue = args.shift() || undefined; + + defaultValue = convertSpecialStrings(defaultValue, value); + + return translations.getText(key, defaultValue); + + case "set-toggle": + case "set-set": + case "set-remove": + const modifier = args.shift(); + let delimiter = args.shift(); + if (delimiter === undefined) { + delimiter = " "; + } + + const set = new Set(value.split(delimiter)); + const toggle = new Set(modifier.split(delimiter)); + if (this.command === "set-toggle") { + for (const t of toggle) { + if (set.has(t)) { + set.delete(t); + } else { + set.add(t); + } + } + } else if (this.command === "set-set") { + for (const t of toggle) { + set.add(t); + } + } else if (this.command === "set-remove") { + for (const t of toggle) { + set.delete(t); + } + } + return Array.from(set).join(delimiter); + + default: + throw new Error(`unknown command ${this.command}`); + } } /** @@ -859,18 +872,18 @@ function transform(value) { * @return {undefined|*|null|string} */ function convertSpecialStrings(input, value) { - switch (input) { - case "value": - return value; - case "\\value": - return "value"; - case "\\undefined": - return undefined; - case "\\null": - return null; - default: - return input; - } + switch (input) { + case "value": + return value; + case "\\value": + return "value"; + case "\\undefined": + return undefined; + case "\\null": + return null; + default: + return input; + } } /** @@ -879,17 +892,17 @@ function convertSpecialStrings(input, value) { * @return {boolean} */ function evaluateCondition(value) { - const lowerValue = typeof value === "string" ? value.toLowerCase() : value; - - return ( - (value !== undefined && - value !== null && - value !== "" && - lowerValue !== "off" && - lowerValue !== "false" && - value !== false) || - lowerValue === "on" || - lowerValue === "true" || - value === true - ); + const lowerValue = typeof value === "string" ? value.toLowerCase() : value; + + return ( + (value !== undefined && + value !== null && + value !== "" && + lowerValue !== "off" && + lowerValue !== "false" && + value !== false) || + lowerValue === "on" || + lowerValue === "true" || + value === true + ); } -- GitLab