forked from nfarina/xmldoc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxmldoc.js
235 lines (180 loc) · 6.4 KB
/
xmldoc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
(function () {
var sax;
if (typeof module !== 'undefined' && module.exports) {
// 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;
}
// SaxParser handlers
XmlElement.prototype._opentag = function(tag) {
var child = new XmlElement(tag);
// add to our children array
this.children.push(child);
// update first/last pointers
if (!this.firstChild) this.firstChild = child;
this.lastChild = child;
delegates.unshift(child);
};
XmlElement.prototype._closetag = function() {
delegates.shift();
};
XmlElement.prototype._text = function(text) {
if (text) this.val += text;
};
XmlElement.prototype._cdata = function(cdata) {
if (cdata) this.val += cdata;
};
// Useful functions
XmlElement.prototype.eachChild = function(iterator, context) {
for (var i=0, l=this.children.length; i<l; i++)
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 ( (value && child.attr[name] === value) || (!value && child.attr[name]) )
return child;
}
return null;
};
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 = 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 null;
};
// 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";
for (var name in this.attr)
if (Object.prototype.hasOwnProperty.call(this.attr, name))
s += " " + name + '="' + this.attr[name] + '"';
var finalVal = this.val.trim().replace(/</g, "<").replace(/>/g, ">").replace(/&/g, '&');
if (options && options.trimmed && finalVal.length > 25)
finalVal = finalVal.substring(0,25).trim() + "…";
if (this.children.length) {
s += ">" + linebreak;
var childIndent = indent + (options && options.compressed ? "" : " ");
if (finalVal.length)
s += childIndent + finalVal + linebreak;
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 (finalVal.length) {
s += ">" + finalVal + "</" + this.name +">";
}
else s += "/>";
return s;
};
/*
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!");
// 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);
};
// 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;
}
// create these closures and cache them by keeping them file-scoped
function parser_opentag() { delegates[0]._opentag.apply(delegates[0],arguments) }
function parser_closetag() { delegates[0]._closetag.apply(delegates[0],arguments) }
function parser_text() { delegates[0]._text.apply(delegates[0],arguments) }
function parser_cdata() { delegates[0]._cdata.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];
}
// Are we being used in a Node-like environment?
if (typeof module !== 'undefined' && module.exports)
module.exports.XmlDocument = XmlDocument;
else
this.XmlDocument = XmlDocument;
})();