export function createElement( tagName: string, attributes: Record = {}, ) { const element = document.createElementNS( 'http://www.w3.org/2000/svg', tagName, ) as T; setAttributes(element, attributes); return element; } export function parseSVG( svg: string, ): T | null { const parser = new DOMParser(); const doc = parser.parseFromString(svg, 'image/svg+xml'); const errorNode = doc.querySelector('parsererror'); if (errorNode) { throw new Error('Invalid SVG string'); } return doc.documentElement as unknown as T; } export function setAttributes(element: SVGElement, attrs: Record) { Object.entries(attrs).forEach(([key, value]) => { if (value === null || value === undefined) { element.removeAttribute(key); } else { element.setAttribute(key, value); } }); } export function getAttributes( element: SVGElement, attrs: readonly string[], ignoreEmpty = true, ) { return attrs.reduce( (acc, attr) => { const value = element.getAttribute(attr); if ( !ignoreEmpty || (value !== null && value !== '' && value !== undefined) ) { acc[attr] = value; } return acc; }, {} as Record, ); } export function removeAttributes(element: SVGElement, attrs: string[]) { attrs.forEach((attr) => { element.removeAttribute(attr); }); } export function traverse( node: SVGElement, callback: (child: T) => boolean | void, ) { if (callback(node as SVGElement) === false) return; const children = Array.from(node.children); children.forEach((child) => { traverse(child as SVGElement, callback); }); } export function getOrCreateDefs( svg: SVGSVGElement, defsId: string = 'infographic-defs', ): SVGDefsElement { const selector = defsId ? `defs#${defsId}` : 'defs'; const defs = svg.querySelector(selector); if (defs) return defs; const newDefs = createElement('defs'); if (defsId) newDefs.id = defsId; svg.prepend(newDefs); return newDefs; }