UNPKG

xmljs

Version:

A small and simple package which can traverse a XML document

209 lines (183 loc) 6.58 kB
module.exports = (function() { "use strict"; var $o = require("./core.js"); var sax = require("sax"); var parser = $o.Object.extend({ saxParser: null, currentNode: null, attributes: null, // storage for attributes for the next node nodeStack: null, // stack of nodes // oPar.strict: Whenever or not to use a strict parser // oPar.noTrim : Do not trimtext and comment nodes // oPar.noNormalizeWhitespaces: Do not normalize whitespaces in text // oPar.lowercaseTagnames: Turn the tagsNames to lowercase // oPar.noTracing: Disable position tracing of sax // oPar.strictEntities: Allow only predefined entities create: function(oPar) { this.nodeStack = []; this.saxParser = sax.parser(!!oPar.strict, { trim: !oPar.noTrim, normalize: !oPar.noNormalizeWhitespaces, lowercase: !!oPar.lowercaseTagnames, xmlns: !oPar.noNamespaces, position: !oPar.noTracing, strictEntities: !!oPar.strictEntities }); this.errors = []; this.attributes = []; this.continueOnError = !!oPar.continueOnError; this._createEvents(); }, _createEvents: function() { var self = this; this.saxParser.onopentag = function(n) { var node = new XmlNode(n); self.attributes.forEach(function(attr) { node.addAttribute(attr); }); self.currentNode.addNode(node); self.attributes.length = 0; self.pushStack(node); }; this.saxParser.onclosetag = function() { self.popStack(); }; this.saxParser.onattribute = function(attr) { if (attr.prefix === "xmlns") return; // ignore namespace attributes self.attributes.push(attr); }; this.saxParser.ontext = function(txt) { self.currentNode.setText(txt); }; this.saxParser.onerror = function(err) { self.errors.push(err); if (self.continueOnError) self.saxParser.resume(); }; }, parseString: function(str, cb) { this.nodeStack.length = 0; this.errors.length = 0; this.root = new Node(); this.currentNode = this.root; try { this.saxParser.write(str.toString()); this.saxParser.close(); } catch (e) { this.errors.push(e); } var err = this.errors.length === 0 ? null : this.errors; cb(err, this.root); return this.errors.length === 0; }, pushStack: function(node) { this.nodeStack.push(this.currentNode); this.currentNode = node; return node; }, popStack: function() { this.currentNode = this.nodeStack.pop(); return this.currentNode; } }); parser.isXmlNode = function(n) { return n instanceof XmlNode; }; var Attribute = $o.Object.extend({ name: null, text: null, create: function(name, value) { this.name = name; this.text = value; } }); var XmlAttribute = Attribute.extend({ ns: null, create: function(xml) { _super(XmlAttribute).create.call(this, xml.local, xml.value); this.ns = xml.namespace; } }); // generic node var Node = $o.Object.extend({ children: null, // dictionary of children nodes children_lower: null, attributes: null, // dictionary of attributes attributes_lower: null, // dictionary of attributes create: function() { this.children = {}; this.children_lower = {}; this.attributes = {}; this.attributes_lower = {}; }, getAttribute: function(attr, ignoreCase) { return ignoreCase ? this.attributes_lower[attr.toLowerCase()] : this.attributes[attr]; }, addAttribute: function(xmlAttr) { var attr = new XmlAttribute(xmlAttr); this.attributes[attr.name] = attr; this.attributes_lower[attr.name.toLowerCase()] = attr; }, addNode: function(n) { if (!this.children[n.localName]) this.children[n.localName] = []; if (!this.children_lower[n.localName.toLowerCase()]) this.children_lower[n.localName.toLowerCase()] = []; this.children[n.localName].push(n); this.children_lower[n.localName.toLowerCase()].push(n); return n; }, path: function(arr, ignoreCase) { var nodes = []; function next(parNode, left) { if (left.length === 0) { nodes.push(parNode); return; } var name = left[0]; var arr = parNode.children[name] || []; arr.forEach(function(n) { next(n, left.slice(1)); }); } function next_caseinvar(parNode, left) { if (left.length === 0) { nodes.push(parNode); return; } var name = left[0]; var arr = parNode.children_lower[name.toLowerCase()] || []; arr.forEach(function(n) { next_caseinvar(n, left.slice(1)); }); } if (ignoreCase) next_caseinvar(this, arr); else next(this, arr); return nodes; }, visit: function(fn) { fn(this); for (var k in this.children) this.children[k].visit(fn); } }); var XmlNode = Node.extend({ name: "", ns: "", prefix: "", localName: "", text: "", _n: null, create: function(n) { _super(XmlNode).create.call(this); this._n = n; this.name = n.name; this.prefix = n.prefix; this.localName = n.local; this.ns = n.ns[this.prefix]; }, setText: function(txt) { this.text = txt; } }); return parser; })();