Skip to content
Snippets Groups Projects
Select Git revision
  • 799ec363689ee2f13c7e9e27fac8e7c29969602d
  • master default protected
  • 1.31
  • 4.38.8
  • 4.38.7
  • 4.38.6
  • 4.38.5
  • 4.38.4
  • 4.38.3
  • 4.38.2
  • 4.38.1
  • 4.38.0
  • 4.37.2
  • 4.37.1
  • 4.37.0
  • 4.36.0
  • 4.35.0
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
23 results

transformer.mjs

Blame
  • transformer.mjs 26.84 KiB
    'use strict';
    
    /**
     * @author schukai GmbH
     */
    
    import {Base} from '../types/base.mjs';
    import {getGlobal, getGlobalObject} from "../types/global.mjs";
    import {ID} from '../types/id.mjs';
    import {isArray, isObject, isString} from '../types/is.mjs';
    import {
        validateFunction,
        validateInteger,
        validateObject,
        validatePrimitive,
        validateString
    } from '../types/validate.mjs';
    import {clone} from "../util/clone.mjs";
    import {Pathfinder} from "./pathfinder.mjs";
    
    export {Transformer}
    
    /**
     * The transformer class is a swiss army knife for manipulating values. especially in combination with the pipe, processing chains can be built up.
     *
     * ```
     * <script type="module">
     * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs';
     * new Transformer()
     * </script>
     * ```
     *
     * A simple example is the conversion of all characters to lowercase. for this purpose the command `tolower` must be used.
     *
     * ```
     * let t = new Transformer('tolower').run('ABC'); // ↦ abc
     * ```
     *
     * **all commands**
     *
     * in the following table all commands, parameters and existing aliases are described.
     *
     * | command      | parameter                  | alias                   | description                                                                                                                                                                                                                                                                                                                                                |
     * |:-------------|:---------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
     * | to-base64    |                            | base64, btob            | Converts the value to base64                                                                                                                                                                                                                                                                                                                               |
     * | from-base64  |                            | atob                    | Converts the value from base64                                                                                                                                                                                                                                                                                                                             |
     * | call         | function:param1:param2:... |                         | Calling a callback function. The function can be defined in three places: either globally, in the context `addCallback` or in the passed object                                                                                                                                                                                                            |
     * | default      | value:type                 | ??                      | If the value is undefined the first argument is returned, otherwise the value. The third optional parameter specifies the desired type. If no type is specified, string is used. Valid types are bool, string, int, float, undefined and object. An object default value must be specified as a base64 encoded json string. (since 1.12.0)                 |
     * | debug        |                            |                         | the passed value is output (console) and returned  |
     * | empty        |                            |                         | Return empty String ""                                                                                                                                                                                                                                                                                                                                     |
     * | first-key    | default                    |                         | Can be applied to objects and returns the value of the first key. All keys of the object are fetched and sorted.    (since 1.23.0)                                                                                                                                                                                                                         |
     * | fromjson     |                            |                         | Type conversion from a JSON string (since 1.12.0)                                                                                                                                                                                                                                                                                                          |
     * | if           | statement1:statement2      | ?                       | Is the ternary operator, the first parameter is the valid statement, the second is the false part. To use the current value in the queue, you can set the value keyword. On the other hand, if you want to have the static string "value", you have to put one backslash \\ in front of it and write value. the follow values are true: 'on', true, 'true'. If you want to have a space, you also have to write \\ in front of the space.  |
     * | index        | key:default                | property, key           | Fetches a value from an object, an array, a map or a set                                                                                                                                                                                                                                                                                                   |
     * | last-key     | default                    |                         | Can be applied to objects and returns the value of the last key. All keys of the object are fetched and sorted. (since 1.23.0)                                                                                                                                                                                                                             |
     * | length       |                            | count                   | Length of the string or entries of an array or object                                                                                                                                                                                                                                                                                                      |
     * | nop          |                            |                         | Do nothing                                                                                                                                                                                                                                                                                                                                                 |
     * | nth-key      | index:default              |                         | Can be applied to objects and returns the value of the nth key. All keys of the object are fetched and sorted. (since 1.23.0)                                                                                                                                                                                                                              |
     * | nth-last-key | index:default              |                         | Can be applied to objects and returns the value of the nth key from behind. All keys of the object are fetched and sorted. (since 1.23.0)                                                                                                                                                                                                                  |
     * | path         | path                       |                         | The access to an object is done via a Pathfinder object                                                                                                                                                                                                                                                                                                    |
     * | path-exists  | path                       |                         | Check if the specified path is available in the value (since 1.24.0)    |
     * | plaintext    |                            | plain                   | All HTML tags are removed (*)                                                                                                                                                                                                                                                                                                                              |
     * | prefix       | text                       |                         | Adds a prefix                                                                                                                                                                                                                                                                                                                                              |
     * | rawurlencode |                            |                         | URL coding                                                                                                                                                                                                                                                                                                                                                 |
     * | static       |                            | none                    | The Arguments value is used and passed to the value. Special characters \ <space> and : can be quotet by a preceding \.                                                                                                                                                                                                                                    |
     * | substring    | start:length               |                         | Returns a substring                                                                                                                                                                                                                                                                                                                                        |
     * | suffix       | text                       |                         | Adds a suffix                                                                                                                                                                                                                                                                                                                                              |
     * | tointeger    |                            |                         | Type conversion to an integer value                                                                                                                                                                                                                                                                                                                        |
     * | tojson       |                            |                         | Type conversion to a JSON string (since 1.8.0)                                                                                                                                                                                                                                                                                                             |
     * | tolower      |                            | strtolower, tolowercase | The input value is converted to lowercase letters                                                                                                                                                                                                                                                                                                          |
     * | tostring     |                            |                         | Type conversion to a string.                                                                                                                                                                                                                                                                                                                               |
     * | toupper      |                            | strtoupper, touppercase | The input value is converted to uppercase letters                                                                                                                                                                                                                                                                                                          |
     * | trim         |                            |                         | Remove spaces at the beginning and end                                                                                                                                                                                                                                                                                                                     |
     * | ucfirst      |                            |                         | First character large                                                                                                                                                                                                                                                                                                                                      |
     * | ucwords      |                            |                         | Any word beginning large                                                                                                                                                                                                                                                                                                                                   |
     * | undefined    |                            |                         | Return undefined                                                                                                                                                                                                                                                                                                                                           |
     * | uniqid       |                            |                         | Creates a string with a unique value (**)
     *
     *  (*) for this functionality the extension [jsdom](https://www.npmjs.com/package/jsdom) must be loaded in the nodejs context.
     *
     * ```
     *  // polyfill
     *  if (typeof window !== "object") {
     *     const {window} = new JSDOM('', {
     *         url: 'http://example.com/',
     *         pretendToBeVisual: true
     *     });
     * 
     *     [
     *         'self',
     *         'document',
     *         'Node',
     *         'Element',
     *         'HTMLElement',
     *         'DocumentFragment',
     *         'DOMParser',
     *         'XMLSerializer',
     *         'NodeFilter',
     *         'InputEvent',
     *         'CustomEvent'
     *     ].forEach(key => (global[key] = window[key]));
     * }
     * ```
     *
     * (**) for this command the crypt library is necessary in the nodejs context.
     *
     * ```
     * import * as Crypto from "@peculiar/webcrypto";
     * global['crypto'] = new Crypto.Crypto();
     * ```
     *
     * @example
     *
     * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs';
     *
     * const transformer = new Transformer("tolower")
     *
     * console.log(transformer.run("HELLO"))
     * // ↦ hello
     *
     * console.log(transformer.run("WORLD"))
     * // ↦ world
     *
     * @since 1.5.0
     * @copyright schukai GmbH
     * @memberOf Monster.Data
     */
    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
         * @returns {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
         * @returns {*}
         * @throws {Error} unknown command
         * @throws {TypeError} unsupported type
         * @throws {Error} type not supported
         */
        run(value) {
            return transform.apply(this, [value])
        }
    }
    
    /**
     *
     * @param {string} command
     * @returns {array}
     * @private
     */
    function disassemble(command) {
    
        validateString(command);
    
        let placeholder = new Map;
        const regex = /((?<pattern>\\(?<char>.)){1})/mig;
    
        // The separator for args must be escaped
        // undefined string which should not occur normally and is also not a regex
        let result = command.matchAll(regex)
    
        for (let m of result) {
            let g = m?.['groups'];
            if (!isObject(g)) {
                continue;
            }
    
            let p = g?.['pattern'];
            let c = g?.['char'];
    
            if (p && c) {
                let 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 (let k of placeholder) {
                v = v.replace(k[0], k[1]);
            }
            return v;
    
    
        });
    
        return parts
    }
    
    /**
     * tries to make a string out of value and if this succeeds to return it back
     *
     * @param {*} value
     * @returns {string}
     * @private
     */
    function convertToString(value) {
    
        if (isObject(value) && value.hasOwnProperty('toString')) {
            value = value.toString();
        }
    
        validateString(value)
        return value;
    }
    
    /**
     *
     * @param {*} value
     * @returns {*}
     * @private
     * @throws {Error} unknown command
     * @throws {TypeError} unsupported type
     * @throws {Error} type not supported
     * @throws {Error} missing key parameter
     */
    function transform(value) {
    
        const console = getGlobalObject('console');
    
        let args = clone(this.args);
        let key, defaultValue;
    
        switch (this.command) {
    
            case 'static':
                return this.args.join(':');
    
            case 'tolower':
            case 'strtolower':
            case 'tolowercase':
                validateString(value)
                return value.toLowerCase();
    
            case 'toupper':
            case 'strtoupper':
            case 'touppercase':
                validateString(value)
                return value.toUpperCase();
    
            case 'tostring':
                return "" + value;
    
            case 'tointeger':
                let n = parseInt(value);
                validateInteger(n);
                return n
    
            case 'tojson':
                return JSON.stringify(value);
    
            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;
                let callbackName = args.shift();
                let context = getGlobal();
    
                if (isObject(value) && value.hasOwnProperty(callbackName)) {
                    callback = value[callbackName];
                } else if (this.callbacks.has(callbackName)) {
                    let 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);
                let 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);
    
                if (trueStatement === 'value') {
                    trueStatement = value;
                }
                if (trueStatement === '\\value') {
                    trueStatement = 'value';
                }
                if (falseStatement === 'value') {
                    falseStatement = value;
                }
                if (falseStatement === '\\value') {
                    falseStatement = 'value';
                }
    
                let condition = ((value !== undefined && value !== '' && value !== 'off' && value !== 'false' && value !== false) || value === 'on' || value === 'true' || value === true);
                return condition ? trueStatement : falseStatement;
    
    
            case 'ucfirst':
                validateString(value);
    
                let 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);
                let prefix = args?.[0];
                return prefix + value;
    
            case 'suffix':
                validateString(value);
                let 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() || '');
    
                let 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 'path':
    
                key = args.shift();
                if (key === undefined) {
                    throw new Error("missing key parameter")
                }
    
                let pf = new Pathfinder(value);
    
                if (!pf.exists(key)) {
                    return undefined;
                }
    
                return pf.getVia(key);
    
    
            case 'substring':
    
                validateString(value);
    
                let start = parseInt(args[0]) || 0;
                let 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")
    
    
            default:
                throw new Error("unknown command " + this.command)
        }
    
        return value;
    }