'use strict'; /* Copyright 2012-2015, Yahoo Inc. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ const INDENT = ' '; function attrString(attrs) { return Object.entries(attrs || {}) .map(([k, v]) => ` ${k}="${v}"`) .join(''); } /** * a utility class to produce well-formed, indented XML * @param {ContentWriter} contentWriter the content writer that this utility wraps * @constructor */ class XMLWriter { constructor(contentWriter) { this.cw = contentWriter; this.stack = []; } indent(str) { return this.stack.map(() => INDENT).join('') + str; } /** * writes the opening XML tag with the supplied attributes * @param {String} name tag name * @param {Object} [attrs=null] attrs attributes for the tag */ openTag(name, attrs) { const str = this.indent(`<${name + attrString(attrs)}>`); this.cw.println(str); this.stack.push(name); } /** * closes an open XML tag. * @param {String} name - tag name to close. This must match the writer's * notion of the tag that is currently open. */ closeTag(name) { if (this.stack.length === 0) { throw new Error(`Attempt to close tag ${name} when not opened`); } const stashed = this.stack.pop(); const str = ``; if (stashed !== name) { throw new Error( `Attempt to close tag ${name} when ${stashed} was the one open` ); } this.cw.println(this.indent(str)); } /** * writes a tag and its value opening and closing it at the same time * @param {String} name tag name * @param {Object} [attrs=null] attrs tag attributes * @param {String} [content=null] content optional tag content */ inlineTag(name, attrs, content) { let str = '<' + name + attrString(attrs); if (content) { str += `>${content}`; } else { str += '/>'; } str = this.indent(str); this.cw.println(str); } /** * closes all open tags and ends the document */ closeAll() { this.stack .slice() .reverse() .forEach(name => { this.closeTag(name); }); } } module.exports = XMLWriter;