Skip to content
File

Blob: src/worker/xml/tree.ts

typescript75 lines
1import type { ParsedXmlElement, ParsedXmlNode } from "@/worker/xml/parser";
2import type { QName } from "@/worker/xml/namespaces";
3 
4export function splitXmlName(name: string): { prefix: string; localName: string } {
5 const index = name.indexOf(":");
6 if (index === -1) return { prefix: "", localName: name };
7 return { prefix: name.slice(0, index), localName: name.slice(index + 1) };
8}
9 
10function elementChildren(element: ParsedXmlElement): ParsedXmlElement[] {
11 return element.children.filter((child): child is ParsedXmlElement => typeof child !== "string");
12}
13 
14export function namespaceMap(element: ParsedXmlElement, parent: Record<string, string> = {}): Record<string, string> {
15 const namespaces = { ...parent };
16 for (const [name, value] of Object.entries(element.attributes)) {
17 if (name === "xmlns") namespaces[""] = value;
18 if (name.startsWith("xmlns:")) namespaces[name.slice("xmlns:".length)] = value;
19 }
20 return namespaces;
21}
22 
23export function qnameForElement(element: ParsedXmlElement, namespaces = namespaceMap(element)): QName {
24 const { prefix, localName } = splitXmlName(element.name);
25 return { nsUri: namespaces[prefix] ?? "", localName };
26}
27 
28export interface NamedXmlElement {
29 element: ParsedXmlElement;
30 qname: QName;
31 namespaces: Record<string, string>;
32}
33 
34export function namedElement(
35 element: ParsedXmlElement,
36 parentNamespaces: Record<string, string> = {},
37): NamedXmlElement {
38 const namespaces = namespaceMap(element, parentNamespaces);
39 return { element, qname: qnameForElement(element, namespaces), namespaces };
40}
41 
42export function namedElementChildren(
43 element: ParsedXmlElement,
44 parentNamespaces: Record<string, string>,
45): NamedXmlElement[] {
46 const namespaces = namespaceMap(element, parentNamespaces);
47 return elementChildren(element).map((child) => namedElement(child, namespaces));
48}
49 
50export function elementsByQName(
51 element: ParsedXmlElement,
52 qname: QName,
53 parentNamespaces: Record<string, string> = {},
54): NamedXmlElement[] {
55 const matches: NamedXmlElement[] = [];
56 for (const child of namedElementChildren(element, parentNamespaces)) {
57 if (child.qname.nsUri === qname.nsUri && child.qname.localName === qname.localName) matches.push(child);
58 matches.push(...elementsByQName(child.element, qname, child.namespaces));
59 }
60 return matches;
61}
62 
63export function textContent(node: ParsedXmlNode): string {
64 if (typeof node === "string") return node;
65 return node.children.map(textContent).join("");
66}
67 
68export function firstTextByQName(
69 element: ParsedXmlElement,
70 qname: QName,
71 parentNamespaces: Record<string, string> = {},
72): string | null {
73 return elementsByQName(element, qname, parentNamespaces).at(0)?.element.children.map(textContent).join("") ?? null;
74}