UNPKG

legal-markdown-js

Version:

Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version

558 lines 19.2 kB
/** * @fileoverview String Manipulation and Formatting Helpers * * This module provides comprehensive string manipulation utilities designed * for legal document processing. It includes functions for case conversion, * text formatting, string validation, and various text processing operations * commonly needed in legal document generation and formatting. * * Features: * - Case conversion (capitalize, title case, upper, lower) * - String formatting (camelCase, kebab-case, snake_case, PascalCase) * - Text manipulation (truncate, clean, pad, pluralize) * - String validation and search (contains, replace) * - Name processing (initials extraction) * - Safe handling of null/undefined values * * @example * ```typescript * import { capitalize, titleCase, pluralize } from './string-helpers.js'; * * // Case conversion * const title = titleCase('legal document processing'); // \"Legal Document Processing\"\n *\n * // Text formatting\n * const name = capitalize('john doe'); // \"John doe\"\n *\n * // Pluralization\n * const items = pluralize('contract', 5); // \"contracts\"\n * ```\n */ /** * Capitalizes the first letter of a string * * Converts the first character to uppercase and the rest to lowercase. * Safely handles null and undefined values by returning an empty string. * * @param {string | undefined | null} str - The string to capitalize * @returns {string} The capitalized string, or empty string if input is null/undefined * * @example * ```typescript * capitalize('hello world'); // "Hello world" * capitalize('HELLO WORLD'); // "Hello world" * capitalize('hELLO wORLD'); // "Hello world" * capitalize(''); // "" * capitalize(null); // "" * capitalize(undefined); // "" * ``` */ export function capitalize(str) { if (!str) return ''; return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); } /** * Capitalizes the first letter of each word in a string * * Applies the capitalize function to each word separated by spaces, * useful for formatting names, titles, and other multi-word strings. * * @param {string | undefined | null} str - The string to capitalize * @returns {string} The string with each word capitalized * * @example * ```typescript * capitalizeWords('john doe'); // "John Doe" * capitalizeWords('legal document'); // "Legal Document" * capitalizeWords('HELLO WORLD'); // "Hello World" * capitalizeWords('mixed CaSe WoRdS'); // "Mixed Case Words" * capitalizeWords(''); // "" * capitalizeWords(null); // "" * ``` */ export function capitalizeWords(str) { if (!str) return ''; return str .split(' ') .map(word => capitalize(word)) .join(' '); } /** * Converts a string to uppercase * * Safely converts all characters in the string to uppercase, * handling null and undefined values gracefully. * * @param {string | undefined | null} str - The string to convert * @returns {string} The uppercase string, or empty string if input is null/undefined * * @example * ```typescript * upper('hello world'); // "HELLO WORLD" * upper('Mixed Case'); // "MIXED CASE" * upper(''); // "" * upper(null); // "" * upper(undefined); // "" * ``` */ export function upper(str) { return str ? str.toUpperCase() : ''; } /** * Converts a string to lowercase * * Safely converts all characters in the string to lowercase, * handling null and undefined values gracefully. * * @param {string | undefined | null} str - The string to convert * @returns {string} The lowercase string, or empty string if input is null/undefined * * @example * ```typescript * lower('HELLO WORLD'); // "hello world" * lower('Mixed Case'); // "mixed case" * lower(''); // "" * lower(null); // "" * lower(undefined); // "" * ``` */ export function lower(str) { return str ? str.toLowerCase() : ''; } /** * Converts a string to title case with proper handling of articles and prepositions * * Applies title case rules where the first and last words are always capitalized, * and small words (articles, prepositions, conjunctions) are kept lowercase * unless they appear at the beginning or end of the string. * * @param {string | undefined | null} str - The string to convert to title case * @returns {string} The title-cased string * * Small words that remain lowercase: a, an, and, as, at, but, by, for, if, in, * nor, of, on, or, so, the, to, up, yet * * @example * ```typescript * titleCase('the quick brown fox'); // "The Quick Brown Fox" * titleCase('a tale of two cities'); // "A Tale of Two Cities" * titleCase('for whom the bell tolls'); // "For Whom the Bell Tolls" * titleCase('HELLO WORLD'); // "Hello World" * titleCase(''); // "" * titleCase(null); // "" * ``` */ export function titleCase(str) { if (!str) return ''; const smallWords = new Set([ 'a', 'an', 'and', 'as', 'at', 'but', 'by', 'for', 'if', 'in', 'nor', 'of', 'on', 'or', 'so', 'the', 'to', 'up', 'yet', ]); return str .split(' ') .map((word, index) => { const lowerWord = word.toLowerCase(); // Always capitalize first and last word if (index === 0 || index === str.split(' ').length - 1) { return capitalize(word); } // Don't capitalize small words unless they start the string if (smallWords.has(lowerWord)) { return lowerWord; } return capitalize(word); }) .join(' '); } /** * Converts a string to kebab-case format * * Transforms camelCase, PascalCase, or space-separated strings into * kebab-case (lowercase with hyphens). Useful for URL slugs, CSS classes, * and file names. * * @param {string | undefined | null} str - The string to convert * @returns {string} The kebab-cased string * * @example * ```typescript * kebabCase('camelCaseString'); // "camel-case-string" * kebabCase('PascalCaseString'); // "pascal-case-string" * kebabCase('normal string'); // "normal-string" * kebabCase('mixed_case string'); // "mixed-case-string" * kebabCase(''); // "" * kebabCase(null); // "" * ``` */ export function kebabCase(str) { if (!str) return ''; return str .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/[\s_]+/g, '-') .toLowerCase(); } /** * Converts a string to snake_case format * * Transforms camelCase, PascalCase, or space-separated strings into * snake_case (lowercase with underscores). Commonly used for variable * names, database fields, and API parameters. * * @param {string | undefined | null} str - The string to convert * @returns {string} The snake_cased string * * @example * ```typescript * snakeCase('camelCaseString'); // "camel_case_string" * snakeCase('PascalCaseString'); // "pascal_case_string" * snakeCase('normal string'); // "normal_string" * snakeCase('kebab-case-string'); // "kebab_case_string" * snakeCase(''); // "" * snakeCase(null); // "" * ``` */ export function snakeCase(str) { if (!str) return ''; return str .replace(/([a-z])([A-Z])/g, '$1_$2') .replace(/[\s-]+/g, '_') .toLowerCase(); } /** * Converts a string to camelCase format * * Transforms space-separated, hyphenated, or snake_case strings into * camelCase where the first word is lowercase and subsequent words * are capitalized. Commonly used for JavaScript variable names. * * @param {string | undefined | null} str - The string to convert * @returns {string} The camelCased string * * @example * ```typescript * camelCase('normal string'); // "normalString" * camelCase('kebab-case-string'); // "kebabCaseString" * camelCase('snake_case_string'); // "snakeCaseString" * camelCase('PascalCaseString'); // "pascalCaseString" * camelCase(''); // "" * camelCase(null); // "" * ``` */ export function camelCase(str) { if (!str) return ''; // Split on spaces, hyphens, underscores, or camelCase boundaries const words = str .replace(/([a-z])([A-Z])/g, '$1 $2') // Insert space before capitals in camelCase .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // Handle acronyms like "XMLParser" .split(/[\s-_]+/) .filter(Boolean); return words .map((word, index) => { if (index === 0) { return word.toLowerCase(); } return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }) .join(''); } /** * Converts a string to PascalCase format * * Transforms strings into PascalCase where the first letter of each word * is capitalized and there are no spaces or separators. Commonly used * for class names, constructor functions, and type names. * * @param {string | undefined | null} str - The string to convert * @returns {string} The PascalCased string * * @example * ```typescript * pascalCase('normal string'); // "NormalString" * pascalCase('kebab-case-string'); // "KebabCaseString" * pascalCase('snake_case_string'); // "SnakeCaseString" * pascalCase('camelCaseString'); // "CamelCaseString" * pascalCase(''); // "" * pascalCase(null); // "" * ``` */ export function pascalCase(str) { if (!str) return ''; const camel = camelCase(str); return camel.charAt(0).toUpperCase() + camel.slice(1); } /** * Truncates a string to a specified length with optional suffix * * Shortens a string to the specified maximum length and appends a suffix * (default: '...') if the string exceeds the limit. Useful for creating * previews, summaries, or fitting text into constrained spaces. * * @param {string | undefined | null} str - The string to truncate * @param {number} length - The maximum length of the result * @param {string} suffix - The suffix to append when truncating (default: '...') * @returns {string} The truncated string with suffix if needed * * @example * ```typescript * truncate('This is a long string', 10); // "This is a..." * truncate('Short', 10); // "Short" * truncate('This is a long string', 15, '…'); // "This is a long…" * truncate('', 10); // "" * truncate(null, 10); // "" * ``` */ export function truncate(str, length, suffix = '...') { if (!str) return ''; if (str.length <= length) return str; if (length <= suffix.length) return str.slice(0, length); return str.slice(0, length - suffix.length) + suffix; } /** * Removes extra whitespace and trims a string * * Collapses multiple consecutive whitespace characters into single spaces * and removes leading/trailing whitespace. Useful for cleaning up user * input or text from various sources. * * @param {string | undefined | null} str - The string to clean * @returns {string} The cleaned string with normalized whitespace * * @example * ```typescript * clean(' hello world '); // "hello world" * clean('\t\n text \r\n'); // "text" * clean('normal spacing'); // "normal spacing" * clean(''); // "" * clean(null); // "" * ``` */ export function clean(str) { if (!str) return ''; return str.replace(/\s+/g, ' ').trim(); } /** * Pluralizes a word based on count with optional custom plural form * * Returns the singular form if count is 1, otherwise returns the plural form. * Can accept a custom plural form or will apply basic English pluralization * rules automatically. * * @param {string} word - The singular form of the word * @param {number} count - The count to determine singular/plural * @param {string} [plural] - Optional custom plural form * @returns {string} The appropriate singular or plural form * * Basic pluralization rules applied: * - Words ending in 's', 'x', 'z', 'sh', 'ch' → add 'es' * - Words ending in consonant + 'y' → change 'y' to 'ies' * - All other words → add 's' * * @example * ```typescript * pluralize('cat', 1); // "cat" * pluralize('cat', 2); // "cats" * pluralize('box', 5); // "boxes" * pluralize('city', 3); // "cities" * pluralize('child', 2, 'children'); // "children" * pluralize('person', 1, 'people'); // "person" * pluralize('person', 3, 'people'); // "people" * ``` */ export function pluralize(word, count, plural) { // Handle Handlebars options object as last argument if (typeof plural === 'object') { plural = undefined; } if (count === 1) return word; if (plural) return plural; // Simple pluralization rules const rules = [ [/(s|x|z|sh|ch)$/i, '$&es'], [/([^aeiou])y$/i, '$1ies'], [/$/i, 's'], ]; for (const [pattern, replacement] of rules) { if (pattern.test(word)) { return word.replace(pattern, replacement); } } return word + 's'; } /** * Pads a string to a specified length from the start (left side) * * Adds padding characters to the beginning of a string until it reaches * the specified length. Useful for formatting numbers, creating aligned * text, or ensuring consistent string lengths. * * @param {string | undefined | null} str - The string to pad * @param {number} length - The target length of the result * @param {string} char - The character to use for padding (default: ' ') * @returns {string} The padded string * * @example * ```typescript * padStart('42', 5); // " 42" * padStart('42', 5, '0'); // "00042" * padStart('hello', 8, '-'); // "---hello" * padStart('toolong', 4); // "toolong" (no padding if already longer) * padStart('', 3); // " " * padStart(null, 5); // " " * ``` */ export function padStart(str, length, char = ' ') { if (!str && str !== 0) return char.repeat(length); const strValue = String(str); return strValue.padStart(length, char); } /** * Pads a string to a specified length from the end (right side) * * Adds padding characters to the end of a string until it reaches * the specified length. Useful for creating aligned columns, * formatting output, or ensuring consistent string lengths. * * @param {string | undefined | null} str - The string to pad * @param {number} length - The target length of the result * @param {string} char - The character to use for padding (default: ' ') * @returns {string} The padded string * * @example * ```typescript * padEnd('42', 5); // "42 " * padEnd('42', 5, '0'); // "42000" * padEnd('hello', 8, '-'); // "hello---" * padEnd('toolong', 4); // "toolong" (no padding if already longer) * padEnd('', 3); // " " * padEnd(null, 5); // " " * ``` */ export function padEnd(str, length, char = ' ') { if (!str && str !== 0) return char.repeat(length); const strValue = String(str); return strValue.padEnd(length, char); } /** * Checks if a string contains a substring with optional case sensitivity * * Performs substring search with configurable case sensitivity. * Useful for filtering, validation, and text analysis. * * @param {string | undefined | null} str - The string to search in * @param {string} substring - The substring to search for * @param {boolean} caseSensitive - Whether the search is case sensitive (default: false) * @returns {boolean} True if the substring is found, false otherwise * * @example * ```typescript * contains('Hello World', 'hello'); // true (case insensitive) * contains('Hello World', 'hello', true); // false (case sensitive) * contains('Hello World', 'World'); // true * contains('Hello World', 'xyz'); // false * contains('', 'test'); // false * contains(null, 'test'); // false * contains('test', ''); // false * ``` */ export function contains(str, substring, caseSensitive = false) { if (!str || !substring) return false; if (caseSensitive) { return str.includes(substring); } return str.toLowerCase().includes(substring.toLowerCase()); } /** * Replaces all occurrences of a substring with a replacement string * * Performs global string replacement without using regular expressions. * Useful for text processing, template substitution, and content cleanup. * * @param {string | undefined | null} str - The string to perform replacements on * @param {string} search - The substring to search for * @param {string} replace - The replacement string * @returns {string} The string with all occurrences replaced * * @example * ```typescript * replaceAll('hello world hello', 'hello', 'hi'); // "hi world hi" * replaceAll('a,b,c', ',', ' | '); // "a | b | c" * replaceAll('test', 'x', 'y'); // "test" (no change) * replaceAll('', 'x', 'y'); // "" * replaceAll(null, 'x', 'y'); // "" * ``` */ export function replaceAll(str, search, replace) { if (!str) return ''; return str.split(search).join(replace); } /** * Extracts initials from a name or multi-word string * * Takes the first character of each word (separated by spaces) and * combines them into uppercase initials. Useful for creating * abbreviated representations of names or titles. * * @param {string | undefined | null} name - The name or string to extract initials from * @returns {string} The initials in uppercase * * @example * ```typescript * initials('John Doe'); // "JD" * initials('Mary Jane Watson'); // "MJW" * initials('single'); // "S" * initials('jean-claude van damme'); // "JVD" (hyphens treated as separators) * initials(''); // "" * initials(null); // "" * ``` */ export function initials(name) { if (!name) return ''; return name .split(/[\s-]+/) .filter(Boolean) .map(word => word.charAt(0).toUpperCase()) .join(''); } export function join(arr, separator = ', ') { if (!Array.isArray(arr)) return String(arr); return arr.map(String).join(separator); } export function length(value) { if (Array.isArray(value)) return value.length; if (typeof value === 'string') return value.length; return 0; } export function defaultVal(value, fallback = '') { return value || fallback; } //# sourceMappingURL=string-helpers.js.map