6-mils
Version:
A JS library for sending, receiving, and parsing cXML messages.
106 lines (88 loc) • 3.27 kB
JavaScript
/**
* Pretty-prints a cXML document with indentation. Only intended for creating
* human-readable representations.
*
* @param {String} src The cXML to format (no whitespace is expected
* between tags).
*
* @return {String}
*/
function humanizeXml (src) {
/**
* Validate input.
*/
src = src || ''
if (src.length === 0) { return '' } // nothing to do!
/**
* As the source XML is parsed for formatting, it will be added to this array
* line by line.
* @type {Array}
*/
const output = []
/**
* The characters to use for indentation.
* @type {String}
*/
const indent = ' '
/**
* A list of elements being traversed by the parser. This enables the parser
* to keep track of where it is in the document tree.
* @type {Array}
*/
const currentTagHierarchy = ['cXML']
/**
* The input to this function, separated by tag (either opening or closing).
* @type {Array}
*/
const lines = src.split('>')
/**
* The last line to parse. This is purposefully set as the second-to-last
* because it is easier (processing-wise) to simply add a trailing "</cXML>"
* outside the loop, since that will always be a constant feature.
* @type {Number}
*/
const lastIndex = lines.length - 2
for (let i = 0; i < lastIndex; i++) {
const currentLine = lines[i]
// the first three lines are all left-aligned (xml, doctype, cXML)
if (i < 3) {
output.push(`${currentLine}>`)
continue
}
const previousLine = output[output.length - 1]
const openingTagMatches = /^<(\w+)/.exec(currentLine)
const closingTagMatches = /^<\/(\w+)/.exec(currentLine)
if (openingTagMatches) {
// start each new element on a separate line, indented appropriately
output.push(`${indent.repeat(currentTagHierarchy.length)}${currentLine}>`)
// if this isn't a self-closing tag, then assume there may be children
// elements
if (!currentLine.endsWith('/')) {
currentTagHierarchy.push(openingTagMatches[1])
}
} else if (closingTagMatches) {
const previousOpeningTagMatches = /<(\w+)/.exec(previousLine)
if (previousOpeningTagMatches && previousOpeningTagMatches[1] === closingTagMatches[1]) {
// since the closing tag is the same as the opening one on the previous
// line, keep them together
output[output.length - 1] = `${previousLine}${currentLine}>`
currentTagHierarchy.pop()
continue
}
// otherwise, place the closing tag on a new line, but update the
// hierarchy first to align the indentation with the matching opening tag
currentTagHierarchy.pop()
output.push(`${indent.repeat(currentTagHierarchy.length)}${currentLine}>`)
} else {
// this line does not start with either an opening tag nor a closing one,
// therefore it should be merged with the previous line
output[output.length - 1] = `${previousLine}${currentLine}>`
// owing to the way the lines were split originally, we know this line
// must end in a closing tag, so update the hierarchy
currentTagHierarchy.pop()
}
}
output.push('</cXML>')
return output.join('\n')
}
module.exports = humanizeXml