/** * @module */ 'use strict' function stripXmlWhitespace(xml) { return xml.replace(/>\s+/g, '>').replace(/<\s+/g, '<').trim() } function escapeXml(s) { if (typeof s === 'number') { return s } else if (s === undefined || typeof s !== 'string') { return undefined } else { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') } } /** * Representation of an XML element */ class XmlElement { /** * Xml Element Constructor * * @param {object} attrs Refer to individual attrs * @param {string} attrs.name * Name of the XML tag * @param {Array.} [attrs.content=[]] * Array of objects to render inside the tag. content may contain a mix of * string and XmlElement objects. If content is `[]` or ommitted the * element will be rendered as a self-closing element. * @param {object} [attrs.attrs={}] * Object representing the tag's attributes as name/value pairs */ constructor({ name, content = [], attrs = {} }) { this.name = name this.content = content this.attrs = attrs } /** * Render the XML element to a string, applying appropriate escaping * * @returns {string} String representation of the XML element */ render() { const attrsStr = Object.entries(this.attrs) .map(([k, v]) => ` ${k}="${escapeXml(v)}"`) .join('') if (this.content.length > 0) { const content = this.content .map(function (el) { if (el instanceof XmlElement) { return el.render() } else { return escapeXml(el) } }) .join(' ') return stripXmlWhitespace( `<${this.name}${attrsStr}>${content}` ) } return stripXmlWhitespace(`<${this.name}${attrsStr}/>`) } } module.exports = { escapeXml, stripXmlWhitespace, XmlElement }