/** * KaTeX Render Helper * Wraps katex.render() with error handling for both display and inline modes. * Security-hardened with strict mode and LaTeX sanitization. */ const KatexRenderer = { /** * Macro whitelist — only macros that KaTeX does NOT natively support. * Prevents injection via \href, \url, \includegraphics, etc. * REMOVED: Standard KaTeX commands (\vec, \overrightarrow, \cdot, \times, * \sqrt, \frac, \pi, \alpha, \beta, \text, \implies, \neq, \infty, \lambda, * \mu, \sum, \int, \dots, \vdots, \ddots, \hline, \det, \operatorname) * — KaTeX already supports these natively; redefining them causes infinite * recursion or strict mode violations. */ KATEX_MACROS: { '\\ran': '\\text{ran}', '\\nul': '\\text{nul}', '\\rg': '\\text{rg}', '\\sen': '\\sin', '\\tg': '\\tan' }, KATEX_OPTIONS: { displayMode: true, throwOnError: false, trust: false, strict: false, macros: {} }, /** * Sanitize LaTeX input by removing potentially dangerous commands. * @param {string} latex - Raw LaTeX string * @returns {string} - Sanitized LaTeX safe for KaTeX */ sanitizeLatex(latex) { if (!latex || typeof latex !== 'string') return ''; return latex .replace(/\\href\{[^}]*\}\{[^}]*\}/g, '') .replace(/\\url\{[^}]*\}/g, '') .replace(/\\includegraphics\{[^}]*\}/g, '') .replace(/\\write\{[^}]*\}/g, '') .replace(/\\input\{[^}]*\}/g, '') .replace(/\\include\{[^}]*\}/g, '') .replace(/\\href\{[^}]*\}/g, ''); }, /** * Render a LaTeX expression into a DOM element. * @param {HTMLElement|string} el - Target element or CSS selector * @param {string} latex - LaTeX string to render * @param {Object} options - KaTeX options override * @returns {boolean} true if rendered successfully */ render(el, latex, options = {}) { const target = typeof el === 'string' ? document.querySelector(el) : el; if (!target) { console.warn('[KatexRenderer] Element not found:', el); return false; } if (typeof katex === 'undefined') { target.textContent = latex; console.warn('[KatexRenderer] KaTeX not loaded'); return false; } const mergedOptions = Object.assign({}, this.KATEX_OPTIONS, { macros: Object.assign({}, this.KATEX_MACROS, options.macros || {}) }); const sanitized = this.sanitizeLatex(latex); try { katex.render(sanitized, target, mergedOptions); return true; } catch (e) { target.innerHTML = '[Error LaTeX]'; console.warn('[KatexRenderer] Parse error:', e.message, '\nLaTeX:', sanitized); return false; } }, /** * Render LaTeX in display mode (centered block). */ renderDisplay(el, latex) { return this.render(el, latex, { displayMode: true }); }, /** * Render LaTeX in inline mode. */ renderInline(el, latex) { return this.render(el, latex, { displayMode: false }); }, /** * Render all math expressions inside a container element. * Uses KaTeX auto-render extension for $...$ and $$...$$ delimiters. * @param {HTMLElement} container - DOM element to scan for math */ renderAll(container) { if (typeof renderMathInElement === 'undefined') { console.warn('[KatexRenderer] auto-render extension not loaded'); return; } renderMathInElement(container || document.body, { delimiters: [ { left: '$$', right: '$$', display: true }, { left: '$', right: '$', display: false }, { left: '\\(', right: '\\)', display: false }, { left: '\\[', right: '\\]', display: true } ], throwOnError: false, trust: false, strict: false, macros: this.KATEX_MACROS }); }, /** * Create a span with rendered math and return it. * Useful for building DOM fragments. */ createMathSpan(latex, displayMode = false) { const span = document.createElement('span'); this.render(span, latex, { displayMode }); return span; } };