RSS Git Download  Clone
Raw Blame History
var sanitizer = require('./sanitize.js')

module.exports = function (json, options) {
    if (json instanceof Buffer) {
        json = json.toString();
    }

    var obj = null;
    if (typeof (json) == 'string') {
        try {
            obj = JSON.parse(json);
        } catch (e) {
            throw new Error("The JSON structure is invalid");
        }
    } else {
        obj = json;
    }
    var toXml = new ToXml(options);
    toXml.parse(obj);
    return toXml.xml;
}

ToXml.prototype.parse = function (obj) {
    if (!obj) return;

    var self = this;
    var keys = Object.keys(obj);
    var len = keys.length;

    // First pass, extract strings only
    for (var i = 0; i < len; i++) {
        var key = keys[i], value = obj[key], isArray = Array.isArray(value);
        var type = typeof (value);
        if (type == 'string' || type == 'number' || type == 'boolean' || isArray) {
            var it = isArray ? value : [value];

            it.forEach(function (subVal) {
                if (typeof (subVal) != 'object') {
                    if (key == '$t') {
                        self.addTextContent(subVal);
                    } else {
                        self.addAttr(key, subVal);
                    }
                }
            });
        }
    }

    // Second path, now handle sub-objects and arrays
    for (var i = 0; i < len; i++) {
        var key = keys[i];

        if (Array.isArray(obj[key])) {
            var elems = obj[key];
            var l = elems.length;
            for (var j = 0; j < l; j++) {
                var elem = elems[j];

                if (typeof (elem) == 'object') {
                    self.openTag(key);
                    self.parse(elem);
                    self.closeTag(key);
                }
            }
        } else if (typeof (obj[key]) == 'object' && !(self.options.ignoreNull && obj[key] === null)) {
            self.openTag(key);
            self.parse(obj[key]);
            self.closeTag(key);
        }
    }

};

ToXml.prototype.openTag = function (key) {
    this.completeTag();
    this.xml += '<' + key;
    this.tagIncomplete = true;
}
ToXml.prototype.addAttr = function (key, val) {
    if (this.options.sanitize) {
        val = sanitizer.sanitize(val, false, true);
    }
    this.xml += ' ' + key + '="' + val + '"';
}
ToXml.prototype.addTextContent = function (text) {
    this.completeTag();
    var newText = (this.options.sanitize ? sanitizer.sanitize(text) : text);
    this.xml += newText;
}
ToXml.prototype.closeTag = function (key) {
    this.completeTag();
    this.xml += '</' + key + '>';
}
ToXml.prototype.completeTag = function () {
    if (this.tagIncomplete) {
        this.xml += '>';
        this.tagIncomplete = false;
    }
}

function ToXml(options) {
    var defaultOpts = {
        sanitize: false,
        ignoreNull: false
    };

    if (options) {
        for (var opt in options) {
            defaultOpts[opt] = options[opt];
        }
    }

    this.options = defaultOpts;
    this.xml = '';
    this.tagIncomplete = false;
}