diff --git a/source/text/bracketed-key-value-hash.mjs b/source/text/bracketed-key-value-hash.mjs index f44f7618a293f74c655727bce69ad0e51d5d41c3..3c7f978f7b513aa4164d344862c61336a4ede061 100644 --- a/source/text/bracketed-key-value-hash.mjs +++ b/source/text/bracketed-key-value-hash.mjs @@ -12,7 +12,7 @@ * SPDX-License-Identifier: AGPL-3.0 */ -export { parseBracketedKeyValueHash, createBracketedKeyValueHash }; +export {parseBracketedKeyValueHash, createBracketedKeyValueHash}; /** * Parses a string containing bracketed key-value pairs and returns an object representing the parsed result. @@ -51,155 +51,155 @@ export { parseBracketedKeyValueHash, createBracketedKeyValueHash }; * @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 {}; + 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 {}; } /** @@ -211,37 +211,41 @@ function parseBracketedKeyValueHash(hashString) { * @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; - } - } - } - - return addHashPrefix ? "#" + hashString : hashString; + 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) { + hashString = "#" + hashString; + } + + return addHashPrefix ? hashString : hashString; }