UNPKG

@gpa-gemstone/helper-functions

Version:
201 lines (200 loc) 9.7 kB
"use strict"; // ****************************************************************************************************** // ParseKeyValuePairs.tsx - Gbtc // // Copyright © 2025, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this // file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 11/09/2023 - Preston Crawford // Migrated code from GSF. // // ****************************************************************************************************** Object.defineProperty(exports, "__esModule", { value: true }); exports.ParseKeyValuePairs = void 0; var RegexEncode_1 = require("./RegexEncode"); var ReplaceAll_1 = require("./ReplaceAll"); /** * Pre-scans the input string to determine which start/end value delimiter * positions form properly matched pairs. Returns a Set of character indices * that participate in a valid match. Unmatched delimiters are excluded so * the main parser can treat them as literal characters. */ var findMatchedDelimiterPositions = function (str, startValueDelimiter, endValueDelimiter) { var matched = new Set(); var escaped = false; var depth = 0; var outerStart = -1; var nestedStarts = []; for (var i = 0; i < str.length; i++) { var ch = str[i]; if (!escaped) { if (ch === startValueDelimiter) { escaped = true; outerStart = i; depth = 0; } // Stray end delimiter outside escaped mode -- just skip it } else { if (ch === startValueDelimiter) { nestedStarts.push(i); depth++; } else if (ch === endValueDelimiter) { if (depth > 0) { var nestedStart = nestedStarts.pop(); matched.add(nestedStart); matched.add(i); depth--; } else { // Closes the outer pair matched.add(outerStart); matched.add(i); escaped = false; outerStart = -1; } } } } // If we're still in escaped mode at end-of-string, the outer start // and any unclosed nested starts are unmatched -- they were intentionally // left out of the matched set. Any nested pairs that DID close inside // are already in the set and stay valid. return matched; }; /** * Parses a delimited string into key/value pairs * * @param str - The input string containing key/value pairs. * @param parameterDelimiter - Delimiter between parameters (default `';'`). * @param keyValueDelimiter - Delimiter between key and value (default `'='`). * @param startValueDelimiter - Delimiter marking the start of a nested value (default `'{'`). * @param endValueDelimiter - Delimiter marking the end of a nested value (default `'}'`). * @param ignoreDuplicateKeys - If `true`, later values overwrite earlier ones for duplicate keys (default `true`). * @returns A `Map<string, string>` of parsed keys to their corresponding values. * @throws If any delimiters are not unique, or if duplicate keys are disallowed and encountered. */ var ParseKeyValuePairs = function (str, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter, ignoreDuplicateKeys) { if (parameterDelimiter === void 0) { parameterDelimiter = ';'; } if (keyValueDelimiter === void 0) { keyValueDelimiter = "="; } if (startValueDelimiter === void 0) { startValueDelimiter = "{"; } if (endValueDelimiter === void 0) { endValueDelimiter = "}"; } if (ignoreDuplicateKeys === void 0) { ignoreDuplicateKeys = true; } if (parameterDelimiter === keyValueDelimiter || parameterDelimiter === startValueDelimiter || parameterDelimiter === endValueDelimiter || keyValueDelimiter === startValueDelimiter || keyValueDelimiter === endValueDelimiter || startValueDelimiter === endValueDelimiter) throw "All delimiters must be unique"; var escapedParameterDelimiter = (0, RegexEncode_1.RegexEncode)(parameterDelimiter); var escapedKeyValueDelimiter = (0, RegexEncode_1.RegexEncode)(keyValueDelimiter); var escapedStartValueDelimiter = (0, RegexEncode_1.RegexEncode)(startValueDelimiter); var escapedEndValueDelimiter = (0, RegexEncode_1.RegexEncode)(endValueDelimiter); var backslashDelimiter = (0, RegexEncode_1.RegexEncode)("\\"); // First pass: find which { } positions are properly matched. // Unmatched delimiters will be treated as literal characters. var matchedPositions = findMatchedDelimiterPositions(str, startValueDelimiter, endValueDelimiter); var keyValuePairs = new Map(); var escapedValue = []; var valueEscaped = false; var delimiterDepth = 0; // Escape any parameter or key/value delimiters within tagged value sequences // For example, the following string: // "normalKVP=-1; nestedKVP={p1=true; p2=false}") // would be encoded as: // "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse") for (var i = 0; i < str.length; i++) { var character = str[i]; var isMatchedDelimiter = matchedPositions.has(i); if (character === startValueDelimiter && isMatchedDelimiter) { if (!valueEscaped) { valueEscaped = true; continue; // Don't add tag start delimiter to final value } // Handle nested delimiters delimiterDepth++; } if (character === endValueDelimiter && isMatchedDelimiter) { if (valueEscaped) { if (delimiterDepth > 0) { // Handle nested delimiters delimiterDepth--; } else { valueEscaped = false; continue; // Don't add tag stop delimiter to final value } } // Unmatched end delimiters that somehow got here are treated as literals below } if (valueEscaped) { // Escape any delimiter characters inside nested key/value pair if (character === parameterDelimiter) escapedValue.push(escapedParameterDelimiter); else if (character === keyValueDelimiter) escapedValue.push(escapedKeyValueDelimiter); else if (character === startValueDelimiter) escapedValue.push(escapedStartValueDelimiter); else if (character === endValueDelimiter) escapedValue.push(escapedEndValueDelimiter); else if (character === "\\") escapedValue.push(backslashDelimiter); else escapedValue.push(character); } else { if (character === "\\") escapedValue.push(backslashDelimiter); else escapedValue.push(character); } } // Parse key/value pairs from escaped value var pairs = escapedValue.join("").split(parameterDelimiter); for (var i = 0; i < pairs.length; i++) { // Separate key from value var elements = pairs[i].split(keyValueDelimiter); if (elements.length === 2) { // Get key var key = elements[0].trim(); var unescapedValue = elements[1].trim(); // Get unescaped value unescapedValue = (0, ReplaceAll_1.ReplaceAll)(unescapedValue, escapedParameterDelimiter, parameterDelimiter); unescapedValue = (0, ReplaceAll_1.ReplaceAll)(unescapedValue, escapedKeyValueDelimiter, keyValueDelimiter); unescapedValue = (0, ReplaceAll_1.ReplaceAll)(unescapedValue, escapedStartValueDelimiter, startValueDelimiter); unescapedValue = (0, ReplaceAll_1.ReplaceAll)(unescapedValue, escapedEndValueDelimiter, endValueDelimiter); unescapedValue = (0, ReplaceAll_1.ReplaceAll)(unescapedValue, backslashDelimiter, "\\"); // Add key/value pair to dictionary if (ignoreDuplicateKeys) { // Add or replace key elements with unescaped value keyValuePairs.set(key, unescapedValue); } else { // Add key elements with unescaped value throwing an exception for encountered duplicate keys if (keyValuePairs.has(key)) throw "Failed to parse key/value pairs: duplicate key encountered. Key \"" + key + "\" is not unique within the string: \"" + str + "\""; keyValuePairs.set(key, unescapedValue); } } } return keyValuePairs; }; exports.ParseKeyValuePairs = ParseKeyValuePairs;