/** * Reduziert HTML aus Übungs-Kurztexten auf eine kleine erlaubte Menge von Tags (ohne Attribute). * Für Anzeige mit dangerouslySetInnerHTML. */ const ALLOWED_TAGS = new Set(['b', 'strong', 'i', 'em', 'br', 'p', 'span', 'ul', 'ol', 'li']) function cleanTree(parent) { const nodes = Array.from(parent.childNodes) for (const node of nodes) { if (node.nodeType === Node.TEXT_NODE) continue if (node.nodeType !== Node.ELEMENT_NODE) { parent.removeChild(node) continue } const tag = node.tagName.toLowerCase() if (!ALLOWED_TAGS.has(tag)) { while (node.firstChild) { parent.insertBefore(node.firstChild, node) } parent.removeChild(node) continue } while (node.attributes.length > 0) { node.removeAttribute(node.attributes[0].name) } cleanTree(node) } } export function sanitizeExerciseRichText(html) { if (html == null || typeof html !== 'string') return '' const trimmed = html.trim() if (!trimmed) return '' const tpl = document.createElement('template') tpl.innerHTML = trimmed cleanTree(tpl.content) return tpl.innerHTML } export function coerceApiNameList(value) { if (Array.isArray(value)) return value.map(String).filter((s) => s.trim()) if (typeof value === 'string') { try { const p = JSON.parse(value) if (Array.isArray(p)) return p.map(String).filter((s) => s.trim()) } catch { return [] } } return [] }