ttrpg-tools/src/components/journal/stat-sheet.ts

64 lines
1.8 KiB
TypeScript

/**
* Stat sheet template engine — parses *.sheet.svg files and extracts
* <text> 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 <text> 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 };
}