xml.browser.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. const parser = new DOMParser();
  4. export function parseXML(str: string): Promise<any> {
  5. try {
  6. const dom = parser.parseFromString(str, "application/xml");
  7. throwIfError(dom);
  8. const obj = domToObject(dom.childNodes[0]);
  9. return Promise.resolve(obj);
  10. } catch (err) {
  11. return Promise.reject(err);
  12. }
  13. }
  14. let errorNS = "";
  15. try {
  16. errorNS = parser.parseFromString("INVALID", "text/xml").getElementsByTagName("parsererror")[0].namespaceURI!;
  17. } catch (ignored) {
  18. // Most browsers will return a document containing <parsererror>, but IE will throw.
  19. }
  20. function throwIfError(dom: Document) {
  21. if (errorNS) {
  22. const parserErrors = dom.getElementsByTagNameNS(errorNS, "parsererror");
  23. if (parserErrors.length) {
  24. throw new Error(parserErrors.item(0)!.innerHTML);
  25. }
  26. }
  27. }
  28. function isElement(node: Node): node is Element {
  29. return !!(node as Element).attributes;
  30. }
  31. /**
  32. * Get the Element-typed version of the provided Node if the provided node is an element with
  33. * attributes. If it isn't, then undefined is returned.
  34. */
  35. function asElementWithAttributes(node: Node): Element | undefined {
  36. return isElement(node) && node.hasAttributes() ? node : undefined;
  37. }
  38. function domToObject(node: Node): any {
  39. let result: any = {};
  40. const childNodeCount: number = node.childNodes.length;
  41. const firstChildNode: Node = node.childNodes[0];
  42. const onlyChildTextValue: string | undefined = (firstChildNode && childNodeCount === 1 && firstChildNode.nodeType === Node.TEXT_NODE && firstChildNode.nodeValue) || undefined;
  43. const elementWithAttributes: Element | undefined = asElementWithAttributes(node);
  44. if (elementWithAttributes) {
  45. result["$"] = {};
  46. for (let i = 0; i < elementWithAttributes.attributes.length; i++) {
  47. const attr = elementWithAttributes.attributes[i];
  48. result["$"][attr.nodeName] = attr.nodeValue;
  49. }
  50. if (onlyChildTextValue) {
  51. result["_"] = onlyChildTextValue;
  52. }
  53. } else if (childNodeCount === 0) {
  54. result = "";
  55. } else if (onlyChildTextValue) {
  56. result = onlyChildTextValue;
  57. }
  58. if (!onlyChildTextValue) {
  59. for (let i = 0; i < childNodeCount; i++) {
  60. const child = node.childNodes[i];
  61. // Ignore leading/trailing whitespace nodes
  62. if (child.nodeType !== Node.TEXT_NODE) {
  63. const childObject: any = domToObject(child);
  64. if (!result[child.nodeName]) {
  65. result[child.nodeName] = childObject;
  66. } else if (Array.isArray(result[child.nodeName])) {
  67. result[child.nodeName].push(childObject);
  68. } else {
  69. result[child.nodeName] = [result[child.nodeName], childObject];
  70. }
  71. }
  72. }
  73. }
  74. return result;
  75. }
  76. // tslint:disable-next-line:no-null-keyword
  77. const doc = document.implementation.createDocument(null, null, null);
  78. const serializer = new XMLSerializer();
  79. export function stringifyXML(obj: any, opts?: { rootName?: string }) {
  80. const rootName = opts && opts.rootName || "root";
  81. const dom = buildNode(obj, rootName)[0];
  82. return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + serializer.serializeToString(dom);
  83. }
  84. function buildAttributes(attrs: { [key: string]: { toString(): string; } }): Attr[] {
  85. const result = [];
  86. for (const key of Object.keys(attrs)) {
  87. const attr = doc.createAttribute(key);
  88. attr.value = attrs[key].toString();
  89. result.push(attr);
  90. }
  91. return result;
  92. }
  93. function buildNode(obj: any, elementName: string): Node[] {
  94. if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
  95. const elem = doc.createElement(elementName);
  96. elem.textContent = obj.toString();
  97. return [elem];
  98. }
  99. else if (Array.isArray(obj)) {
  100. const result = [];
  101. for (const arrayElem of obj) {
  102. for (const child of buildNode(arrayElem, elementName)) {
  103. result.push(child);
  104. }
  105. }
  106. return result;
  107. } else if (typeof obj === "object") {
  108. const elem = doc.createElement(elementName);
  109. for (const key of Object.keys(obj)) {
  110. if (key === "$") {
  111. for (const attr of buildAttributes(obj[key])) {
  112. elem.attributes.setNamedItem(attr);
  113. }
  114. } else {
  115. for (const child of buildNode(obj[key], key)) {
  116. elem.appendChild(child);
  117. }
  118. }
  119. }
  120. return [elem];
  121. }
  122. else {
  123. throw new Error(`Illegal value passed to buildObject: ${obj}`);
  124. }
  125. }