xml-writer.js 2.36 KB
'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 = `</${name}>`;

        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}</${name}>`;
        } 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;