Spaces:
Build error
Build error
| /** | |
| * @file XML utility functions shared across the application. | |
| */ | |
| /** | |
| * Pretty-prints an XML DOM Document by inserting whitespace text nodes. | |
| * This modifies the original document or node object in place and returns it. | |
| * | |
| * @param {Document} xmlDoc - The XML DOM Document object | |
| * @param {string|null} selector - A selector for querySelector() that targets a sub-node for pretty-printing | |
| * @param {string} [spacing=' '] - The string to use for each level of indentation (e.g., ' ' or '\t'). | |
| * @returns {Document} - The modified XML DOM Document object. | |
| */ | |
| export function prettyPrintXmlDom(xmlDoc, selector = null, spacing = ' ') { | |
| if (!(xmlDoc instanceof Document)) { | |
| throw new Error(`Invalid parameter: Expected document, got ${xmlDoc}`); | |
| } | |
| let root; | |
| if (selector) { | |
| root = xmlDoc.querySelector(selector); | |
| if (!root) { | |
| throw new Error(`Invalid selector: no node found for "${selector}"`); | |
| } | |
| } else { | |
| root = xmlDoc.documentElement; | |
| } | |
| // Helper function to remove existing pure whitespace text nodes | |
| function removeWhitespaceNodes(node) { | |
| const children = Array.from(node.childNodes); | |
| for (const child of children) { | |
| if (child.nodeType === Node.TEXT_NODE) { | |
| if (/^\s*$/.test(child.nodeValue)) { | |
| node.removeChild(child); | |
| } | |
| } else if (child.nodeType === Node.ELEMENT_NODE) { | |
| removeWhitespaceNodes(child); | |
| } | |
| } | |
| } | |
| // Helper recursive function to add indentation | |
| function addIndentation(node, depth, spacing, doc) { | |
| if (node.nodeType !== Node.ELEMENT_NODE) { | |
| return; | |
| } | |
| const indent = '\n' + spacing.repeat(depth); | |
| const children = Array.from(node.childNodes); | |
| let lastElementChild = null; | |
| for (const child of children) { | |
| if (child.nodeType === Node.ELEMENT_NODE) { | |
| node.insertBefore(doc.createTextNode(indent + spacing), child); | |
| addIndentation(child, depth + 1, spacing, doc); | |
| lastElementChild = child; | |
| } | |
| } | |
| if (lastElementChild !== null) { | |
| node.insertBefore(doc.createTextNode(indent), lastElementChild.nextSibling); | |
| } | |
| } | |
| // Clean up existing whitespace | |
| removeWhitespaceNodes(root); | |
| // Add indentation | |
| const rootChildren = Array.from(root.childNodes); | |
| let lastProcessedRootNode = null; | |
| for (const child of rootChildren) { | |
| if (child.nodeType === Node.ELEMENT_NODE) { | |
| root.insertBefore(xmlDoc.createTextNode('\n' + spacing), child); | |
| addIndentation(child, 1, spacing, xmlDoc); | |
| lastProcessedRootNode = child; | |
| } else if (child.nodeType === Node.PROCESSING_INSTRUCTION_NODE || child.nodeType === Node.COMMENT_NODE) { | |
| const nextSibling = child.nextSibling; | |
| if (nextSibling && nextSibling.nodeType === Node.ELEMENT_NODE) { | |
| if (!(nextSibling.previousSibling && nextSibling.previousSibling.nodeType === Node.TEXT_NODE && nextSibling.previousSibling.nodeValue?.includes('\n'))) { | |
| root.insertBefore(xmlDoc.createTextNode('\n'), nextSibling); | |
| } | |
| } | |
| lastProcessedRootNode = child; | |
| } else if (child.nodeType === Node.TEXT_NODE && child.nodeValue?.trim() !== '') { | |
| lastProcessedRootNode = child; | |
| } | |
| } | |
| // Add final newline before closing tag | |
| const actualLastChild = root.lastChild; | |
| if (lastProcessedRootNode && !(actualLastChild && actualLastChild.nodeType === Node.TEXT_NODE && actualLastChild.nodeValue?.endsWith('\n'))) { | |
| root.appendChild(xmlDoc.createTextNode('\n')); | |
| } | |
| return xmlDoc; | |
| } | |