blob: 3cabaec10c12c685686b271c3606cad589dcbe3a [file] [log] [blame]
Meki Cherkaoui97e7a592012-04-14 02:50:06 -07001var sax = require("./sax");
2var 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 */
40DomJS = function() {
41 this.root = null;
42 this.stack = new Array();
43 this.currElement = null;
44 this.error = false;
45};
46
47DomJS.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
109DomJS.prototype.reset = function() {
110 this.root = null;
111 this.stack = new Array();
112 this.currElement = null;
113 this.error = false;
114};
115
116var escape = function(string) {
117 return string.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
118};
119
120
121Element = function(name, attributes, children ) {
122 this.name = name;
123 this.attributes = attributes || [];
124 this.children = children || [];
125}
126Element.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};
147Element.prototype.firstChild = function() {
148 if ( this.children.length > 0) {
149 return this.children[0];
150 }
151 return null;
152};
153Element.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
162Text = function(data){
163 this.text = data;
164};
165Text.prototype.toXml = function(sb) {
166 sb.buf += escape(this.text);
167};
168
169Comment = function(comment) {
170 this.comment = comment;
171};
172Comment.prototype.toXml = function(sb) {
173 sb.buf += '<!--' + this.comment + '-->';
174};
175
176CDATASection = function(data){
177 this.text = data || '';
178};
179CDATASection.prototype.toXml = function(sb) {
180 sb.buf += '<![CDATA[' + this.text + ']]>';
181};
182CDATASection.prototype.appendData = function(data) {
183 this.text += data;
184};
185
186
187
188