(function () { var sax; if (typeof module !== 'undefined' && module.exports) { // We're being used in a Node-like environment sax = require('sax'); } else { // assume it's attached to the Window object in a browser sax = this.sax; if (!sax) // no sax for you! throw new Error("Expected sax to be defined. Make sure you're including sax.js before this file."); } /* XmlElement is our basic building block. Everything is an XmlElement; even XmlDocument behaves like an XmlElement by inheriting its attributes and functions. */ function XmlElement(tag) { // Capture the parser object off of the XmlDocument delegate var parser = delegates[delegates.length - 1].parser; this.name = tag.name; this.attr = tag.attributes || {}; this.val = ""; this.isValCdata = false; this.children = []; this.firstChild = null; this.lastChild = null; // Assign parse information this.line = parser.line; this.column = parser.column; this.position = parser.position; this.startTagPosition = parser.startTagPosition; } // SaxParser handlers XmlElement.prototype._opentag = function(tag) { var child = new XmlElement(tag); // add to our children array this.children.push(child); // update first/last pointers if (!this.firstChild) this.firstChild = child; this.lastChild = child; delegates.unshift(child); }; XmlElement.prototype._closetag = function() { delegates.shift(); }; XmlElement.prototype._text = function(text) { if (text) this.val += text; }; XmlElement.prototype._cdata = function(cdata) { if (cdata) { this.val += cdata; this.isValCdata=true; } }; XmlElement.prototype._error = function(err) { throw err; }; // Useful functions XmlElement.prototype.eachChild = function(iterator, context) { for (var i=0, l=this.children.length; i 1 ? descendant.attr[components[1]] : descendant.val; else return undefined; }; // String formatting (for debugging) XmlElement.prototype.toString = function(options) { return this.toStringWithIndent("", options); }; XmlElement.prototype.toStringWithIndent = function(indent, options) { var s = indent + "<" + this.name; var linebreak = options && options.compressed ? "" : "\n"; var preserveWhitespace = options && options.preserveWhitespace; for (var name in this.attr) if (Object.prototype.hasOwnProperty.call(this.attr, name)) s += " " + name + '="' + escapeXML(this.attr[name]) + '"'; var finalVal = ''; if (this.isValCdata){ finalVal = ''; } else if (preserveWhitespace) { finalVal = escapeXML(this.val); } else{ finalVal = escapeXML(this.val.trim()); } if (options && options.trimmed && finalVal.length > 25) finalVal = finalVal.substring(0,25).trim() + "…"; if (this.children.length) { s += ">" + linebreak; var childIndent = indent + (options && options.compressed ? "" : " "); if (finalVal.length) s += childIndent + finalVal + linebreak; for (var i=0, l=this.children.length; i"; } else if (finalVal.length) { s += ">" + finalVal + ""; } else s += "/>"; return s; }; /* XmlDocument is the class we expose to the user; it uses the sax parser to create a hierarchy of XmlElements. */ function XmlDocument(xml) { xml && (xml = xml.toString().trim()); if (!xml) throw new Error("No XML to parse!"); // Expose the parser to the other delegates while the parser is running this.parser = sax.parser(true); // strict addParserEvents(this.parser); // We'll use the file-scoped "delegates" var to remember what elements we're currently // parsing; they will push and pop off the stack as we get deeper into the XML hierarchy. // It's safe to use a global because JS is single-threaded. delegates = [this]; this.parser.write(xml); // Remove the parser as it is no longer needed and should not be exposed to clients delete this.parser; } // make XmlDocument inherit XmlElement's methods extend(XmlDocument.prototype, XmlElement.prototype); XmlDocument.prototype._opentag = function(tag) { if (typeof this.children === 'undefined') // the first tag we encounter should be the root - we'll "become" the root XmlElement XmlElement.call(this,tag); else // all other tags will be the root element's children XmlElement.prototype._opentag.apply(this,arguments); }; // file-scoped global stack of delegates var delegates = null; /* Helper functions */ function addParserEvents(parser) { parser.onopentag = parser_opentag; parser.onclosetag = parser_closetag; parser.ontext = parser_text; parser.oncdata = parser_cdata; parser.onerror = parser_error; } // create these closures and cache them by keeping them file-scoped function parser_opentag() { delegates[0]._opentag.apply(delegates[0],arguments) } function parser_closetag() { delegates[0]._closetag.apply(delegates[0],arguments) } function parser_text() { delegates[0]._text.apply(delegates[0],arguments) } function parser_cdata() { delegates[0]._cdata.apply(delegates[0],arguments) } function parser_error() { delegates[0]._error.apply(delegates[0],arguments) } // a relatively standard extend method function extend(destination, source) { for (var prop in source) if (source.hasOwnProperty(prop)) destination[prop] = source[prop]; } // escapes XML entities like "<", "&", etc. function escapeXML(value){ return value.replace(//g, ">").replace(/&/g, '&').replace(/'/g, ''').replace(/"/g, '"'); } // Are we being used in a Node-like environment? if (typeof module !== 'undefined' && module.exports) module.exports.XmlDocument = XmlDocument; else this.XmlDocument = XmlDocument; })();