Current File : /home/tradevaly/www/node_modules/xmldoc/lib/xmldoc.js |
(function () {
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) {
// 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.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;
}
// 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.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 "<!--"+formatText(escapeXML(this.comment), options)+"-->";
};
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];
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);
};
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 module !== 'undefined' && module.exports && !global.xmldocAssumeBrowser)
module.exports.XmlDocument = XmlDocument;
else
this.XmlDocument = XmlDocument;
})();