123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- /**
- * 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) + "</" + this.name + ">";
- } 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 + "</" + this.name + ">";
- } 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 += "></" + this.name + ">";
- } 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 "<![CDATA[" + formatText(this.cdata, options) + "]]>";
- };
- 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, "'").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;
- }
|