// Copyright 2005 Google Inc. // All Rights Reserved // // An XML parse and a minimal DOM implementation that just supportes // the subset of the W3C DOM that is used in the XSLT implementation. // // References: // // [DOM] W3C DOM Level 3 Core Specification // . // // // Author: Steffen Meschkat // NOTE: The split() method in IE omits empty result strings. This is // utterly annoying. So we don't use it here. // Resolve entities in XML text fragments. According to the DOM // specification, the DOM is supposed to resolve entity references at // the API level. I.e. no entity references are passed through the // API. See "Entities and the DOM core", p.12, DOM 2 Core // Spec. However, different browsers actually pass very different // values at the API. // function xmlResolveEntities(s) { var parts = stringSplit(s, '&'); var ret = parts[0]; for (var i = 1; i < parts.length; ++i) { var rp = stringSplit(parts[i], ';'); if (rp.length == 1) { // no entity reference: just a & but no ; ret += parts[i]; continue; } var ch; switch (rp[0]) { case 'lt': ch = '<'; break; case 'gt': ch = '>'; break; case 'amp': ch = '&'; break; case 'quot': ch = '"'; break; case 'apos': ch = '\''; break; case 'nbsp': ch = String.fromCharCode(160); break; default: // Cool trick: let the DOM do the entity decoding. We assign // the entity text through non-W3C DOM properties and read it // through the W3C DOM. W3C DOM access is specified to resolve // entities. var span = window.document.createElement('span'); span.innerHTML = '&' + rp[0] + '; '; ch = span.childNodes[0].nodeValue.charAt(0); } ret += ch + rp[1]; } return ret; } // Parses the given XML string with our custom, JavaScript XML parser. Written // by Steffen Meschkat (mesch@google.com). function xmlParse(xml) { Timer.start('xmlparse'); var regex_empty = /\/$/; // See also for // allowed chars in a tag and attribute name. TODO(mesch): the // following is still not completely correct. var regex_tagname = /^([\w:-]*)/; var regex_attribute = /([\w:-]+)\s?=\s?('([^\']*)'|"([^\"]*)")/g; var xmldoc = new XDocument(); var root = xmldoc; // For the record: in Safari, we would create native DOM nodes, but // in Opera that is not possible, because the DOM only allows HTML // element nodes to be created, so we have to do our own DOM nodes. // xmldoc = document.implementation.createDocument('','',null); // root = xmldoc; // .createDocumentFragment(); // NOTE(mesch): using the DocumentFragment instead of the Document // crashes my Safari 1.2.4 (v125.12). var stack = []; var parent = root; stack.push(parent); var x = stringSplit(xml, '<'); for (var i = 1; i < x.length; ++i) { var xx = stringSplit(x[i], '>'); var tag = xx[0]; var text = xmlResolveEntities(xx[1] || ''); if (tag.charAt(0) == '/') { stack.pop(); parent = stack[stack.length-1]; } else if (tag.charAt(0) == '?') { // Ignore XML declaration and processing instructions } else if (tag.charAt(0) == '!') { // Ignore notation and comments } else { var empty = tag.match(regex_empty); var tagname = regex_tagname.exec(tag)[1]; var node = xmldoc.createElement(tagname); var att; while (att = regex_attribute.exec(tag)) { var val = xmlResolveEntities(att[3] || att[4] || ''); node.setAttribute(att[1], val); } if (empty) { parent.appendChild(node); } else { parent.appendChild(node); parent = node; stack.push(node); } } if (text && parent != root) { parent.appendChild(xmldoc.createTextNode(text)); } } Timer.end('xmlparse'); return root; } // Our W3C DOM Node implementation. Note we call it XNode because we // can't define the identifier Node. We do this mostly for Opera, // where we can't reuse the HTML DOM for parsing our own XML, and for // Safari, where it is too expensive to have the template processor // operate on native DOM nodes. function XNode(type, name, value, owner) { this.attributes = []; this.childNodes = []; XNode.init.call(this, type, name, value, owner); } // Don't call as method, use apply() or call(). XNode.init = function(type, name, value, owner) { this.nodeType = type - 0; this.nodeName = '' + name; this.nodeValue = '' + value; this.ownerDocument = owner; this.firstChild = null; this.lastChild = null; this.nextSibling = null; this.previousSibling = null; this.parentNode = null; } XNode.unused_ = []; XNode.recycle = function(node) { if (!node) { return; } if (node.constructor == XDocument) { XNode.recycle(node.documentElement); return; } if (node.constructor != this) { return; } XNode.unused_.push(node); for (var a = 0; a < node.attributes.length; ++a) { XNode.recycle(node.attributes[a]); } for (var c = 0; c < node.childNodes.length; ++c) { XNode.recycle(node.childNodes[c]); } node.attributes.length = 0; node.childNodes.length = 0; XNode.init.call(node, 0, '', '', null); } XNode.create = function(type, name, value, owner) { if (XNode.unused_.length > 0) { var node = XNode.unused_.pop(); XNode.init.call(node, type, name, value, owner); return node; } else { return new XNode(type, name, value, owner); } } XNode.prototype.appendChild = function(node) { // firstChild if (this.childNodes.length == 0) { this.firstChild = node; } // previousSibling node.previousSibling = this.lastChild; // nextSibling node.nextSibling = null; if (this.lastChild) { this.lastChild.nextSibling = node; } // parentNode node.parentNode = this; // lastChild this.lastChild = node; // childNodes this.childNodes.push(node); } XNode.prototype.replaceChild = function(newNode, oldNode) { if (oldNode == newNode) { return; } for (var i = 0; i < this.childNodes.length; ++i) { if (this.childNodes[i] == oldNode) { this.childNodes[i] = newNode; var p = oldNode.parentNode; oldNode.parentNode = null; newNode.parentNode = p; p = oldNode.previousSibling; oldNode.previousSibling = null; newNode.previousSibling = p; if (newNode.previousSibling) { newNode.previousSibling.nextSibling = newNode; } p = oldNode.nextSibling; oldNode.nextSibling = null; newNode.nextSibling = p; if (newNode.nextSibling) { newNode.nextSibling.previousSibling = newNode; } if (this.firstChild == oldNode) { this.firstChild = newNode; } if (this.lastChild == oldNode) { this.lastChild = newNode; } break; } } } XNode.prototype.insertBefore = function(newNode, oldNode) { if (oldNode == newNode) { return; } if (oldNode.parentNode != this) { return; } if (newNode.parentNode) { newNode.parentNode.removeChild(newNode); } var newChildren = []; for (var i = 0; i < this.childNodes.length; ++i) { var c = this.childNodes[i]; if (c == oldNode) { newChildren.push(newNode); newNode.parentNode = this; newNode.previousSibling = oldNode.previousSibling; oldNode.previousSibling = newNode; if (newNode.previousSibling) { newNode.previousSibling.nextSibling = newNode; } newNode.nextSibling = oldNode; if (this.firstChild == oldNode) { this.firstChild = newNode; } } newChildren.push(c); } this.childNodes = newChildren; } XNode.prototype.removeChild = function(node) { var newChildren = []; for (var i = 0; i < this.childNodes.length; ++i) { var c = this.childNodes[i]; if (c != node) { newChildren.push(c); } else { if (c.previousSibling) { c.previousSibling.nextSibling = c.nextSibling; } if (c.nextSibling) { c.nextSibling.previousSibling = c.previousSibling; } if (this.firstChild == c) { this.firstChild = c.nextSibling; } if (this.lastChild == c) { this.lastChild = c.previousSibling; } } } this.childNodes = newChildren; } XNode.prototype.hasAttributes = function() { return this.attributes.length > 0; } XNode.prototype.setAttribute = function(name, value) { for (var i = 0; i < this.attributes.length; ++i) { if (this.attributes[i].nodeName == name) { this.attributes[i].nodeValue = '' + value; return; } } this.attributes.push(new XNode(DOM_ATTRIBUTE_NODE, name, value)); } XNode.prototype.getAttribute = function(name) { for (var i = 0; i < this.attributes.length; ++i) { if (this.attributes[i].nodeName == name) { return this.attributes[i].nodeValue; } } return null; } XNode.prototype.removeAttribute = function(name) { var a = []; for (var i = 0; i < this.attributes.length; ++i) { if (this.attributes[i].nodeName != name) { a.push(this.attributes[i]); } } this.attributes = a; } function XDocument() { XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, this); this.documentElement = null; } XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document'); XDocument.prototype.clear = function() { XNode.recycle(this.documentElement); this.documentElement = null; } XDocument.prototype.appendChild = function(node) { XNode.prototype.appendChild.call(this, node); this.documentElement = this.childNodes[0]; } XDocument.prototype.createElement = function(name) { return XNode.create(DOM_ELEMENT_NODE, name, null, this); } XDocument.prototype.createDocumentFragment = function() { return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment', null, this); } XDocument.prototype.createTextNode = function(value) { return XNode.create(DOM_TEXT_NODE, '#text', value, this); } XDocument.prototype.createAttribute = function(name) { return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this); } XDocument.prototype.createComment = function(data) { return XNode.create(DOM_COMMENT_NODE, '#comment', data, this); } XNode.prototype.getElementsByTagName = function(name, list) { if (!list) { list = []; } if (this.nodeName == name) { list.push(this); } for (var i = 0; i < this.childNodes.length; ++i) { this.childNodes[i].getElementsByTagName(name, list); } return list; }