/** * Decode HTML entities in strings * Converts ' " & < > etc. to their actual characters */ export function decodeHtmlEntities(text: string): string { if (!text || typeof text !== 'string') { return text; } // Create a temporary element to decode HTML entities const textarea = document.createElement('textarea'); textarea.innerHTML = text; return textarea.value; } /** * Server-side HTML entity decoding (for Node.js/Next.js API routes) */ export function decodeHtmlEntitiesServer(text: string): string { if (!text || typeof text !== 'string') { return text; } // Map of common HTML entities (including all variations of apostrophe) const entityMap: Record = { ''': "'", '"': '"', '&': '&', '<': '<', '>': '>', ''': "'", ''': "'", '/': '/', '`': '`', '=': '=', '’': "'", '‘': "'", '”': '"', '“': '"', }; // First replace known entities let decoded = text; for (const [entity, replacement] of Object.entries(entityMap)) { decoded = decoded.replace(new RegExp(entity, 'gi'), replacement); } // Then handle numeric entities (' ' etc.) decoded = decoded.replace(/&#(\d+);/g, (match, num) => { return String.fromCharCode(parseInt(num, 10)); }); decoded = decoded.replace(/&#x([0-9a-f]+);/gi, (match, hex) => { return String.fromCharCode(parseInt(hex, 16)); }); return decoded; }