import type { ParsedXmlElement, ParsedXmlNode } from "@/worker/xml/parser"; import type { QName } from "@/worker/xml/namespaces"; export function splitXmlName(name: string): { prefix: string; localName: string } { const index = name.indexOf(":"); if (index === -1) return { prefix: "", localName: name }; return { prefix: name.slice(0, index), localName: name.slice(index + 1) }; } function elementChildren(element: ParsedXmlElement): ParsedXmlElement[] { return element.children.filter((child): child is ParsedXmlElement => typeof child !== "string"); } export function namespaceMap(element: ParsedXmlElement, parent: Record = {}): Record { const namespaces = { ...parent }; for (const [name, value] of Object.entries(element.attributes)) { if (name === "xmlns") namespaces[""] = value; if (name.startsWith("xmlns:")) namespaces[name.slice("xmlns:".length)] = value; } return namespaces; } export function qnameForElement(element: ParsedXmlElement, namespaces = namespaceMap(element)): QName { const { prefix, localName } = splitXmlName(element.name); return { nsUri: namespaces[prefix] ?? "", localName }; } export interface NamedXmlElement { element: ParsedXmlElement; qname: QName; namespaces: Record; } export function namedElement( element: ParsedXmlElement, parentNamespaces: Record = {}, ): NamedXmlElement { const namespaces = namespaceMap(element, parentNamespaces); return { element, qname: qnameForElement(element, namespaces), namespaces }; } export function namedElementChildren( element: ParsedXmlElement, parentNamespaces: Record, ): NamedXmlElement[] { const namespaces = namespaceMap(element, parentNamespaces); return elementChildren(element).map((child) => namedElement(child, namespaces)); } export function elementsByQName( element: ParsedXmlElement, qname: QName, parentNamespaces: Record = {}, ): NamedXmlElement[] { const matches: NamedXmlElement[] = []; for (const child of namedElementChildren(element, parentNamespaces)) { if (child.qname.nsUri === qname.nsUri && child.qname.localName === qname.localName) matches.push(child); matches.push(...elementsByQName(child.element, qname, child.namespaces)); } return matches; } export function textContent(node: ParsedXmlNode): string { if (typeof node === "string") return node; return node.children.map(textContent).join(""); } export function firstTextByQName( element: ParsedXmlElement, qname: QName, parentNamespaces: Record = {}, ): string | null { return elementsByQName(element, qname, parentNamespaces).at(0)?.element.children.map(textContent).join("") ?? null; }