import { v4 as uuid } from "uuid"; import ArrayUtils from "./ArrayUtils.js"; import { JSUtils } from "./JSUtils.js"; /** * `StringUtils`: Utilizado para manipulação de Strings. */ export class StringUtils { /** * Retorna se a string está vazia. * Valores null e undefined são considerados como vazio. * * @param value - String para ser validada. * @returns - Verdadeiro caso a string não contenha informação. * */ static isEmpty(value: any): Boolean { if (value == undefined || value === null) { return true; } value = value.toString(); value = value.trim(); if (value.length === 0) { return true; } return false; } /** * Verifica se argumento informado é do tipo string * @param text */ static isString(text: any): boolean { return typeof text === 'string' || text instanceof String; } /** * Remove acentos de vogais maiúsculas * * @param text String para ser transformada. * @example entrada: "ÁÊÇÒ" // retorno: "AECO" */ static replaceAccentuatedCharsUpper(text: string): string { if(!this.isString(text)){ return text; } return text.replace(/[ÀÁÂÃÄÅÆ]/g, 'A') .replace(/[ÈÉÊË]/g, 'E') .replace(/[ÌÍÎÏ]/g, 'I') .replace(/[ÒÓÔÕÖØ]/g, 'O') .replace(/[ÙÚÛÜ]/g, 'U') .replace(/[ÝŸ]/g, 'Y') .replace(/[Ñ]/g, 'N') .replace(/[Ç]/g, 'C'); } /** * Converte todas as entidades HTML de um texto * * @param text String para ser transformada. * @example entrada: "<Teste>" // retorno: "" */ static decodeHtmlEntities(text: string): string { if(!text){ return text; } const entities = [ ['amp', '&'], ['apos', '\''], ['#x27', '\''], ['#x2F', '/'], ['#39', '\''], ['#47', '/'], ['lt', '<'], ['gt', '>'], ['nbsp', ' '], ['quot', '"'] ]; for(const entity of entities) { text = text.replace(new RegExp(`&${entity[0]};`), entity[1]); } return text; } /** * Remove acentos de vogais minúsculas. * * @param text String para ser transformada. * @example entrada: "áêçò" // retorno: "aeco" */ static replaceAccentuatedCharsLower(text: string): string { if(!this.isString(text)){ return text; } return text.replace(/[àáâãäåæ]/g, 'a') .replace(/[èéêë]/g, 'e') .replace(/[ìíîï]/g, 'i') .replace(/[òóôõöø]/g, 'o') .replace(/[ùúûü]/g, 'u') .replace(/[ýÿ]/g, 'y') .replace(/[ñ]/g, 'n') .replace(/[ç]/g, 'c'); } /** * Remove acentuação de vogais e de caracteres especiais que não sejam letras ou numeral. * * @param text String para ser transformada. * @param removeSpecialChars Remove outros caracteres especiais que não sejam letras e números. * @example * entrada: "á@Êç#Ò", false // retorno: "a@Ec#O" * entrada: "á@Êç#Ò", true // retorno: "aEcO" */ static replaceAccentuatedChars(text: string, removeSpecialChars: boolean = true): string { if(!this.isString(text)){ return text; } text = StringUtils.replaceAccentuatedCharsLower(text); text = StringUtils.replaceAccentuatedCharsUpper(text); if(removeSpecialChars){ text = text.replace(/[^a-z0-9]/gi, ""); } return text; } /** * Remove acentos de vogais, substitui Ç por c e retorna a string em caixa alta mantendo espaços e símbolos. * @param text - Texto para ser transformado. * @returns - Texto sem acentuação e caixa alta, mantendo espaços e símbolos. */ static replaceAccentuatedCharsKeepSymbols(text: string): string { if(!this.isString(text)){ return text; } return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase(); } /** * Calcula um código hash para uma string. * @param value String que será gerado o hash code. * @returns - Um hash calculado com base no valor informado. * @example * Informado: '123456' | Retorna: 1450575459 */ static hashCode(value: string): string { let hash = 0, i, chr; if (value.length === 0) return hash.toString(); for (i = 0; i < value.length; i++) { chr = value.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash |= 0; // Convert to 32bit integer } return hash.toString(); } /** * Converte um texto do tipo string para um booleano. * * @param value Valor a ser convertido. * @param defaultValue Valor padrão. * @returns - Texto convertido. * @example * Informado: 'true' | Retorna: true * Informado: 'false' | Retorna: false */ static getBooleanValue(value:string, defaultValue:boolean = false): boolean { if([true, "true", "S"].includes(value)) { return true; } else if([false, "false", "N"].includes(value)) { return false; } else { return defaultValue; } } /** * * Adiciona caracteres à esquerda caso texto seja menor que o valor do parâmetro len passado. * * @param str - Texto para ser ajustado. * @param len - Tamanho desejado do texto. * @param pad - Caractere a ser adicionado a esquerda caso o texto menor que o tamanho passado. * @returns - Texto passado se este for maior que o len. * Ou retorna o texto com os caracteres adicionados na esquerda. * * @example * padStart('SANKHYA', 8,'.') | Retorna: '...SANKHYA' * @example * padStart('SANKHYA', 5,'A') | Retorna: 'SANKHYA' * * */ static padStart(str: string, len: number, pad: string = " "): string { str = str != undefined ? str : ""; while (str.length < len) { str = pad + str; } return str; } /** * * Adiciona caracteres à direita caso texto seja menor que o valor do parâmetro len passado. * * @param str - Texto para ser ajustado. * @param len - Tamanho desejado do texto. * @param pad - Caractere a ser adicionado a direita caso o texto menor que o tamanho passado. * @returns - Texto passado se este for maior que o len. * Ou retorna o texto com os caracteres adicionados na direita. * * @example * padStart('SANKHYA', 8,'.') | Retorna: 'SANKHYA...' * @example * padStart('SANKHYA', 5,'A') | Retorna: 'SANKHYA' * * */ static padEnd(str: string, len: number, pad: string = " "): string { str = str != undefined ? str : ""; while (str.length < len) { str = str + pad; } return str; } /** * Determina a ordem de strings. * * @param a - Primeira string para comparação. * @param b - Segunda string para comparação. * @returns - Um numeral negativo se o primeiro argumento é menor que o segundo, zero se os dois são iguais e um numeral positivo quando o primeiro é maior que o segundo. */ static compare(a: string, b: string): number{ if(a === undefined){ return b === undefined ? 0 : 1; } else if(b === undefined){ return -1; } if(a === null){ return b === null ? 0 : 1; } else if(b === null){ return -1; } return a.localeCompare(b); } /** * Converte string em camelCase. * Combina palavras compostas ou frases, alterando a inicial de cada uma, a partir da primeira, para maiúscula e unidas sem espaços. * @param value String a ser convertida. * @returns String convertida em camelCase. * * @example * toCamelCase('Exemplo de uso') | Retorna: 'exemploDeUso' */ static toCamelCase(value: string): string { if(!value) return ""; if (value.match(/^[a-z]+[A-Z]/) || value.includes(".")) { return value; } return value .toLowerCase() .replace(/^([A-Z])|[\s-_](\w)/g, (_, p1, p2) => p2?.toUpperCase() || p1.toLowerCase()); } /** * Converte string para PascalCase. * @param value String a ser convertida. * @returns String convertida em PascalCase. */ static toPascalCase(value: string): string{ return (value || "") .toLowerCase() .replace(/(?:^|\s)\w/g, (match: string) => match.toUpperCase()) .replace(/[\s-_]/g,''); } /** * Utilitário para converter string em snake_case. * @param value String a ser convertida. * @returns String convertida em snake_case. */ static toSnakeCase(value: string): string{ return (value || "") .toLowerCase() .replace(/[A-Z]/g, (match: string) => `_${match.toLowerCase()}`) .replace(/[\s-]/g, '_') .replace(/^_/, ""); } /** * Utilitário para converter string em kebab-case. * @param value String a ser convertida. * @returns String convertida em KebabCase. */ static toKebabCase(value: string): string{ return (value || "").replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase(); } /** * Utilitário para remover caracteres em branco da string. * @param value String a ser removido os espaços. * @returns String sem espaços em branco. */ static replaceBlankCharacters(value: string): string{ return (value || "").replace(/ /g, ""); } /** * Utilitário para formatar bytes em string legível, convertendo para * múltiplos maiores caso necessário. * @param byte quantidade de bytes a serem representados. * @returns string formatada de acordo com a unidade. */ static formatBytes(bytes: number): string { const units = ["B", "KB", "MB", "GB"]; if (bytes < 1024) { return `${bytes?.toString()}B`; } const base = Math.log(bytes) / Math.log(1024); const offSet = Math.floor(base); if (offSet >= units.length) { return `${bytes?.toString()}B`; } const value = this.prettyPrecision(Math.pow(1024, base - offSet).toFixed(2).toString()); const unit = units[offSet]; return `${value}${unit}`; } /** * Método interno. Remove zeros depois da vírgula diminuindo o tamanho * da string. * @param strNumber representação textual do número a ser ajustado. * @returns simplificação do strNumber, sem os zeros à direita depois do ponto. */ private static prettyPrecision(strNumber: string): string { const parts = strNumber.split("."); if (parts.length > 1) { const lastPart = parts.length - 1; let lastPartStr = parts[lastPart]; while (lastPartStr.length > 0 && lastPartStr[lastPartStr.length - 1] === "0") { lastPartStr = lastPartStr.slice(0, lastPartStr.length - 1); } if (lastPartStr) { parts[lastPart] = lastPartStr; } else { parts.pop(); } } return parts.join("."); } /** * Método utilizado para gerar IDs únicos. * @returns id único randômico. */ static generateUUID():string { return uuid(); } /** * Checa se uma string pode ser convertida para letras maiúsculas ou minúsculas * * @param {string} original - A string a ser verificada * @return {boolean} Se a string pode ser convertida */ static isCaseable(original: string): boolean { if(original == null) return false; const uppercase = original.toUpperCase() const lowercase = original.toLowerCase() return original !== uppercase || original !== lowercase } /** * Checa se uma string é minúscula * * @param {string} original - A string a ser verificada * @return {boolean} Se a string é minúscula */ static isLowerCase(original: string): boolean { if(original == null) return false; const uppercase = original.toUpperCase() return this.isCaseable(original) && uppercase !== original } /** * Inverte uma string de minúsculas para maiúsculas e vice-versa * * @param {string} original - A string a ser invertida * @return {string} A string invertida */ static getOppositeCase(original: string): string { if(original == null) return ""; return this.isLowerCase(original) ? original.toUpperCase() : original.toLowerCase() } public static replaceToSpace(source: string, replaceList: string[] = []) { if (source == undefined) { return source; } replaceList.forEach((word: string) => { const wordAux = new RegExp(word, 'g'); let whiteSpace = ''; for (let i = 0; i < word.length; i++) { whiteSpace += ' '; } source = String(source).replace(wordAux, whiteSpace); }); return source; } public static replaceAccentuatedCharsHtmlEntities(source: string) { const charsWithoutAccentuation = "AEIOU_AO_AEIOU_AEIOU_U_C_aeiou_ao_aeiou_aeiou_u_c"; if (source == undefined) { return source; } const replaceList = ['&', '<', '>', '"']; const sourceAux = StringUtils.replaceToSpace(source, replaceList); return sourceAux.replace(/[^\w ]/g, (char: string) => { let index = charsWithoutAccentuation.indexOf(char); if (index > -1) { char = charsWithoutAccentuation.charAt(index); } return char || ''; }); } public static getSpecialCharacters(str: string) { const specialCharsRegex = /[[.\-\$\+\*,_\&\(\)%\/\\#@!:\|\=\'\"]/gmi; let match: any; let charList = []; while ((match = specialCharsRegex.exec(str))) { charList.push(str[match.index]); } return charList; } public static replaceAll(str: string, strFrom: string, strTo: string) { let pos = (str != undefined ? str : '').indexOf(strFrom); while (pos > -1) { str = str.replace(strFrom, strTo); pos = str.indexOf(strFrom); } return (str); } public static removeSpecialCharacters(str: string) { let specialChars = StringUtils.getSpecialCharacters(str); specialChars.forEach((ch: string) => { str = StringUtils.replaceAll(str, ch, ''); }); return str; } public static getArgumentNumber(argument: String) { return Number(argument || undefined); } public static highlightValue(argument: String, matchFields: any, value: string, fieldMD: any, forceMatch: boolean) { const startHighlightTag = ""; //FIXME: valor hardcoded, não podemos reaproveitar para outros cenários, dessa forma. const endHighlightTag = ""; let valueAux = JSUtils.replaceHtmlEntities(value); if (argument && (forceMatch || ArrayUtils.isIn(matchFields, fieldMD.fieldName))) { const argumentNumber = StringUtils.getArgumentNumber(argument); let replacements = (isNaN(argumentNumber) ? argument : argumentNumber.toString()).split(/%|,|\s+/); let lastMatchIndex = 0; let cleanText = StringUtils.replaceAccentuatedCharsHtmlEntities(valueAux); replacements.forEach((r: string) => { if (r) { r = StringUtils.replaceAccentuatedCharsHtmlEntities(r); let specialChars = StringUtils.getSpecialCharacters(r); if (specialChars != undefined && specialChars.length > 0) { r = StringUtils.removeSpecialCharacters(r); } if (fieldMD.mask || fieldMD.uiType === 'CGC_CPF' || fieldMD.uiType === 'Phone') { //Fazemos isso para descondirerar os caracteres especiais de formatação da string r = r.split('').join('\\.?\\-?\\/?\\(?\\)?'); } let regex = new RegExp(r, "ig"); regex.lastIndex = lastMatchIndex; let match = regex.exec(cleanText); if (match && match.length > 0) { lastMatchIndex = match.index; let sizeMatch = match[0].length; let txtToColor = valueAux.substring(lastMatchIndex, lastMatchIndex + sizeMatch); let txtColored = txtToColor?.trim() ? startHighlightTag + txtToColor + endHighlightTag : ''; let start = valueAux.substring(0, lastMatchIndex); let end = valueAux.substring(lastMatchIndex + sizeMatch); valueAux = start + txtColored + end; lastMatchIndex = lastMatchIndex + txtColored.length; cleanText = StringUtils.replaceAccentuatedCharsHtmlEntities(valueAux); } } }); } return valueAux; } /** * Escapa caracteres especiais em uma string, usando sequencias de escape Unicode. * @param str A string que deve sofrer alteração. * @returns String com valores alterados. */ public static escapeString(str: string): string { // eslint-disable-next-line no-control-regex return str.replace(/[\u0000-\u001F\u007F-\uFFFF<>=&"']/g, (char) => { return "\\u" + char.charCodeAt(0).toString(16).padStart(4, "0"); }); } }