/** * github https://github.com/nfarina/xmldoc/blob/master/README.md */ function createXmldoc() { var xmldoc = {}; (function(xmldoc) { var sax; if (typeof module !== "undefined" && module.exports && !global.xmldocAssumeBrowser) { // 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, parser) { // If you didn't hand us a parser (common case) see if we can grab one // from the current execution stack. if (!parser) { var delegate = delegates[delegates.length - 1]; if (delegate.parser) { parser = delegate.parser; } } this.name = tag.name; this.attr = tag.attributes; this.val = ""; this.children = []; this.firstChild = null; this.lastChild = null; // Assign parse information this.line = parser ? parser.line : null; this.column = parser ? parser.column : null; this.position = parser ? parser.position : null; this.startTagPosition = parser ? parser.startTagPosition : null; } // Private methods XmlElement.prototype._addChild = function(child) { // add to our children array this.children.push(child); // update first/last pointers if (!this.firstChild) this.firstChild = child; this.lastChild = child; }; // SaxParser handlers XmlElement.prototype._opentag = function(tag) { var child = new XmlElement(tag); this._addChild(child); delegates.unshift(child); }; XmlElement.prototype._closetag = function() { delegates.shift(); }; XmlElement.prototype._text = function(text) { if (typeof this.children === "undefined") return; this.val += text; this._addChild(new XmlTextNode(text)); }; XmlElement.prototype._cdata = function(cdata) { this.val += cdata; this._addChild(new XmlCDataNode(cdata)); }; XmlElement.prototype._comment = function(comment) { if (typeof this.children === "undefined") return; this._addChild(new XmlCommentNode(comment)); }; XmlElement.prototype._error = function(err) { throw err; }; // Useful functions XmlElement.prototype.eachChild = function(iterator, context) { for (var i = 0, l = this.children.length; i < l; i++) if (this.children[i].type === "element") if (iterator.call(context, this.children[i], i, this.children) === false) return; }; XmlElement.prototype.childNamed = function(name) { for (var i = 0, l = this.children.length; i < l; i++) { var child = this.children[i]; if (child.name === name) return child; } return undefined; }; XmlElement.prototype.childrenNamed = function(name) { var matches = []; for (var i = 0, l = this.children.length; i < l; i++) if (this.children[i].name === name) matches.push(this.children[i]); return matches; }; XmlElement.prototype.childWithAttribute = function(name, value) { for (var i = 0, l = this.children.length; i < l; i++) { var child = this.children[i]; if (child.type === "element" && (value && child.attr[name] === value || !value && child.attr[name])) return child; } return undefined; }; XmlElement.prototype.descendantsNamed = function(name) { var matches = []; for (var i = 0, l = this.children.length; i < l; i++) { var child = this.children[i]; if (child.type === "element") { if (child.name === name) matches.push(child); matches = matches.concat(child.descendantsNamed(name)); } } return matches; }; XmlElement.prototype.descendantWithPath = function(path) { var descendant = this; var components = path.split("."); for (var i = 0, l = components.length; i < l; i++) if (descendant && descendant.type === "element") descendant = descendant.childNamed(components[i]); else return undefined; return descendant; }; XmlElement.prototype.valueWithPath = function(path) { var components = path.split("@"); var descendant = this.descendantWithPath(components[0]); if (descendant) return components.length > 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]) + '"'; if (this.children.length === 1 && this.children[0].type !== "element") { s += ">" + this.children[0].toString(options) + ""; } else if (this.children.length) { s += ">" + linebreak; var childIndent = indent + (options && options.compressed ? "" : " "); for (var i = 0, l = this.children.length; i < l; i++) { s += this.children[i].toStringWithIndent(childIndent, options) + linebreak; } s += indent + ""; } else if (options && options.html) { var whiteList = [ "area", "base", "br", "col", "embed", "frame", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr" ]; if (whiteList.indexOf(this.name) !== -1) s += "/>"; else s += ">"; } else { s += "/>"; } return s; }; // Alternative XML nodes function XmlTextNode(text) { this.text = text; } XmlTextNode.prototype.toString = function(options) { return formatText(escapeXML(this.text), options); }; XmlTextNode.prototype.toStringWithIndent = function(indent, options) { return indent + this.toString(options); }; function XmlCDataNode(cdata) { this.cdata = cdata; } XmlCDataNode.prototype.toString = function(options) { return ""; }; XmlCDataNode.prototype.toStringWithIndent = function(indent, options) { return indent + this.toString(options); }; function XmlCommentNode(comment) { this.comment = comment; } XmlCommentNode.prototype.toString = function(options) { return "\x3c!--" + formatText(escapeXML(this.comment), options) + "--\x3e"; }; XmlCommentNode.prototype.toStringWithIndent = function(indent, options) { return indent + this.toString(options); }; // Node type tag XmlElement.prototype.type = "element"; XmlTextNode.prototype.type = "text"; XmlCDataNode.prototype.type = "cdata"; XmlCommentNode.prototype.type = "comment"; /** * 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!"); // Stores doctype (if defined) this.doctype = ""; // 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 ]; try { this.parser.write(xml); } finally { // 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); // all other tags will be the root element's children else XmlElement.prototype._opentag.apply(this, arguments); }; XmlDocument.prototype._doctype = function(doctype) { this.doctype += doctype; }; // 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.oncomment = parser_comment; parser.ondoctype = parser_doctype; parser.onerror = parser_error; } // create these closures and cache them by keeping them file-scoped function parser_opentag() { delegates[0] && delegates[0]._opentag.apply(delegates[0], arguments); } function parser_closetag() { delegates[0] && delegates[0]._closetag.apply(delegates[0], arguments); } function parser_text() { delegates[0] && delegates[0]._text.apply(delegates[0], arguments); } function parser_cdata() { delegates[0] && delegates[0]._cdata.apply(delegates[0], arguments); } function parser_comment() { delegates[0] && delegates[0]._comment.apply(delegates[0], arguments); } function parser_doctype() { delegates[0] && delegates[0]._doctype.apply(delegates[0], arguments); } function parser_error() { delegates[0] && 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.toString().replace(/&/g, "&").replace(//g, ">").replace(/'/g, "'").replace(/"/g, """); } // formats some text for debugging given a few options function formatText(text, options) { var finalText = text; if (options && options.trimmed && text.length > 25) { finalText = finalText.substring(0, 25).trim() + "…"; } if (!(options && options.preserveWhitespace)) { finalText = finalText.trim(); } return finalText; } // // Are we being used in a Node-like environment? // if (typeof define === "function" && define.amd) { // define(function() { // return { // XmlDocument: XmlDocument, // XmlElement: XmlElement, // XmlTextNode: XmlTextNode, // XmlCDataNode: XmlCDataNode, // XmlCommentNode: XmlCommentNode // }; // }); // } else if (typeof module === "object" && module.exports) { // module.exports.XmlDocument = XmlDocument; // module.exports.XmlElement = XmlElement; // module.exports.XmlTextNode = XmlTextNode; // module.exports.XmlCDataNode = XmlCDataNode; // module.exports.XmlCommentNode = XmlCommentNode; // } else { // xmldoc.XmlDocument = XmlDocument; // xmldoc.XmlElement = XmlElement; // xmldoc.XmlTextNode = XmlTextNode; // xmldoc.XmlCDataNode = XmlCDataNode; // xmldoc.XmlCommentNode = XmlCommentNode; // } xmldoc.XmlDocument = XmlDocument; xmldoc.XmlElement = XmlElement; xmldoc.XmlTextNode = XmlTextNode; xmldoc.XmlCDataNode = XmlCDataNode; xmldoc.XmlCommentNode = XmlCommentNode; })(xmldoc = {}); return xmldoc; }