const GROUP_RE = (id) => new RegExp(`<g id="${id}"[^>]*>([\\s\\S]*?)</g>`);
const ATTR_RE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)="([^"]*)"/g;
function extractAttrs(tagOpenSource) {
const attrs = {};
ATTR_RE.lastIndex = 0;
let m;
while ((m = ATTR_RE.exec(tagOpenSource))) {
attrs[m[1]] = m[2];
}
return attrs;
}
function extractTags(groupContent, tagName) {
const re = new RegExp(`<${tagName}\\b([^>]*)>`, "g");
const tags = [];
let m;
while ((m = re.exec(groupContent))) {
tags.push(extractAttrs(m[1]));
}
return tags;
}
/**
* Extracts the handful of attributes createMap() needs from a topo-map SVG's raw text, with
* no DOM/DOMParser involved — this must run in a Web Worker or Node worker_threads thread,
* neither of which has DOMParser. #polygons is parsed (so its escaped data-points JSON
* attribute is exercised) but intentionally discarded — see
* notes/Programming/specs/2026-08-03-createmap-topo-refactor-design.md.
*/
export function parseSvgMetadata(svgText) {
const rootMatch = svgText.match(/<svg\b([^>]*)>/);
const rootAttrs = rootMatch ? extractAttrs(rootMatch[1]) : {};
let width, height;
if (rootAttrs.viewBox) {
const parts = rootAttrs.viewBox.trim().split(/[\s,]+/).map(Number);
width = parts[2];
height = parts[3];
} else {
width = parseFloat(rootAttrs.width) || 800;
height = parseFloat(rootAttrs.height) || 600;
}
const sitesMatch = svgText.match(GROUP_RE("sites"));
const sites = sitesMatch
? extractTags(sitesMatch[1], "image")
.filter((attrs) => attrs["data-types"])
.map((attrs) => ({
id: attrs["data-id"],
types: attrs["data-types"].split(",").filter(Boolean),
}))
: [];
const labelsMatch = svgText.match(GROUP_RE("labels"));
const labels = labelsMatch
? extractTags(labelsMatch[1], "text")
.filter((attrs) => attrs["data-type"])
.map((attrs) => ({
id: attrs["data-id"],
type: attrs["data-type"],
group: attrs["data-group"] ?? null,
}))
: [];
// #polygons isn't turned into Locations yet — parsed via the same extractTags/extractAttrs
// path used for #sites/#labels (so its escaped data-points JSON attribute is genuinely
// exercised) and discarded. See notes/Programming/specs/2026-08-03-createmap-topo-refactor-design.md.
const polygonsMatch = svgText.match(GROUP_RE("polygons"));
if (polygonsMatch) extractTags(polygonsMatch[1], "path");
return { name: rootAttrs["data-topo-name"], width, height, sites, labels };
}