rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
62 lines (52 loc) • 1.81 kB
JavaScript
/**
* Formats/prettifies an XML string with indentation.
*
* @param {string} xml - The XML string to format.
* @param {object} [options]
* @param {string} [options.indentor=' '] - The indentation string (e.g. ' ' or '\t').
* @param {boolean} [options.textNodesOnSameLine=true] - Keep simple text nodes on the same line as their opening and closing tags.
* @returns {string} Formatted XML string.
*/
export function formatXml(xml, { indentor = " ", textNodesOnSameLine = true } = {}) {
if (!xml || typeof xml !== "string") {
return "";
}
// Tokenize by XML tags (including declarations, comments, and CDATA) while preserving text
const tokens = xml
.split(/(<[^>]+>)/g)
.map((s) => s.trim())
.filter(Boolean);
let depth = 0;
const lines = [];
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
const isClosing = token.startsWith("</");
const isSelfClosing = token.endsWith("/>");
const isOpening = /^<[^/!?]/.test(token) && !isSelfClosing;
if (isClosing) {
depth = Math.max(0, depth - 1);
}
const indent = indentor.repeat(depth);
// Lookahead for OpeningTag -> Text -> ClosingTag to collapse into one line
if (
textNodesOnSameLine &&
isOpening &&
i + 2 < tokens.length &&
!tokens[i + 1].startsWith("<") &&
tokens[i + 2].startsWith("</")
) {
const openTag = token.match(/^<([a-zA-Z0-9_\-:]+)/)?.[1];
const closeTag = tokens[i + 2].match(/^<\/([a-zA-Z0-9_\-:]+)/)?.[1];
if (openTag && closeTag && openTag === closeTag) {
lines.push(`${indent}${token}${tokens[i + 1]}${tokens[i + 2]}`);
i += 2;
continue;
}
}
lines.push(`${indent}${token}`);
if (isOpening) {
depth++;
}
}
return lines.join("\n");
}