UNPKG

@schukai/monster

Version:

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

252 lines (214 loc) 6.66 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 { parseBracketedKeyValueHash, createBracketedKeyValueHash }; /** * Parses a string containing bracketed key-value pairs and returns an object representing the parsed result. * * - The string starts with a hash symbol #. * - After the hash symbol, there are one or more selector strings, separated by a semicolon ;. * - Each selector string has the format selectorName(key1=value1,key2=value2,...). * - The selector name is a string of one or more alphanumeric characters. * - The key-value pairs are separated by commas , and are of the form key=value. * - The key is a string of one or more alphanumeric characters. * - The value can be an empty string or a string of one or more characters. * - If the value contains commas, it must be enclosed in double quotes ". * - The entire key-value pair must be URL-encoded. * - The closing parenthesis ) for each selector must be present, even if there are no key-value pairs. * * @example * * ```javascript * // Example 1: * const hashString = '#selector1(key1=value1,key2=value2);selector2(key3=value3)'; * const result = parseBracketedKeyValueHash(hashString); * // result => { selector1: { key1: "value1", key2: "value2" }, selector2: { key3: "value3" } } * ``` * * @example * * ```javascript * // Example 2: * const hashString = '#selector1(key1=value1,key2=value2);selector2('; * const result = parseBracketedKeyValueHash(hashString); * // result => {} * ``` * * @since 3.37.0 * @param {string} hashString - The string to parse, containing bracketed key-value pairs. * @return {Object} - An object representing the parsed result, with keys representing the selectors and values representing the key-value pairs associated with each selector. * - Returns an empty object if there was an error during parsing. */ function parseBracketedKeyValueHash(hashString) { const selectors = {}; //const selectorStack = []; //const keyValueStack = []; const trimmedHashString = hashString.trim(); const cleanedHashString = trimmedHashString.charAt(0) === "#" ? trimmedHashString.slice(1) : trimmedHashString; //const selectors = (keyValueStack.length > 0) ? result[selectorStack[selectorStack.length - 1]] : result; let currentSelector = ""; function addToResult(key, value) { if (currentSelector && key) { if (!selectors[currentSelector]) { selectors[currentSelector] = {}; } selectors[currentSelector][key] = value; } } let currentKey = ""; let currentValue = ""; let inKey = true; let inValue = false; let inQuotedValue = false; let inSelector = true; let escaped = false; let quotedValueStartChar = ""; for (let i = 0; i < cleanedHashString.length; i++) { const c = cleanedHashString[i]; const nextChar = cleanedHashString?.[i + 1]; if (c === "\\" && !escaped) { escaped = true; continue; } if (escaped) { if (inSelector) { currentSelector += c; } else if (inKey) { currentKey += c; } else if (inValue) { currentValue += c; } escaped = false; continue; } if (inQuotedValue && quotedValueStartChar !== c) { if (inSelector) { currentSelector += c; } else if (inKey) { currentKey += c; } else if (inValue) { currentValue += c; } continue; } if (c === ";" && inSelector) { inSelector = true; currentSelector = ""; continue; } if (inSelector === true && c !== "(") { currentSelector += c; continue; } if (c === "(" && inSelector) { inSelector = false; inKey = true; currentKey = ""; continue; } if (inKey === true && c !== "=") { currentKey += c; continue; } if (c === "=" && inKey) { inKey = false; inValue = true; if (nextChar === '"' || nextChar === "'") { inQuotedValue = true; quotedValueStartChar = nextChar; i++; continue; } currentValue = ""; continue; } if (inValue === true) { if (inQuotedValue) { if (c === quotedValueStartChar) { inQuotedValue = false; continue; } currentValue += c; continue; } if (c === ",") { inValue = false; inKey = true; const decodedCurrentValue = decodeURIComponent(currentValue); addToResult(currentKey, decodedCurrentValue); currentKey = ""; currentValue = ""; continue; } if (c === ")") { inValue = false; //inKey = true; inSelector = true; const decodedCurrentValue = decodeURIComponent(currentValue); addToResult(currentKey, decodedCurrentValue); currentKey = ""; currentValue = ""; currentSelector = ""; continue; } currentValue += c; continue; } } if (inSelector) { return selectors; } return {}; } /** * Creates a hash selector string from an object. * * @param {Object} object - The object containing selectors and key-value pairs. * @param {boolean} addHashPrefix - Whether to add the hash prefix # to the beginning of the string. * @return {string} The hash selector string. * @since 3.37.0 */ function createBracketedKeyValueHash(object, addHashPrefix = true) { if (!object) { return addHashPrefix ? "#" : ""; } let hashString = ""; function encodeKeyValue(key, value) { return encodeURIComponent(key) + "=" + encodeURIComponent(value); } for (const selector in object) { if (object.hasOwnProperty(selector)) { const keyValuePairs = object[selector]; let selectorString = selector; let keyValueString = ""; for (const key in keyValuePairs) { if (keyValuePairs.hasOwnProperty(key)) { const value = keyValuePairs[key]; keyValueString += keyValueString.length === 0 ? "" : ","; keyValueString += encodeKeyValue(key, value); } } if (keyValueString.length > 0) { selectorString += "(" + keyValueString + ")"; hashString += hashString.length === 0 ? "" : ";"; hashString += selectorString; } } } if (hashString.length > 0 && addHashPrefix) { hashString = "#" + hashString; } return hashString; }