RSS Git Download  Clone
Raw Blame History
var expat = require('node-expat');
var fs = require('fs');

// This object will hold the final result.
var obj = {}; 
var currentObject = {};
var ancestors = [];
var currentElementName = null;

var options = {}; //configuration options
function startElement(name, attrs) {
    currentElementName = name;
    if(options.coerce) {
        // Looping here in stead of making coerce generic as object walk is unnecessary
        for (var key in attrs) {
            if (attrs.hasOwnProperty(key)) {
                attrs[key] = coerce(attrs[key]);
            }
        }
    }
    if (! (name in currentObject)) {
        currentObject[name] = attrs;
    } else if (! (currentObject[name] instanceof Array)) {
        // Put the existing object in an array.
        var newArray = [currentObject[name]];
        // Add the new object to the array.
        newArray.push(attrs);
        // Point to the new array.
        currentObject[name] = newArray;
    } else {
        // An array already exists, push the attributes on to it.
        currentObject[name].push(attrs);
    }

    // Store the current (old) parent.
    ancestors.push(currentObject);

    // We are now working with this object, so it becomes the current parent.
    if (currentObject[name] instanceof Array) {
        // If it is an array, get the last element of the array.
        currentObject = currentObject[name][currentObject[name].length - 1];
    } else {
        // Otherwise, use the object itself.
        currentObject = currentObject[name];
    }
}

function text(data) {
    if (!options.space) {
        data = data.trim();
        if (!data.length) {
            return;
        }
    }
    currentObject['$t'] = coerce((currentObject['$t'] || "") + data);
}

function endElement(name) {
    if (options.space && currentElementName !== name) {
        delete currentObject['$t'];
    }
    // This should check to make sure that the name we're ending 
    // matches the name we started on.
    var ancestor = ancestors.pop();
    if (!options.reversible) {
        if ((Object.keys(currentObject).length == 1) && ('$t' in currentObject)) {
            if (ancestor[name] instanceof Array) {
                ancestor[name].push(ancestor[name].pop()['$t']);
            } else {
                ancestor[name] = currentObject['$t'];
            }
        }
    }

    currentObject = ancestor;
}

function coerce(val) {
    if (!options.coerce) {
        return val;
    }
    var num = Number(val);
    if (!isNaN(num)) {
        return num;
    }
    switch (val.toLowerCase()){
        case 'true':
        case 'yes':
            return true;
        case 'false':
        case 'no':
            return false;
        default: return val;
    }
}

module.exports = function(xml, _options) {
    var parser = new expat.Parser('UTF-8');

    parser.on('startElement', startElement);
    parser.on('text', text);
    parser.on('endElement', endElement);

    obj = currentObject = {};
    ancestors = [];
    currentElementName = null;

    options = {
        object: false,
        reversible: false,
        space: false, // keep space or not
    };

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

    if (!parser.parse(xml)) {
        throw new Error('There are errors in your xml file: ' + parser.getError());
    }

    if (options.object) {
        return obj;
    }

    return JSON.stringify(obj);
};