1
// Copyright 2005 Google Inc.
4
// An XML parse and a minimal DOM implementation that just supportes
5
// the subset of the W3C DOM that is used in the XSLT implementation.
9
// [DOM] W3C DOM Level 3 Core Specification
10
// <http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/>.
13
// Author: Steffen Meschkat <mesch@google.com>
15
// NOTE: The split() method in IE omits empty result strings. This is
16
// utterly annoying. So we don't use it here.
18
// Resolve entities in XML text fragments. According to the DOM
19
// specification, the DOM is supposed to resolve entity references at
20
// the API level. I.e. no entity references are passed through the
21
// API. See "Entities and the DOM core", p.12, DOM 2 Core
22
// Spec. However, different browsers actually pass very different
25
function xmlResolveEntities(s) {
27
var parts = stringSplit(s, '&');
30
for (var i = 1; i < parts.length; ++i) {
31
var rp = stringSplit(parts[i], ';');
33
// no entity reference: just a & but no ;
56
ch = String.fromCharCode(160);
59
// Cool trick: let the DOM do the entity decoding. We assign
60
// the entity text through non-W3C DOM properties and read it
61
// through the W3C DOM. W3C DOM access is specified to resolve
63
var span = window.document.createElement('span');
64
span.innerHTML = '&' + rp[0] + '; ';
65
ch = span.childNodes[0].nodeValue.charAt(0);
74
// Parses the given XML string with our custom, JavaScript XML parser. Written
75
// by Steffen Meschkat (mesch@google.com).
76
function xmlParse(xml) {
77
Timer.start('xmlparse');
78
var regex_empty = /\/$/;
80
// See also <http://www.w3.org/TR/REC-xml/#sec-common-syn> for
81
// allowed chars in a tag and attribute name. TODO(mesch): the
82
// following is still not completely correct.
84
var regex_tagname = /^([\w:-]*)/;
85
var regex_attribute = /([\w:-]+)\s?=\s?('([^\']*)'|"([^\"]*)")/g;
87
var xmldoc = new XDocument();
90
// For the record: in Safari, we would create native DOM nodes, but
91
// in Opera that is not possible, because the DOM only allows HTML
92
// element nodes to be created, so we have to do our own DOM nodes.
94
// xmldoc = document.implementation.createDocument('','',null);
95
// root = xmldoc; // .createDocumentFragment();
96
// NOTE(mesch): using the DocumentFragment instead of the Document
97
// crashes my Safari 1.2.4 (v125.12).
103
var x = stringSplit(xml, '<');
104
for (var i = 1; i < x.length; ++i) {
105
var xx = stringSplit(x[i], '>');
107
var text = xmlResolveEntities(xx[1] || '');
109
if (tag.charAt(0) == '/') {
111
parent = stack[stack.length-1];
113
} else if (tag.charAt(0) == '?') {
114
// Ignore XML declaration and processing instructions
115
} else if (tag.charAt(0) == '!') {
116
// Ignore notation and comments
118
var empty = tag.match(regex_empty);
119
var tagname = regex_tagname.exec(tag)[1];
120
var node = xmldoc.createElement(tagname);
123
while (att = regex_attribute.exec(tag)) {
124
var val = xmlResolveEntities(att[3] || att[4] || '');
125
node.setAttribute(att[1], val);
129
parent.appendChild(node);
131
parent.appendChild(node);
137
if (text && parent != root) {
138
parent.appendChild(xmldoc.createTextNode(text));
142
Timer.end('xmlparse');
147
// Our W3C DOM Node implementation. Note we call it XNode because we
148
// can't define the identifier Node. We do this mostly for Opera,
149
// where we can't reuse the HTML DOM for parsing our own XML, and for
150
// Safari, where it is too expensive to have the template processor
151
// operate on native DOM nodes.
152
function XNode(type, name, value, owner) {
153
this.attributes = [];
154
this.childNodes = [];
156
XNode.init.call(this, type, name, value, owner);
159
// Don't call as method, use apply() or call().
160
XNode.init = function(type, name, value, owner) {
161
this.nodeType = type - 0;
162
this.nodeName = '' + name;
163
this.nodeValue = '' + value;
164
this.ownerDocument = owner;
166
this.firstChild = null;
167
this.lastChild = null;
168
this.nextSibling = null;
169
this.previousSibling = null;
170
this.parentNode = null;
175
XNode.recycle = function(node) {
180
if (node.constructor == XDocument) {
181
XNode.recycle(node.documentElement);
185
if (node.constructor != this) {
189
XNode.unused_.push(node);
190
for (var a = 0; a < node.attributes.length; ++a) {
191
XNode.recycle(node.attributes[a]);
193
for (var c = 0; c < node.childNodes.length; ++c) {
194
XNode.recycle(node.childNodes[c]);
196
node.attributes.length = 0;
197
node.childNodes.length = 0;
198
XNode.init.call(node, 0, '', '', null);
201
XNode.create = function(type, name, value, owner) {
202
if (XNode.unused_.length > 0) {
203
var node = XNode.unused_.pop();
204
XNode.init.call(node, type, name, value, owner);
207
return new XNode(type, name, value, owner);
211
XNode.prototype.appendChild = function(node) {
213
if (this.childNodes.length == 0) {
214
this.firstChild = node;
218
node.previousSibling = this.lastChild;
221
node.nextSibling = null;
222
if (this.lastChild) {
223
this.lastChild.nextSibling = node;
227
node.parentNode = this;
230
this.lastChild = node;
233
this.childNodes.push(node);
237
XNode.prototype.replaceChild = function(newNode, oldNode) {
238
if (oldNode == newNode) {
242
for (var i = 0; i < this.childNodes.length; ++i) {
243
if (this.childNodes[i] == oldNode) {
244
this.childNodes[i] = newNode;
246
var p = oldNode.parentNode;
247
oldNode.parentNode = null;
248
newNode.parentNode = p;
250
p = oldNode.previousSibling;
251
oldNode.previousSibling = null;
252
newNode.previousSibling = p;
253
if (newNode.previousSibling) {
254
newNode.previousSibling.nextSibling = newNode;
257
p = oldNode.nextSibling;
258
oldNode.nextSibling = null;
259
newNode.nextSibling = p;
260
if (newNode.nextSibling) {
261
newNode.nextSibling.previousSibling = newNode;
264
if (this.firstChild == oldNode) {
265
this.firstChild = newNode;
268
if (this.lastChild == oldNode) {
269
this.lastChild = newNode;
277
XNode.prototype.insertBefore = function(newNode, oldNode) {
278
if (oldNode == newNode) {
282
if (oldNode.parentNode != this) {
286
if (newNode.parentNode) {
287
newNode.parentNode.removeChild(newNode);
290
var newChildren = [];
291
for (var i = 0; i < this.childNodes.length; ++i) {
292
var c = this.childNodes[i];
294
newChildren.push(newNode);
296
newNode.parentNode = this;
298
newNode.previousSibling = oldNode.previousSibling;
299
oldNode.previousSibling = newNode;
300
if (newNode.previousSibling) {
301
newNode.previousSibling.nextSibling = newNode;
304
newNode.nextSibling = oldNode;
306
if (this.firstChild == oldNode) {
307
this.firstChild = newNode;
312
this.childNodes = newChildren;
315
XNode.prototype.removeChild = function(node) {
316
var newChildren = [];
317
for (var i = 0; i < this.childNodes.length; ++i) {
318
var c = this.childNodes[i];
322
if (c.previousSibling) {
323
c.previousSibling.nextSibling = c.nextSibling;
326
c.nextSibling.previousSibling = c.previousSibling;
328
if (this.firstChild == c) {
329
this.firstChild = c.nextSibling;
331
if (this.lastChild == c) {
332
this.lastChild = c.previousSibling;
336
this.childNodes = newChildren;
340
XNode.prototype.hasAttributes = function() {
341
return this.attributes.length > 0;
345
XNode.prototype.setAttribute = function(name, value) {
346
for (var i = 0; i < this.attributes.length; ++i) {
347
if (this.attributes[i].nodeName == name) {
348
this.attributes[i].nodeValue = '' + value;
352
this.attributes.push(new XNode(DOM_ATTRIBUTE_NODE, name, value));
356
XNode.prototype.getAttribute = function(name) {
357
for (var i = 0; i < this.attributes.length; ++i) {
358
if (this.attributes[i].nodeName == name) {
359
return this.attributes[i].nodeValue;
365
XNode.prototype.removeAttribute = function(name) {
367
for (var i = 0; i < this.attributes.length; ++i) {
368
if (this.attributes[i].nodeName != name) {
369
a.push(this.attributes[i]);
376
function XDocument() {
377
XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, this);
378
this.documentElement = null;
381
XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document');
383
XDocument.prototype.clear = function() {
384
XNode.recycle(this.documentElement);
385
this.documentElement = null;
388
XDocument.prototype.appendChild = function(node) {
389
XNode.prototype.appendChild.call(this, node);
390
this.documentElement = this.childNodes[0];
393
XDocument.prototype.createElement = function(name) {
394
return XNode.create(DOM_ELEMENT_NODE, name, null, this);
397
XDocument.prototype.createDocumentFragment = function() {
398
return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment',
402
XDocument.prototype.createTextNode = function(value) {
403
return XNode.create(DOM_TEXT_NODE, '#text', value, this);
406
XDocument.prototype.createAttribute = function(name) {
407
return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);
410
XDocument.prototype.createComment = function(data) {
411
return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);
414
XNode.prototype.getElementsByTagName = function(name, list) {
419
if (this.nodeName == name) {
423
for (var i = 0; i < this.childNodes.length; ++i) {
424
this.childNodes[i].getElementsByTagName(name, list);