(xml: string, indent: string = " ")
| 13 | * @returns Formatted XML string |
| 14 | */ |
| 15 | export function formatXML(xml: string, indent: string = " "): string { |
| 16 | let formatted = "" |
| 17 | let pad = 0 |
| 18 | |
| 19 | // Remove existing whitespace between tags |
| 20 | xml = xml.replace(/>\s*</g, "><").trim() |
| 21 | |
| 22 | // Split on tags |
| 23 | const tags = xml.split(/(?=<)|(?<=>)/g).filter(Boolean) |
| 24 | |
| 25 | tags.forEach((node) => { |
| 26 | if (node.match(/^<\/\w/)) { |
| 27 | // Closing tag - decrease indent |
| 28 | pad = Math.max(0, pad - 1) |
| 29 | formatted += indent.repeat(pad) + node + "\n" |
| 30 | } else if (node.match(/^<\w[^>]*[^/]>.*$/)) { |
| 31 | // Opening tag |
| 32 | formatted += indent.repeat(pad) + node |
| 33 | // Only add newline if next item is a tag |
| 34 | const nextIndex = tags.indexOf(node) + 1 |
| 35 | if (nextIndex < tags.length && tags[nextIndex].startsWith("<")) { |
| 36 | formatted += "\n" |
| 37 | if (!node.match(/^<\w[^>]*\/>$/)) { |
| 38 | pad++ |
| 39 | } |
| 40 | } |
| 41 | } else if (node.match(/^<\w[^>]*\/>$/)) { |
| 42 | // Self-closing tag |
| 43 | formatted += indent.repeat(pad) + node + "\n" |
| 44 | } else if (node.startsWith("<")) { |
| 45 | // Other tags (like <?xml) |
| 46 | formatted += indent.repeat(pad) + node + "\n" |
| 47 | } else { |
| 48 | // Text content |
| 49 | formatted += node |
| 50 | } |
| 51 | }) |
| 52 | |
| 53 | return formatted.trim() |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Efficiently converts a potentially incomplete XML string to a legal XML string by closing any open tags properly. |
no outgoing calls
no test coverage detected