Meki Cherkaoui | 97e7a59 | 2012-04-14 02:50:06 -0700 | [diff] [blame^] | 1 | var sax = require("./sax"); |
| 2 | var strict = true; |
| 3 | |
| 4 | /** |
| 5 | * Really simple XML DOM implementation based on sax that works with Strings. |
| 6 | * |
| 7 | * If you have an XML string and want a DOM this utility is convenient. |
| 8 | * |
| 9 | * var domjs = new DomJS(); |
| 10 | * domjs.parse(xmlString, function(err, dom) { |
| 11 | * |
| 12 | * }); |
| 13 | * |
| 14 | * If you want to compile C there are versions based on libxml2 |
| 15 | * and jsdom is full featured but complicated. |
| 16 | * |
| 17 | * This is "lightweight" meaning really simple and serves my purpose, it does not support namespaces or all |
| 18 | * of the features of XML 1.0 it just takes a string and returns a JavaScript object graph. |
| 19 | * |
| 20 | * There are only three types of object supported Element, Text and Comment. |
| 21 | * |
| 22 | * e.g. |
| 23 | * |
| 24 | * take <xml><elem att="val1"/><elem att="val1"/><elem att="val1"/></xml> |
| 25 | * |
| 26 | * return { name : "xml", |
| 27 | * attributes : {} |
| 28 | * children [ |
| 29 | * { name : "elem", attributes : {att:'val1'}, children [] }, |
| 30 | * { name : "elem", attributes : {att:'val1'}, children [] }, |
| 31 | * { name : "elem", attributes : {att:'val1'}, children [] } |
| 32 | * ] |
| 33 | * } |
| 34 | * |
| 35 | * The object returned can be serialized back out with obj.toXml(); |
| 36 | * |
| 37 | * |
| 38 | * @constructor DomJS |
| 39 | */ |
| 40 | DomJS = function() { |
| 41 | this.root = null; |
| 42 | this.stack = new Array(); |
| 43 | this.currElement = null; |
| 44 | this.error = false; |
| 45 | }; |
| 46 | |
| 47 | DomJS.prototype.parse = function(string, cb) { |
| 48 | if (typeof string != 'string') { |
| 49 | cb(true, 'Data is not a string'); |
| 50 | return; |
| 51 | } |
| 52 | var self = this; |
| 53 | parser = sax.parser(strict); |
| 54 | |
| 55 | parser.onerror = function (err) { |
| 56 | self.error = true; |
| 57 | cb(true, err); |
| 58 | }; |
| 59 | parser.ontext = function (text) { |
| 60 | if (self.currElement == null) { |
| 61 | // console.log("Content in the prolog " + text); |
| 62 | return; |
| 63 | } |
| 64 | var textNode = new Text(text); |
| 65 | self.currElement.children.push(textNode); |
| 66 | }; |
| 67 | parser.onopencdata = function () { |
| 68 | var cdataNode = new CDATASection(); |
| 69 | self.currElement.children.push(cdataNode); |
| 70 | }; |
| 71 | parser.oncdata = function (data) { |
| 72 | var cdataNode = self.currElement.children[self.currElement.children.length - 1]; |
| 73 | cdataNode.appendData(data); |
| 74 | }; |
| 75 | // do nothing on parser.onclosecdata |
| 76 | parser.onopentag = function (node) { |
| 77 | var elem = new Element(node.name, node.attributes); |
| 78 | if (self.root == null) { |
| 79 | self.root = elem; |
| 80 | } |
| 81 | if (self.currElement != null) { |
| 82 | self.currElement.children.push(elem); |
| 83 | } |
| 84 | self.currElement = elem; |
| 85 | self.stack.push(self.currElement); |
| 86 | }; |
| 87 | parser.onclosetag = function (node) { |
| 88 | self.stack.pop(); |
| 89 | self.currElement = self.stack[self.stack.length - 1 ];// self.stack.peek(); |
| 90 | }; |
| 91 | parser.oncomment = function (comment) { |
| 92 | if (self.currElement == null) { |
| 93 | //console.log("Comments in the prolog discarded " + comment); |
| 94 | return; |
| 95 | } |
| 96 | var commentNode = new Comment(comment); |
| 97 | self.currElement.children.push(commentNode); |
| 98 | }; |
| 99 | |
| 100 | parser.onend = function () { |
| 101 | if ( self.error == false) { |
| 102 | cb(false, self.root); |
| 103 | } |
| 104 | }; |
| 105 | |
| 106 | parser.write(string).close(); |
| 107 | }; |
| 108 | |
| 109 | DomJS.prototype.reset = function() { |
| 110 | this.root = null; |
| 111 | this.stack = new Array(); |
| 112 | this.currElement = null; |
| 113 | this.error = false; |
| 114 | }; |
| 115 | |
| 116 | var escape = function(string) { |
| 117 | return string.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); |
| 118 | }; |
| 119 | |
| 120 | |
| 121 | Element = function(name, attributes, children ) { |
| 122 | this.name = name; |
| 123 | this.attributes = attributes || []; |
| 124 | this.children = children || []; |
| 125 | } |
| 126 | Element.prototype.toXml = function(sb) { |
| 127 | if (typeof sb == 'undefined') { |
| 128 | sb = {buf:''}; // Strings are pass by value in JS it seems |
| 129 | } |
| 130 | sb.buf += '<' + this.name; |
| 131 | for (att in this.attributes) { |
| 132 | |
| 133 | sb.buf += ' ' + att + '="' + escape(this.attributes[att]) + '"'; |
| 134 | } |
| 135 | if (this.children.length != 0) { |
| 136 | sb.buf += '>'; |
| 137 | for (var i = 0 ; i < this.children.length ; i++) { |
| 138 | this.children[i].toXml(sb); |
| 139 | } |
| 140 | sb.buf += '</' + this.name + '>'; |
| 141 | } |
| 142 | else { |
| 143 | sb.buf += '/>'; |
| 144 | } |
| 145 | return sb.buf; |
| 146 | }; |
| 147 | Element.prototype.firstChild = function() { |
| 148 | if ( this.children.length > 0) { |
| 149 | return this.children[0]; |
| 150 | } |
| 151 | return null; |
| 152 | }; |
| 153 | Element.prototype.text = function() { |
| 154 | if ( this.children.length > 0) { |
| 155 | if (typeof this.children[0].text == 'string') { |
| 156 | return this.children[0].text; |
| 157 | }; |
| 158 | } |
| 159 | return null; |
| 160 | }; |
| 161 | |
| 162 | Text = function(data){ |
| 163 | this.text = data; |
| 164 | }; |
| 165 | Text.prototype.toXml = function(sb) { |
| 166 | sb.buf += escape(this.text); |
| 167 | }; |
| 168 | |
| 169 | Comment = function(comment) { |
| 170 | this.comment = comment; |
| 171 | }; |
| 172 | Comment.prototype.toXml = function(sb) { |
| 173 | sb.buf += '<!--' + this.comment + '-->'; |
| 174 | }; |
| 175 | |
| 176 | CDATASection = function(data){ |
| 177 | this.text = data || ''; |
| 178 | }; |
| 179 | CDATASection.prototype.toXml = function(sb) { |
| 180 | sb.buf += '<![CDATA[' + this.text + ']]>'; |
| 181 | }; |
| 182 | CDATASection.prototype.appendData = function(data) { |
| 183 | this.text += data; |
| 184 | }; |
| 185 | |
| 186 | |
| 187 | |
| 188 | |