1 |
|
2 |
|
3 |
|
4 | const parser = new DOMParser();
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | let ttPolicy: Pick<TrustedTypePolicy, "createHTML"> | undefined;
|
13 | try {
|
14 | if (typeof self.trustedTypes !== "undefined") {
|
15 | ttPolicy = self.trustedTypes.createPolicy("@azure/ms-rest-js#xml.browser", {
|
16 | createHTML: (s) => s,
|
17 | });
|
18 | }
|
19 | } catch (e) {
|
20 | console.warn('Could not create trusted types policy "@azure/ms-rest-js#xml.browser"');
|
21 | }
|
22 |
|
23 | export function parseXML(str: string): Promise<any> {
|
24 | try {
|
25 | const dom = parser.parseFromString((ttPolicy?.createHTML(str) ?? str) as string, "application/xml");
|
26 | throwIfError(dom);
|
27 |
|
28 | const obj = domToObject(dom.childNodes[0]);
|
29 | return Promise.resolve(obj);
|
30 | } catch (err) {
|
31 | return Promise.reject(err);
|
32 | }
|
33 | }
|
34 |
|
35 | let errorNS = "";
|
36 | try {
|
37 | const invalidXML = (ttPolicy?.createHTML("INVALID") ?? "INVALID") as string;
|
38 | errorNS =
|
39 | parser.parseFromString(invalidXML, "text/xml").getElementsByTagName("parsererror")[0]
|
40 | .namespaceURI! ?? "";
|
41 | } catch (ignored) {
|
42 |
|
43 | }
|
44 |
|
45 | function throwIfError(dom: Document) {
|
46 | if (errorNS) {
|
47 | const parserErrors = dom.getElementsByTagNameNS(errorNS, "parsererror");
|
48 | if (parserErrors.length) {
|
49 | throw new Error(parserErrors.item(0)!.innerHTML);
|
50 | }
|
51 | }
|
52 | }
|
53 |
|
54 | function isElement(node: Node): node is Element {
|
55 | return !!(node as Element).attributes;
|
56 | }
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | function asElementWithAttributes(node: Node): Element | undefined {
|
63 | return isElement(node) && node.hasAttributes() ? node : undefined;
|
64 | }
|
65 |
|
66 | function domToObject(node: Node): any {
|
67 | let result: any = {};
|
68 |
|
69 | const childNodeCount: number = node.childNodes.length;
|
70 |
|
71 | const firstChildNode: Node = node.childNodes[0];
|
72 | const onlyChildTextValue: string | undefined =
|
73 | (firstChildNode &&
|
74 | childNodeCount === 1 &&
|
75 | firstChildNode.nodeType === Node.TEXT_NODE &&
|
76 | firstChildNode.nodeValue) ||
|
77 | undefined;
|
78 |
|
79 | const elementWithAttributes: Element | undefined = asElementWithAttributes(node);
|
80 | if (elementWithAttributes) {
|
81 | result["$"] = {};
|
82 |
|
83 | for (let i = 0; i < elementWithAttributes.attributes.length; i++) {
|
84 | const attr = elementWithAttributes.attributes[i];
|
85 | result["$"][attr.nodeName] = attr.nodeValue;
|
86 | }
|
87 |
|
88 | if (onlyChildTextValue) {
|
89 | result["_"] = onlyChildTextValue;
|
90 | }
|
91 | } else if (childNodeCount === 0) {
|
92 | result = "";
|
93 | } else if (onlyChildTextValue) {
|
94 | result = onlyChildTextValue;
|
95 | }
|
96 |
|
97 | if (!onlyChildTextValue) {
|
98 | for (let i = 0; i < childNodeCount; i++) {
|
99 | const child = node.childNodes[i];
|
100 |
|
101 | if (child.nodeType !== Node.TEXT_NODE) {
|
102 | const childObject: any = domToObject(child);
|
103 | if (!result[child.nodeName]) {
|
104 | result[child.nodeName] = childObject;
|
105 | } else if (Array.isArray(result[child.nodeName])) {
|
106 | result[child.nodeName].push(childObject);
|
107 | } else {
|
108 | result[child.nodeName] = [result[child.nodeName], childObject];
|
109 | }
|
110 | }
|
111 | }
|
112 | }
|
113 |
|
114 | return result;
|
115 | }
|
116 |
|
117 |
|
118 | const doc = document.implementation.createDocument(null, null, null);
|
119 | const serializer = new XMLSerializer();
|
120 |
|
121 | export function stringifyXML(obj: any, opts?: { rootName?: string }) {
|
122 | const rootName = (opts && opts.rootName) || "root";
|
123 | const dom = buildNode(obj, rootName)[0];
|
124 | return (
|
125 | '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + serializer.serializeToString(dom)
|
126 | );
|
127 | }
|
128 |
|
129 | function buildAttributes(attrs: { [key: string]: { toString(): string } }): Attr[] {
|
130 | const result = [];
|
131 | for (const key of Object.keys(attrs)) {
|
132 | const attr = doc.createAttribute(key);
|
133 | attr.value = attrs[key].toString();
|
134 | result.push(attr);
|
135 | }
|
136 | return result;
|
137 | }
|
138 |
|
139 | function buildNode(obj: any, elementName: string): Node[] {
|
140 | if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
|
141 | const elem = doc.createElement(elementName);
|
142 | elem.textContent = obj.toString();
|
143 | return [elem];
|
144 | } else if (Array.isArray(obj)) {
|
145 | const result = [];
|
146 | for (const arrayElem of obj) {
|
147 | for (const child of buildNode(arrayElem, elementName)) {
|
148 | result.push(child);
|
149 | }
|
150 | }
|
151 | return result;
|
152 | } else if (typeof obj === "object") {
|
153 | const elem = doc.createElement(elementName);
|
154 | for (const key of Object.keys(obj)) {
|
155 | if (key === "$") {
|
156 | for (const attr of buildAttributes(obj[key])) {
|
157 | elem.attributes.setNamedItem(attr);
|
158 | }
|
159 | } else {
|
160 | for (const child of buildNode(obj[key], key)) {
|
161 | elem.appendChild(child);
|
162 | }
|
163 | }
|
164 | }
|
165 | return [elem];
|
166 | } else {
|
167 | throw new Error(`Illegal value passed to buildObject: ${obj}`);
|
168 | }
|
169 | }
|