// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type {Brand} from './Brand.js'; export const escapeCharacters = (inputString: string, charsToEscape: string): string => { let foundChar = false; for (let i = 0; i < charsToEscape.length; ++i) { if (inputString.indexOf(charsToEscape.charAt(i)) !== -1) { foundChar = true; break; } } if (!foundChar) { return String(inputString); } let result = ''; for (let i = 0; i < inputString.length; ++i) { if (charsToEscape.indexOf(inputString.charAt(i)) !== -1) { result += '\\'; } result += inputString.charAt(i); } return result; }; const toHexadecimal = (charCode: number, padToLength: number): string => { return charCode.toString(16).toUpperCase().padStart(padToLength, '0'); }; // Remember to update the third group in the regexps patternsToEscape and // patternsToEscapePlusSingleQuote when adding new entries in this map. const escapedReplacements = new Map([ ['\b', '\\b'], ['\f', '\\f'], ['\n', '\\n'], ['\r', '\\r'], ['\t', '\\t'], ['\v', '\\v'], ['\'', '\\\''], ['\\', '\\\\'], ['<------------2-----------> <---------3--------> <-----4----> <------5-----> <-----6----> <7> */ const WORD = /[A-Z]{2,}(?=[A-Z0-9][a-z0-9]+|\b|_)|[A-Za-z][0-9]+[a-z]?|[A-Z]?[a-z]+|[0-9][A-Za-z]+|[A-Z]|[0-9]+|[.]/g; export const toKebabCase = function(input: string): Lowercase { return (input.match?.(WORD)?.map(w => w.toLowerCase()).join('-').replaceAll('-.-', '.') || input) as Lowercase; }; export function toKebabCaseKeys(settingValue: Record): Record { return Object.fromEntries(Object.entries(settingValue).map(([key, value]) => [toKebabCase(key), value])); } /** * Converts a given string to snake_case. * This function handles camelCase, PascalCase, and acronyms, including transitions between letters and numbers. * It uses Unicode-aware regular expressions (`\p{L}`, `\p{N}`, `\p{Lu}`, `\p{Ll}` with the `u` flag) * to correctly process letters and numbers from various languages. * * @param text The input string to convert to snake_case. * @returns The snake_case version of the input string. */ export function toSnakeCase(text: string): string { if (!text) { return ''; } // First, handle case-based transformations to insert underscores correctly. // 1. Add underscore between a letter and a number. // e.g., "version2" -> "version_2" // 2. Add underscore between an uppercase letter sequence and a following uppercase+lowercase sequence. // e.g., "APIFlags" -> "API_Flags" // 3. Add underscore between a lowercase/number and an uppercase letter. // e.g., "lastName" -> "last_Name", "version_2Update" -> "version_2_Update" // 4. Replace sequences of non-alphanumeric with a single underscore // 5. Remove any leading or trailing underscores. const result = text.replace(/(\p{L})(\p{N})/gu, '$1_$2') // 1 .replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1_$2') // 2 .replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, '$1_$2') // 3 .toLowerCase() .replace(/[^\p{L}\p{N}]+/gu, '_') // 4 .replace(/^_|_$/g, ''); // 5 return result; } /** Replaces the last occurrence of parameter `search` with parameter `replacement` in `input` **/ export const replaceLast = function(input: string, search: string, replacement: string): string { const replacementStartIndex = input.lastIndexOf(search); if (replacementStartIndex === -1) { return input; } return input.slice(0, replacementStartIndex) + input.slice(replacementStartIndex).replace(search, replacement); }; export const stringifyWithPrecision = function stringifyWithPrecision(s: number, precision = 2): string { if (precision === 0) { return s.toFixed(0); } const string = s.toFixed(precision).replace(/\.?0*$/, ''); return string === '-0' ? '0' : string; }; /** * Somewhat efficiently concatenates 2 base64 encoded strings. */ export const concatBase64 = function(lhs: string, rhs: string): string { if (lhs.length === 0 || !lhs.endsWith('=')) { // Empty string or no padding, we can straight-up concatenate. return lhs + rhs; } const lhsLeaveAsIs = lhs.substring(0, lhs.length - 4); const lhsToDecode = lhs.substring(lhs.length - 4); return lhsLeaveAsIs + globalThis.btoa(globalThis.atob(lhsToDecode) + globalThis.atob(rhs)); };