UNPKG

@thirteen-13/string-utility

Version:
490 lines (489 loc) 23.6 kB
export const isString = (str) => typeof str === 'string' || str instanceof String; export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); export const toCamelCase = (str) => { if (/^[a-z][a-zA-Z0-9]*$/.test(str)) return str; return str .toLowerCase() .replace(/[^a-zA-Z0-9]+(\w)/g, (_, c) => c.toUpperCase()) .replace(/^[^a-zA-Z0-9]*([a-zA-Z])/, (_, c) => c.toLowerCase()); }; export const toKebabCase = (str) => str.trim() // Trim any leading or trailing spaces .replace(/([a-z])([A-Z])/g, '$1-$2') // Add hyphen between lowercase and uppercase letters .replace(/(\d)([a-zA-Z])/g, '$1-$2') // Add hyphen between numbers and letters .replace(/[\s_]+/g, '-') // Replace spaces and underscores with hyphens .toLowerCase(); // Convert all to lowercase export const toSnakeCase = (str) => str .replace(/([a-z])([A-Z])/g, '$1_$2') .replace(/(\d)([a-zA-Z])/g, '$1_$2') .replace(/[\s-]+/g, '_') .toLowerCase(); export const reverse = (str) => str.split('').reverse().join(''); export const truncate = (str, length) => { const graphemes = Array.from(str); return graphemes.length > length ? graphemes.slice(0, length).join('') + '…' : str; }; export const stripHtml = (str) => { const withoutScriptsAndStyles = str .replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '') .replace(/<style[\s\S]*?>[\s\S]*?<\/style>/gi, ''); return withoutScriptsAndStyles.replace(/<\/?[a-z][\s\S]*?>/gi, ''); }; export const escapeHtml = (str) => str.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#039;'); export const isUpperCase = (str) => str === str.toUpperCase(); export const isLowerCase = (str) => str === str.toLowerCase(); export const repeat = (str, count) => { return str.repeat(Math.floor(count)); }; export const padLeft = (str, length, char = ' ') => { const fill = Array.from(char); return str.length >= length ? str : fill[0]?.repeat(length - str.length) + str; }; export const padRight = (str, length, char = ' ') => { const fill = Array.from(char); return str.length >= length ? str : str + fill[0]?.repeat(length - str.length); }; export const contains = (str, substr) => str.includes(substr); export const startsWith = (str, prefix) => str.startsWith(prefix); export const endsWith = (str, suffix) => str.endsWith(suffix); export const removeNonAlpha = (str) => str.replace(/[^a-z]/gi, ''); export const removeNonNumeric = (str) => str.replace(/[^0-9]/g, ''); export const removeWhitespace = (str) => str.replace(/\s/g, ''); export const countOccurrences = (str, sub) => { if (sub === "") { return str?.length + 1; } return str.split(sub).length - 1; }; export const slugify = (str) => str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); export const getInitials = (str) => str .split(' ') .map((word) => word[0]) .join('') .toUpperCase(); export const isStrictPalindrome = (str) => str === str.split('').reverse().join(''); export const isLoosePalindrome = (str) => { const cleaned = str.replace(/[^a-z0-9]/gi, '').toLowerCase(); return cleaned === cleaned.split('').reverse().join(''); }; export const extractNumbers = (str) => (str.match(/\d+/g) || []).map((value) => parseInt(value)); export const extractWords = (str) => str.match(/\b\w+\b/g) || []; export const maskString = (str, start, end, maskChar = '*') => str .split('') .map((char, i) => (i >= start && i < end ? maskChar : char)) .join(''); export const randomString = (length) => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; return Array.from({ length }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join(''); }; export const isAlpha = (str) => /^[A-Za-z]+$/.test(str); export const isAlphanumeric = (str) => /^[A-Za-z0-9]+$/.test(str); export const isEmail = (str) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str); export const extractEmails = (str) => str.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || []; export const extractUrls = (str) => str.match(/https?:\/\/[^\s]+[a-zA-Z0-9]/g) || []; export const titleCase = (str) => { const leadingSpaces = str.match(/^(\s*)/)?.[0] || ''; const trailingSpaces = str.match(/(\s*)$/)?.[0] || ''; const words = str.trim().split(/\s+/); const titled = words .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) .join(' '); return leadingSpaces + titled + trailingSpaces; }; export const swapCase = (str) => str .split('') .map((c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()) .join(''); export const removeDuplicateWords = (str) => Array.from(new Set(str.split(' ').filter(value => !!value))).join(" "); export const safeString = (str) => str.replace(/[^\w\s]/gi, ''); export const compressWhitespace = (str) => str.replace(/\s+/g, ' ').trim(); export const charFrequency = (str) => Array.from(str).reduce((acc, char) => { acc[char] = (acc[char] || 0) + 1; return acc; }, {}); export const levenshteinDistance = (a, b) => { const m = a.length; const n = b.length; if (m === 0) return n; if (n === 0) return m; const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = a[i - 1] === b[j - 1] ? 0 : 1; dp[i][j] = Math.min(dp[i - 1][j] + 1, // deletion dp[i][j - 1] + 1, // insertion dp[i - 1][j - 1] + cost // substitution ); } } return dp[m][n]; }; export const toPascalCase = (str) => str .replace(/[^a-zA-Z0-9]+/g, ' ') // turn all non-alphanum into space .trim() .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); export const toDotCase = (str) => str.trim().replace(/\s+/g, '.').toLowerCase(); export const toSpaceCase = (str) => str.replace(/[_-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2'); export const endsWithAny = (str, suffixes) => suffixes.some(suffix => str.endsWith(suffix)); export const startsWithAny = (str, prefixes) => prefixes.some(prefix => str.startsWith(prefix)); export const trimChar = (str, char) => { const escapedChar = char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return str.replace(new RegExp(`^${escapedChar}+|${escapedChar}+$`, 'g'), ''); }; export const removeDiacritics = (str) => str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); export const getUniqueCharacters = (str) => Array.from(new Set(str.split(''))); export const stringToCharCodeArray = (str) => { const codes = []; for (const char of str) { codes.push(char.codePointAt(0)); } return codes; }; export const charCodeArrayToString = (arr) => arr.map(code => String.fromCodePoint(code)).join(''); export const wrap = (str, wrapper) => `${wrapper}${str}${wrapper}`; export const ensureStartsWith = (str, prefix) => str.startsWith(prefix) ? str : prefix + str; export const ensureEndsWith = (str, suffix) => str.endsWith(suffix) ? str : str + suffix; export const repeatStringUntilLength = (str, targetLength) => str.repeat(Math.ceil(targetLength / (str?.length || 1))).slice(0, targetLength); export const collapseNewlines = (str) => str.replace(/[\r\n]+/g, '\n'); export const stringToAsciiSum = (str) => [...str].reduce((sum, char) => sum + char.codePointAt(0), 0); export const getCharAtSafe = (str, index) => str[index] ?? ''; export const isWhitespace = (str) => /^\s*$/.test(str); export const isEmpty = (str) => str?.length === 0; export const isBlank = (str) => str.trim().length === 0; export const getNthWord = (str, n) => str.split(/\s+/)[n] ?? ''; export const countVowels = (str) => (str.match(/[aeiou]/gi) || []).length; export const countConsonants = (str) => (str.match(/[bcdfghjklmnpqrstvwxyz]/gi) || []).length; export const stripPunctuation = (str) => str.replace(/[.!@#$%^&*()\-_=+{}\[\]:;"\/\\,~]/g, ''); export const extractHashtags = (str) => str.match(/#[\w]+/g) || []; export const extractMentions = (str) => str.match(/@[\w]+/g) || []; export const hasRepeatedCharacters = (str) => /(.)\1/.test(str); export const isHexColor = (str) => /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str); export const isRgbColor = (str) => /^rgb\((\d{1,3},\s*){2}\d{1,3}\)$/.test(str); export const getLastNChars = (str, n) => { if (n === 0) return ""; return str.slice(-n); }; export const getFirstNChars = (str, n) => str.slice(0, n); export const containsAny = (str, items) => items.some(item => str.includes(item)); export const replaceAll = (str, find, replace) => str.replaceAll(find, replace); export const isAllUpperCase = (str) => str === str.toUpperCase() && /[A-Z]/.test(str); export const isAllLowerCase = (str) => str === str.toLowerCase() && /[a-z]/.test(str); export const toCharArray = (str) => Array.from(str); export const reverseWords = (str) => str.split(' ').reverse().join(' '); export const countWords = (str) => (str.match(/\b\w+\b/g) || []).length; export const repeatWithSeparator = (str, count, sep) => Array(count).fill(str).join(sep); export const trimStart = (str) => str.replace(/^\s+/, ''); export const trimEnd = (str) => str.replace(/\s+$/, ''); export const obfuscateEmail = (email) => { if (!isEmail(email)) { console.error(`provide valid email address${email}`); return email; } ; const [user, domain] = email.split('@'); return `${user[0]}***@${domain}`; }; export const base64Encode = (str) => Buffer.from(str, 'utf-8').toString('base64'); export const base64Decode = (str) => Buffer.from(str, 'base64').toString('utf-8'); export const camelToSnake = (str) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); export const snakeToCamel = (str) => trimChar(str.replace(/(_\w)/g, m => m[1].toUpperCase()), "_"); export const removeTrailingSlash = (str) => str.endsWith('/') ? str.slice(0, -1) : str; export const removeLeadingSlash = (str) => str.startsWith('/') ? str.slice(1) : str; export const splitByLength = (str, length) => str.match(new RegExp(`.{1,${length}}`, 'g')) || []; export const truncateWords = (str, numWords) => str.split(' ').slice(0, numWords).join(' ') + '…'; export const isUUID = (str) => { return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(str); }; export const generateUUID = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); export const removeDuplicateChars = (str) => Array.from(new Set(str)).join(''); export const percentEncode = (str) => { return encodeURIComponent(str).replace(/[!'()*]/g, ch => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`); }; export const percentDecode = (str) => decodeURIComponent(str); export const getByteLength = (str) => new Blob([str]).size; export const endsWithPunctuation = (str) => /[.!?]$/.test(str); export const stringSimilarity = (a, b) => { let matches = 0; if (Math.max(a.length, b.length) === 0) return 1; // Both empty, 100% similar if (Math.min(a.length, b.length) === 0 && Math.max(a.length, b.length) > 0) return 0; // One empty, one not, 0% similar for (let i = 0; i < Math.min(a.length, b.length); i++) { if (a[i] === b[i]) matches++; } return matches / Math.max(a.length, b.length); }; export const censor = (str, words, mask = '*') => words.reduce((s, word) => s.replace(new RegExp(`\\b${word}\\b`, 'gi'), mask.repeat(word.length)), str); export const safeJsonParse = (str) => { try { return JSON.parse(str); } catch { return null; } }; export const mirrorString = (str) => str + str.split('').reverse().join(''); export const removeHtmlTags = (str) => str.replace(/<[^>]*>/g, ''); export const unescapeHtml = (str) => str.replace(/&amp;/g, '&').replace(/&lt;/g, '<') .replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#039;/g, '\''); export const countCharacterOccurrences = (str, char) => { return str.split(char).length + (!char ? +1 : -1); }; export const extractInitials = (str) => str.split(' ').filter(word => word.length > 0).map(word => word[0].toUpperCase()).join(''); export const stripAnsiCodes = (str) => str.replace(/\x1B[[(?);]{0,2}(;?\d)*./g, ''); export const removeAllNumbers = (str) => str.replace(/\d+/g, ''); export const extractAllNumbers = (str) => str.match(/\d+/g) || []; export const padCenter = (str, length, padChar = ' ') => { if (str.length >= length) return str; const totalPadding = length - str.length; const paddingStart = Math.floor(totalPadding / 2); const paddingEnd = totalPadding - paddingStart; return padChar.repeat(paddingStart) + str + padChar.repeat(paddingEnd); }; export const hasEmoji = (str) => /[\u{1F600}-\u{1F6FF}]/u.test(str); export const extractEmoji = (str) => Array.from(str.match(/[\u{1F600}-\u{1F6FF}|\u{1F300}-\u{1F5FF}|\u{1F900}-\u{1F9FF}|\u{2600}-\u{26FF}|\u{2700}-\u{27BF}]/gu) || []); export const toCurrencyFormat = (numStr, currency = 'USD') => { const num = Number(numStr); if (isNaN(num)) return "NaN"; // Or throw error, or return original string return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(num); }; export const stripSpaces = (str) => str.replace(/\s+/g, ''); export const extractDomain = (url) => { try { return new URL(url).hostname; } catch { return null; // Return null for invalid URLs } }; export const extractTLD = (url) => { try { const hostname = new URL(url).hostname; const parts = hostname.split('.'); if (parts.length > 1) { return parts.pop() || null; } return null; // For hostnames like 'localhost' or single-label domains } catch { return null; // Return null for invalid URLs } }; export const removeAlphanumeric = (str) => str.replace(/[a-z0-9]/gi, ''); export const getMiddleCharacter = (str) => { if (str.length === 0) return ''; return str[Math.floor((str.length - 1) / 2)]; // Corrected for even length to pick left of middle }; export const insertAt = (str, index, value) => str.slice(0, index) + value + str.slice(index); export const removeAt = (str, index, count = 1) => { if (index < 0 || index >= str.length || count <= 0) return str; return str.slice(0, index) + str.slice(index + count); }; export const reverseSentences = (str) => { const sentences = str.match(/[^.!?]+[.!?]+/g) || []; if (sentences.length === 0 && str.trim().length > 0) return str; // Handle single phrase without punctuation if (sentences.length === 0) return ''; return sentences?.map((value) => value?.trim()).reverse().join(' ').trim(); }; export const capitalizeSentences = (str) => str.replace(/(^\s*\w|[.!?]\s*\w)/g, c => c.toUpperCase()); export const decapitalize = (str) => str.charAt(0).toLowerCase() + str.slice(1); export const toUpperFirstChar = (str) => str.charAt(0).toUpperCase() + str.slice(1); export const toLowerFirstChar = (str) => str.charAt(0).toLowerCase() + str.slice(1); export const removeQuotes = (str) => str.replace(/^['"](.*)['"]$/, '$1').replace(/^'(.*)'$/, '$1').replace(/^"(.*)"$/, '$1'); export const surroundWithQuotes = (str, quoteType = '"') => `${quoteType}${str}${quoteType}`; export const formatPhoneNumber = (num) => { const cleaned = num.replace(/\D/g, ''); if (cleaned.length === 10) { return cleaned.replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); } if (cleaned.length === 11 && (cleaned.startsWith('1') || cleaned.startsWith('0'))) { // e.g. US with country code return cleaned.slice(1).replace(/(\d{3})(\d{3})(\d{4})/, '($1) $2-$3'); } return num; // Return original if not a clear 10-digit or 11-digit US number }; export const convertToBinary = (str) => { if (!str) return ""; return [...str].map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' '); // Pad to 8 bits }; export const binaryToString = (bin) => { if (!bin) return ""; return bin.split(' ').map(b => String.fromCharCode(parseInt(b, 2))).join(''); }; export const convertToHex = (str) => { if (!str) return ""; return [...str].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' '); // Pad to 2 hex chars }; export const hexToString = (hex) => { if (!hex) return ""; return hex.split(' ').map(h => String.fromCharCode(parseInt(h, 16))).join(''); }; export const htmlEntityEncode = (str) => str.replace(/[\u00A0-\u9999<>\&"']/gim, i => `&#${i.charCodeAt(0)};`); // Added " and ' export const htmlEntityDecode = (str) => str.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec)); export const countLines = (str) => { if (str === '') return 0; // Or 1 for an empty string if it's considered one line return str.split(/\r\n|\r|\n/).length; }; export const getFirstLine = (str) => str.split(/\r?\n/)[0] || ''; export const getLastLine = (str) => str.split(/\r?\n/).pop() || ''; export const highlightSubstr = (str, substr, tagOpen = '**', tagClose = '**') => { if (!substr) return str; // Escape special characters in substr for RegExp const escapedSubstr = substr.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return str?.replaceAll(new RegExp(escapedSubstr, 'g'), `${tagOpen}${substr}${tagClose}`); }; export const replaceAt = (str, index, char) => { if (index < 0 || index >= str.length) return str; return str.substring(0, index) + char + str.substring(index + 1); }; export const stripLeadingZeros = (str) => { if (str === '0') return '0'; return str.replace(/^0+/, ''); }; export const removeDuplicatesWords = (str) => [...new Set(str.split(' '))].join(' '); export const sortWords = (str) => str.split(' ').sort().join(' '); export const uniqueWords = (str) => [...new Set(str.toLowerCase().match(/\b\w+\b/g) || [])]; export const toTitleCase = (str) => str.replace(/\b\w/g, l => l.toUpperCase()); export const slugToCamelCase = (str) => str.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); export const camelCaseToSlug = (str) => str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); export const removeSpecialChars = (str) => str.replace(/[^\w\s]/gi, ''); export const countPunctuation = (str) => (str.match(/[.,!?;:]/g) || []).length; export const countUppercase = (str) => (str.match(/[A-Z]/g) || []).length; export const countLowercase = (str) => (str.match(/[a-z]/g) || []).length; export const shuffleCharacters = (str) => [...str].sort(() => Math.random() - 0.5).join(''); export const containsUppercase = (str) => /[A-Z]/.test(str); export const containsLowercase = (str) => /[a-z]/.test(str); export const rotateString = (str, n) => { if (str.length === 0) return ''; const k = n % str.length; return str.slice(k) + str.slice(0, k); }; export const toggleCase = (str) => [...str].map(c => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).join(''); export const reverseEachWord = (str) => str.split(' ').map(word => word.split('').reverse().join('')).join(' '); export const splitToWords = (str) => str.trim().split(/\s+/).filter(Boolean); export const countSentences = (str) => (str.match(/[\w|\)][.?!](\s|$)/g) || []).length; export const extractSentences = (str) => str.match(/[^.!?]+[.!?]+\s*/g)?.map(s => s.trim()) || []; export const generateAcronym = (str) => str.split(/\s+/).filter(Boolean).map(word => word[0].toUpperCase()).join(''); export const titleToSlug = (str) => str.toLowerCase().replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-'); export const sanitizeFileName = (str) => { return str.replace(/[^a-zA-Z0-9._-]/g, '_'); }; export const isIpAddress = (str) => { if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(str)) return false; return str.split('.').every(part => parseInt(part, 10) <= 255); }; export const isUrl = (str) => { if (!str || typeof str !== 'string') return false; const normalized = str.startsWith('www.') || /^[a-zA-Z0-9.-]+\.[a-z]{2,}$/.test(str) ? `http://${str}` : str; try { const url = new URL(normalized); return ['http:', 'https:', 'ftp:'].includes(url.protocol); } catch { return false; } }; export const getFileExtension = (str) => { const lastDot = str.lastIndexOf('.'); if (lastDot === -1 || lastDot === 0 || lastDot === str.length - 1) return ''; // No extension or hidden file return str.substring(lastDot + 1); }; export const removeFileExtension = (str) => { const lastDot = str.lastIndexOf('.'); if (lastDot === -1 || lastDot === 0) return str; // No extension or hidden file return str.substring(0, lastDot); }; export const isNumericString = (str) => /^\d+$/.test(str); export const compactWhitespace = (str) => str.replace(/\s+/g, ' ').trim(); export const unescapeBackslashes = (str) => str.replace(/\\(.)/g, '$1'); export const stringToUnicode = (str) => [...str].map(c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0')).join(''); export const unicodeToString = (unicodeStr) => unicodeStr.replace(/\\u([\dA-Fa-f]{4})/g, (_, g) => String.fromCharCode(parseInt(g, 16))); export const removeVowels = (str) => str.replace(/[aeiou]/gi, ''); export const removeConsonants = (str) => str.replace(/[^aeiouAEIOU\s\d\W_]/gi, ''); // Keep vowels, spaces, digits, and symbols export const alternateCase = (str) => [...str].map((c, i) => i % 2 ? c.toLowerCase() : c.toUpperCase()).join(''); export const randomStringBase36 = (length) => Array.from({ length }, () => Math.random().toString(36)[2]).join(''); export const obfuscatePhoneNumber = (num) => { const match = num.match(/^(\d{5})(\d{4})(.*)$/); return match ? `${match[1]}****${match[3]}` : num; }; export const countWordsByLength = (str) => str.trim().split(/\s+/).filter(Boolean).reduce((acc, word) => { const len = word.length; if (len > 0) { acc[len] = (acc[len] || 0) + 1; } return acc; }, {}); export const stringToArrayBuffer = (str) => new TextEncoder().encode(str).buffer; export const arrayBufferToString = (buf) => new TextDecoder().decode(buf); export const isStrongPassword = (str) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\w\d\s:]).{8,}$/.test(str); export const getLongestWord = (str) => { const words = str.trim().split(/\s+/).filter(Boolean); if (words.length === 0) return ''; return words.reduce((a, b) => a.length >= b.length ? a : b, ''); }; export const getShortestWord = (str) => { const words = str.trim().split(/\s+/).filter(Boolean); if (words.length === 0) return ''; return words.reduce((a, b) => (a.length <= b.length ? a : b)); }; export const getAllIndexesOf = (str, target, overlapping) => { if (target.length === 0) return Array.from({ length: str.length + 1 }, (_, i) => i); const indices = []; let i = -1; while ((i = str.indexOf(target, i)) !== -1) { indices.push(i); if (!overlapping) { i = i + target?.length; } else { i++; } } return indices; };