/** * Stat sheet template engine — parses *.sheet.svg files and extracts * elements containing ${key} template patterns. * * The SVG element is parsed once via DOMParser. The caller (StatsView) * reactively updates the text nodes whenever the stats store changes. */ /** Regex matching ${key} template patterns (captures the key name). */ const TEMPLATE_RE = /\$\{(\w+)\}/g; /** A single template binding in a element. */ export interface TextTemplate { el: SVGTextElement; template: string; keys: string[]; } /** The result of parsing a stat sheet SVG string. */ export interface ParsedSheet { svgElement: SVGSVGElement; templates: TextTemplate[]; } /** * Parse an SVG string into a live DOM element and extract all text * elements that contain ${key} template patterns. */ export function parseSheet(svgString: string): ParsedSheet { const parser = new DOMParser(); const doc = parser.parseFromString(svgString, "image/svg+xml"); const svgElement = doc.documentElement as unknown as SVGSVGElement; if (!svgElement || svgElement.tagName !== "svg") { return { svgElement: document.createElementNS( "http://www.w3.org/2000/svg", "svg", ) as SVGSVGElement, templates: [], }; } const templates: TextTemplate[] = []; const textEls = svgElement.querySelectorAll("text"); for (const el of textEls) { const content = el.textContent; if (!content) continue; TEMPLATE_RE.lastIndex = 0; const keys: string[] = []; let m: RegExpExecArray | null; while ((m = TEMPLATE_RE.exec(content)) !== null) { keys.push(m[1]); } if (keys.length > 0) { templates.push({ el: el as SVGTextElement, template: content, keys }); } } return { svgElement, templates }; }