1/* dommer.js - a (mostly) compliant subset of DOM level 2 for JS 2 (c) Guido Wesdorp 2004-2007 3 email johnny@debris.demon.nl 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software 17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 19 dommer.js 20 21 This library provides a mostly compliant subset of the DOM API in core 22 JavaScript. A number of methods aren't implemented, and there are a few 23 semantic differences between the standard and this implementations, but 24 it provides most of DOM level 2's features and is usable in almost all JS 25 environments (also stand-alone ones). 26 27 I started writing this mainly because of IE's lack of proper namespace 28 support, and to have a portable, reliable DOM implementation. 29 30 Non-standard are: 31 32 - Whitespace is never ignored. 33 34 - Because of JS doesn't (by default) allow computing attributes on request, 35 this API doesn't create Element.nodeName on setting element.prefix, 36 therefore a new method was added: Element.setPrefix (note that this 37 is not required if the library is not used on browsers that don't 38 support __defineGetter__ and __defineSetter__ (such as IE)). 39 40 $Id: dommer.js,v 1.1.1.1 2011/11/09 06:36:04 sungmin Exp $ 41 42*/ 43 44// If the following switch is set to true, setting Element.prefix 45// will result in an exception. This serves to make sure scripts work 46// cross-browser: IE does not support __defineSetter__, which is used 47// to ensure Element.nodeName is updated if Element.prefix 48// is changed (and also to ensure Element.nodeName and 49// Element.localName can't be changed directly). The lack of this 50// method on IE means that on that platform it is possible to break 51// integrity (by setting .prefix directly, .nodeName will be out-of-date). 52// Note that this means that if you intend to use this lib only on Mozilla 53// (or other browsers that support dynamic properties), you can safely 54// set this to false and set .prefix without breaking integrity. 55var WARN_ON_PREFIX = true; 56 57// give this a namespace... 58try { 59 var global = window; 60} catch(e) { 61 var global = this; 62}; 63global.dommer = new function() { 64 /* Exceptions */ 65 function DOMException(errorcode, message) { 66 this.code = null; 67 this.error = null; 68 this.message = message 69 for (var attr in DOMException) { 70 if (DOMException[attr] == errorcode) { 71 this.error = attr; 72 break; 73 }; 74 }; 75 this.code = errorcode; 76 if (!this.error) { 77 this.error = 'Unknown'; 78 }; 79 this.stack = stack = createStack(); 80 this.lineNumber = getLineNo(stack); 81 this.fileName = getFileName(stack); 82 }; 83 84 this.DOMException = DOMException; 85 86 // error codes 87 // XXX should we make these global, like in the specs? 88 DOMException.INDEX_SIZE_ERR = 1, 89 DOMException.DOMSTRING_SIZE_ERR = 2; 90 DOMException.HIERARCHY_REQUEST_ERR = 3; 91 DOMException.WRONG_DOCUMENT_ERR = 4; 92 DOMException.INVALID_CHARACTER_ERR = 5; 93 DOMException.NO_DATA_ALLOWED_ERR = 6; 94 DOMException.NO_MODIFICATION_ALLOWED_ERR = 7; 95 DOMException.NOT_FOUND_ERR = 8; 96 DOMException.NOT_SUPPORTED_ERR = 9; 97 DOMException.INUSE_ATTRIBUTE_ERR = 10; 98 DOMException.INVALID_STATE_ERR = 11; 99 DOMException.SYNTAX_ERR = 12; 100 DOMException.INVALID_MODIFICATION_ERR = 13; 101 DOMException.NAMESPACE_ERR = 14; 102 DOMException.INVALID_ACCESS_ERR = 15; 103 104 DOMException.prototype.toString = function() { 105 var ret = 'DOMException: ' + this.error + ' (' + this.code + ')'; 106 if (this.message) { 107 ret += ' - ' + this.message; 108 }; 109 return ret; 110 }; 111 112 /* Node interface */ 113 function Node() { 114 this.ELEMENT_NODE = 1; 115 this.ATTRIBUTE_NODE = 2; 116 this.TEXT_NODE = 3; 117 this.CDATA_SECTION_NODE = 4; 118 this.ENTITY_REFERENCE_NODE = 5; 119 this.ENTITY_NODE = 6; 120 this.PROCESSING_INSTRUCTION_NODE = 7; 121 this.COMMENT_NODE = 8; 122 this.DOCUMENT_NODE = 9; 123 this.DOCUMENT_TYPE_NODE = 10; 124 this.DOCUMENT_FRAGMENT_NODE = 11; 125 this.NOTATION_NODE = 12; 126 127 // These are defined in-line rather than on .prototype to allow using 128 // them below, too. This way we don't have to check whether attributes 129 // are already protected while this constructor is ran or not (in JS, 130 // when you set 'Foo.prototype = new Bar;', the Bar constructor is 131 // actually ran, in our case this means that the state of the 132 // superclass changes). 133 this._protectAttribute = function(attr) { 134 /* make an attribute read-only */ 135 this.__defineSetter__(attr, 136 function(value) { 137 throw( 138 (new DOMException( 139 DOMException.NO_MODIFICATION_ALLOWED_ERR, attr)) 140 ); 141 } 142 ); 143 this.__defineGetter__(attr, 144 function() { 145 return this['_' + attr]; 146 } 147 ); 148 }; 149 150 this._setProtected = function(name, value) { 151 /* set a read-only attribute 152 153 THIS IS AN INTERNAL METHOD that should not get used as part 154 of the API 155 */ 156 this['_' + name] = value; 157 if (!this.__defineSetter__) { 158 this[name] = value; 159 }; 160 }; 161 162 this.nodeValue = null; 163 if (this.__defineSetter__) { 164 // on browsers that support __define[GS]etter__, perform integrity 165 // checks 166 // nodeValue should be settable on certain nodeTypes 167 this.__defineSetter__('nodeValue', 168 function(nodeValue) { 169 if (this.nodeType != this.TEXT_NODE && 170 this.nodeType != this.ATTRIBUTE_NODE && 171 this.nodeType != this.COMMENT_NODE) { 172 throw( 173 (new DOMException( 174 DOMException.NO_DATA_ALLOWED_ERR, 175 'nodeValue')) 176 ); 177 }; 178 // XXX should check on allowed chars here, but not 179 // sure which? 180 this._nodeValue = nodeValue; 181 } 182 ); 183 // XXX not sure if we should protect reading .nodeValue 184 this.__defineGetter__('nodeValue', 185 function() { 186 if (this.nodeType != this.TEXT_NODE && 187 this.nodeType != this.ATTRIBUTE_NODE && 188 this.nodeType != this.COMMENT_NODE) { 189 throw( 190 (new DOMException( 191 DOMException.NO_DATA_ALLOWED_ERR, 192 'nodeValue')) 193 ); 194 }; 195 return this._nodeValue; 196 } 197 ); 198 var toprotect = ['nodeType', 'nodeName', 'parentNode', 199 'childNodes', 'firstChild', 'lastChild', 200 'previousSibling', 'nextSibling', 201 'attributes', 'ownerDocument', 'namespaceURI', 202 'localName']; 203 for (var i=0; i < toprotect.length; i++) { 204 this._protectAttribute(toprotect[i]); 205 }; 206 }; 207 208 this._setProtected('namespaceURI', null); 209 this._setProtected('prefix', null); 210 this._setProtected('nodeName', null); 211 this._setProtected('localName', null); 212 this._setProtected('parentNode', null); 213 // note that this is shared between subclass instances, so should be 214 // re-set in every .initialize() (so below is just for show) 215 this._setProtected('childNodes', []); 216 this._setProtected('firstChild', null); 217 this._setProtected('lastChild', null); 218 this._setProtected('previousSibling', null); 219 this._setProtected('nextSibling', null); 220 this._setProtected('ownerDocument', null); 221 }; 222 223 this.Node = Node; 224 225 var thrownotsupported = function() {throw('not supported');}; 226 227 // XXX these should be implemented at some point... 228 Node.prototype.normalize = thrownotsupported; 229 Node.prototype.isSupported = thrownotsupported; // hehehe... 230 231 // non-standard method, use this always instead of setting .prefix 232 // yourself, as this will update the .nodeName property too 233 Node.prototype.setPrefix = function(prefix) { 234 if (this.__defineSetter__) { 235 this._prefix = prefix; 236 this._nodeName = prefix + ':' + this.localName; 237 } else { 238 this.prefix = prefix; 239 this.nodeName = prefix + ':' + this.localName; 240 }; 241 }; 242 243 Node.prototype.cloneNode = function() { 244 throw( 245 (new DOMException(DOMException.NOT_SUPPORTED_ERR)) 246 ); 247 }; 248 249 Node.prototype.hasChildNodes = function() { 250 return (this.childNodes && this.childNodes.length > 0); 251 }; 252 253 Node.prototype.hasAttributes = function() { 254 return (this.attributes !== undefined && this.attributes.length); 255 }; 256 257 Node.prototype.appendChild = function(newChild) { 258 this._checkModificationAllowed(); 259 this._attach(newChild); 260 }; 261 262 Node.prototype.removeChild = function(oldChild) { 263 this._checkModificationAllowed(); 264 this._checkIsChild(oldChild); 265 var newChildren = new NodeList(); 266 var found = false; 267 for (var i=0; i < this.childNodes.length; i++) { 268 if (this.childNodes[i] === oldChild) { 269 oldChild._setProtected('parentNode', null); 270 var previous = oldChild.previousSibling; 271 if (previous) { 272 oldChild._setProtected('previousSibling', null); 273 previous._setProtected('nextSibling', 274 oldChild.nextSibling); 275 }; 276 var next = oldChild.nextSibling; 277 if (next) { 278 next._setProtected('previousSibling', previous); 279 oldChild._setProtected('nextSibling', null); 280 }; 281 continue; 282 }; 283 newChildren.push(this.childNodes[i]); 284 }; 285 this._setProtected('childNodes', newChildren); 286 this._setProtected('firstChild', 287 (this.childNodes.length > 0 ? this.childNodes[0] : null)); 288 this._setProtected('lastChild', ( 289 this.childNodes.length > 0 ? 290 this.childNodes[this.childNodes.length - 1] : null)); 291 }; 292 293 Node.prototype.replaceChild = function(newChild, refChild) { 294 this._checkModificationAllowed(); 295 this._checkIsChild(refChild); 296 this._attach(newChild, refChild, true); 297 }; 298 299 Node.prototype.insertBefore = function(newChild, refChild) { 300 this._checkModificationAllowed(); 301 this._checkIsChild(refChild); 302 this._attach(newChild, refChild); 303 }; 304 305 Node.prototype._attach = function(newChild, refChild, replace) { 306 // see if the child is in the same document 307 if (newChild.ownerDocument != this.ownerDocument) { 308 throw( 309 (new DOMException(DOMException.WRONG_DOCUMENT_ERR)) 310 ); 311 }; 312 // see if the child is of an allowed type 313 if (newChild.nodeType != newChild.ELEMENT_NODE && 314 newChild.nodeType != newChild.TEXT_NODE && 315 newChild.nodeType != newChild.CDATA_SECTION_NODE && 316 newChild.nodeType != newChild.COMMENT_NODE) { 317 throw( 318 (new DOMException(DOMException.HIERARCHY_REQUEST_ERR)) 319 ); 320 }; 321 // see if the child isn't a (grand)parent of ourselves 322 var currparent = this; 323 while (currparent && currparent.nodeType != newChild.DOCUMENT_NODE) { 324 if (currparent === newChild) { 325 throw( 326 (new DOMException(DOMException.HIERARCHY_REQUEST_ERR)) 327 ); 328 }; 329 currparent = currparent.parentNode; 330 }; 331 // seems to be okay, add it 332 newChild._setProtected('parentNode', this); 333 if (!refChild) { 334 if (this.childNodes.length) { 335 this.childNodes[this.childNodes.length - 1]._setProtected( 336 'nextSibling', newChild); 337 newChild._setProtected('previousSibling', 338 this.childNodes[this.childNodes.length - 1]); 339 }; 340 this.childNodes.push(newChild); 341 } else { 342 var newchildren = []; 343 var found = false; 344 for (var i=0; i < this.childNodes.length; i++) { 345 var currChild = this.childNodes[i]; 346 if (currChild === refChild) { 347 newchildren.push(newChild); 348 var previous = this.childNodes[i - 1]; 349 if (previous) { 350 newChild._setProtected('previousSibling', previous); 351 previous._setProtected('nextSibling', newChild); 352 }; 353 if (!replace) { 354 newchildren.push(currChild); 355 currChild._setProtected('previousSibling', newChild); 356 newChild._setProtected('nextSibling', currChild); 357 } else { 358 currChild._setProtected('parentNode', null); 359 currChild._setProtected('previousSibling', null); 360 currChild._setProtected('nextSibling', null); 361 var next = this.childNodes[i + 1]; 362 newChild._setProtected('nextSibling', next); 363 next._setProtected('previousSibling', newChild); 364 }; 365 found = true; 366 } else { 367 newchildren.push(currChild); 368 }; 369 }; 370 if (!found) { 371 throw( 372 (new DOMException(DOMException.NOT_FOUND_ERR)) 373 ); 374 }; 375 this._setProtected('childNodes', newchildren); 376 }; 377 this._setProtected('firstChild', this.childNodes[0]); 378 this._setProtected('lastChild', 379 this.childNodes[this.childNodes.length - 1]); 380 }; 381 382 Node.prototype._checkModificationAllowed = function() { 383 if (this.nodeType != this.ELEMENT_NODE && 384 this.nodeType != this.DOCUMENT_NODE && 385 this.nodeType != this.DOCUMENT_FRAGMENT_NODE) { 386 throw( 387 (new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)) 388 ); 389 }; 390 }; 391 392 Node.prototype._checkIsChild = function(refChild) { 393 if (refChild.parentNode !== this) { 394 throw( 395 (new DOMException(DOMException.NOT_FOUND_ERR)) 396 ); 397 }; 398 }; 399 400 function DocumentFragment() { 401 this._setProtected('nodeType', 11); 402 }; 403 404 DocumentFragment.prototype = new Node; 405 this.DocumentFragment = DocumentFragment; 406 407 function Element() { 408 this._setProtected('nodeType', 1); 409 }; 410 411 Element.prototype = new Node; 412 this.Element = Element; 413 414 Element.prototype.initialize = function(namespaceURI, qname, 415 ownerDocument) { 416 // XXX the specs are very vague about an id, it says the DOM 417 // implementation must have info about which attributes are of the id 418 // type, I'll just use the property here for now... 419 this.id = ''; // empty string like in Mozilla, seems weird to me though 420 421 this._setProtected('attributes', []); 422 this._setProtected('childNodes', []); 423 this._setProtected('ownerDocument', ownerDocument); 424 425 // try to ensure integrity by defining getters and setters for certain 426 // properties, since this only works in certain browsers it makes sense to 427 // test your applications on one of those platforms, see also 428 // WARN_ON_PREFIX in the top of the document 429 if (this.__defineSetter__) { 430 this._nodeName = this.nodeName; 431 this.__defineSetter__('nodeName', function() { 432 throw( 433 (new DOMException( 434 DOMException.NO_MODIFICATION_ALLOWED_ERR))) 435 }); 436 this.__defineGetter__('nodeName', 437 function() {return this._nodeName}); 438 this.__defineSetter__('prefix', 439 function(value) { 440 if (WARN_ON_PREFIX) { 441 throw('Setting prefix directly ' + 442 'breaks integrity of the ' + 443 'XML DOM in Internet ' + 444 'Explorer browsers!'); 445 }; 446 this._prefix = value; 447 this._nodeName = this._prefix + 448 this._localName; 449 }); 450 this.__defineGetter__('prefix', function() {return this._prefix}); 451 }; 452 // XXX both the ns and qname need integrity checks 453 this._setProtected('namespaceURI', namespaceURI); 454 if (qname.indexOf(':') > -1) { 455 var tup = qname.split(':'); 456 this.setPrefix(tup.shift()); 457 this._setProtected('localName', tup.join(':')); 458 } else { 459 this.setPrefix(null); 460 this._setProtected('localName', qname); 461 }; 462 if (this.prefix) { 463 this._setProtected('nodeName', this.prefix + ':' + this.localName); 464 } else { 465 this._setProtected('nodeName', this.localName); 466 }; 467 }; 468 469 Element.prototype.toString = function() { 470 return '<Element "' + this.nodeName + '" (type ' + 471 this.nodeType + ')>'; 472 }; 473 474 Element.prototype.toXML = function(context) { 475 // context is used when toXML is called recursively 476 // marker 477 var no_prefix_id = '::no_prefix::'; 478 if (!context) { 479 context = { 480 namespace_stack: [] 481 }; 482 }; 483 var new_namespaces = {}; // any namespaces that weren't declared yet 484 var current_namespaces = {}; 485 var last_namespaces = context.namespace_stack[ 486 context.namespace_stack.length - 1]; 487 context.namespace_stack.push(current_namespaces); 488 if (last_namespaces) { 489 for (var prefix in last_namespaces) { 490 current_namespaces[prefix] = last_namespaces[prefix]; 491 }; 492 }; 493 var xml = '<' + this.nodeName; 494 var prefix = this.prefix || no_prefix_id; 495 if (this.namespaceURI && 496 (current_namespaces[prefix] != this.namespaceURI)) { 497 current_namespaces[prefix] = this.namespaceURI; 498 new_namespaces[prefix] = this.namespaceURI; 499 }; 500 for (var i=0; i < this.attributes.length; i++) { 501 var attr = this.attributes[i]; 502 var aprefix = attr.prefix || no_prefix_id; 503 if (attr.namespaceURI && 504 current_namespaces[aprefix] != attr.namespaceURI) { 505 current_namespaces[aprefix] = attr.namespaceURI; 506 new_namespaces[aprefix] = attr.namespaceURI; 507 }; 508 xml += ' ' + attr.nodeName + '="' + 509 string.entitize(attr.nodeValue) + '"'; 510 }; 511 512 // take care of any new namespaces 513 for (var prefix in new_namespaces) { 514 xml += ' xmlns'; 515 if (prefix != no_prefix_id) { 516 xml += ':' + prefix; 517 }; 518 xml += '="' + string.entitize(new_namespaces[prefix]) + '"'; 519 }; 520 521 if (this.childNodes.length) { 522 xml += '>'; 523 for (var i=0; i < this.childNodes.length; i++) { 524 xml += this.childNodes[i].toXML(context); 525 }; 526 xml += '</' + this.nodeName + '>'; 527 } else { 528 xml += ' />'; 529 }; 530 context.namespace_stack.pop(); 531 return xml; 532 }; 533 534 Element.prototype.cloneNode = function(deep) { 535 var el = new Element(); 536 el.initialize(this.namespaceURI, this.nodeName, this.ownerDocument); 537 for (var i=0; i < this.attributes.length; i++) { 538 var clone = this.attributes[i].cloneNode(); 539 clone._setProtected('ownerElement', el); 540 el.attributes.push(clone); 541 }; 542 if (deep) { 543 for (var i=0; i < this.childNodes.length; i++) { 544 var clone = this.childNodes[i].cloneNode(true); 545 clone._setProtected('parentNode', el); 546 el.appendChild(clone); 547 }; 548 }; 549 return el; 550 }; 551 552 Element.prototype.getAttributeNodeNS = function(namespaceURI, qname) { 553 for (var i=0; i < this.attributes.length; i++) { 554 var attr = this.attributes[i]; 555 if (attr.namespaceURI == namespaceURI && attr.nodeName == qname) { 556 return attr; 557 }; 558 }; 559 }; 560 561 Element.prototype.getAttributeNode = function(name) { 562 return this.getAttributeNodeNS(undefined, name); 563 }; 564 565 Element.prototype.getAttribute = function(name) { 566 var attr = this.getAttributeNode(name) 567 return (attr ? attr.nodeValue : null); 568 }; 569 570 Element.prototype.getAttributeNS = function(namespaceURI, name) { 571 var attr = this.getAttributeNodeNS(namespaceURI, name); 572 return (attr ? attr.nodeValue : null); 573 }; 574 575 Element.prototype.hasAttributeNS = function(namespaceURI, name) { 576 return !!(this.getAttributeNS(namespaceURI, name)); 577 }; 578 579 Element.prototype.hasAttribute = function(name) { 580 return this.hasAttributeNS(this.namespaceURI, name); 581 }; 582 583 Element.prototype.setAttributeNS = function(namespaceURI, name, value) { 584 for (var i=0; i < this.attributes.length; i++) { 585 var attr = this.attributes[i]; 586 if (attr.namespaceURI == namespaceURI && attr.nodeName == name) { 587 attr.nodeValue = value; 588 return; 589 }; 590 }; 591 var attr = new Attribute(); 592 attr.initialize(namespaceURI, name, value, this.ownerDocument); 593 attr._setProtected('ownerElement', this); 594 this.attributes.push(attr); 595 }; 596 597 Element.prototype.setAttribute = function(name, value) { 598 this.setAttributeNS(undefined, name, value); 599 }; 600 601 Element.prototype.setAttributeNodeNS = function(newAttr) { 602 for (var i=0; i < this.attributes.length; i++) { 603 var attr = this.attributes[i]; 604 if (attr.namespaceURI == newAttr.namespaceURI && 605 attr.nodeName == newAttr.nodeName) { 606 throw( 607 (new DOMException(DOMException.INUSE_ATTRIBUTE_ERR)) 608 ); 609 }; 610 }; 611 this.attributes.push(newAttr); 612 }; 613 614 Element.prototype.setAttributeNode = function(newAttr) { 615 // XXX should this fail if no namespaceURI is available or something? 616 this.setAttributeNodeNS(newAttr); 617 }; 618 619 Element.prototype.removeAttributeNS = function(namespaceURI, name) { 620 for (var i=0; i < this.attributes.length; i++) { 621 var attr = this.attributes[i]; 622 if (attr.namespaceURI == namespaceURI && attr.nodeName == name) { 623 delete this.attributes[i]; 624 return true; 625 }; 626 }; 627 return false; 628 }; 629 630 Element.prototype.removeAttribute = function(name) { 631 return this.removeAttributeNS(this.namespaceURI, name); 632 }; 633 634 Element.prototype.getElementsByTagNameNS = function(namespaceURI, 635 name, ret) { 636 // XXX *very* slow!!! 637 // needs to be optimized later on (probably by using some mapping) 638 if (!ret) { 639 ret = []; 640 }; 641 for (var i=0; i < this.childNodes.length; i++) { 642 var child = this.childNodes[i]; 643 if (name == child.nodeName || name == '*') { 644 if ((!namespaceURI && !child.namespaceURI) || 645 (namespaceURI == child.namespaceURI)) { 646 ret.push(child); 647 }; 648 }; 649 if (child.nodeType == 1) { 650 child.getElementsByTagNameNS(namespaceURI, name, ret); 651 }; 652 }; 653 return ret; 654 }; 655 656 Element.prototype.getElementsByTagName = function(name) { 657 return this.getElementsByTagNameNS(this.namespaceURI, name); 658 }; 659 660 Element.prototype.getElementById = function(id) { 661 // XXX *very* slow!!! 662 // needs to be optimized later on (probably by using some mapping) 663 if (this.id == id) { 664 return this; 665 }; 666 for (var i=0; i < this.childNodes.length; i++) { 667 var child = this.childNodes[i]; 668 if (child.id == id) { 669 return child; 670 }; 671 if (child.nodeType == 1) { 672 var found = this.childNodes[i].getElementById(id); 673 if (found) { 674 return found; 675 }; 676 }; 677 }; 678 }; 679 680 function TextNode() { 681 this._setProtected('nodeType', 3); 682 this._setProtected('nodeName', '#text'); 683 }; 684 685 TextNode.prototype = new Node; 686 this.TextNode = TextNode; 687 688 TextNode.prototype.initialize = function(data, ownerDocument) { 689 this._setProtected('ownerDocument', ownerDocument); 690 this._setProtected('childNodes', new NodeList()); 691 // nodeValue is not protected 692 this.nodeValue = data; 693 }; 694 695 TextNode.prototype.toXML = function() { 696 return string.entitize(this.nodeValue); 697 }; 698 699 TextNode.prototype.cloneNode = function() { 700 var node = new TextNode(); 701 node.initialize(this.nodeValue, this.ownerDocument); 702 return node; 703 }; 704 705 function CommentNode() { 706 /* a comment node */ 707 this._setProtected('nodeType', 8); 708 this._setProtected('nodeName', '#comment'); 709 }; 710 711 CommentNode.prototype = new TextNode; 712 this.CommentNode = CommentNode; 713 714 CommentNode.prototype.initialize = function(data, ownerDocument) { 715 this._setProtected('ownerDocument', ownerDocument); 716 this._setProtected('childNodes', []); 717 this._setProtected('nodeValue', data); 718 }; 719 720 CommentNode.prototype.toXML = function() { 721 return "<!--" + this.nodeValue + "-->"; 722 }; 723 724 // Attribute, subclass of TextNode because of the nice implementation 725 function Attribute() { 726 /* an attribute node */ 727 this._setProtected('nodeType', 2); 728 }; 729 730 Attribute.prototype = new Node; 731 this.Attribute = Attribute; 732 733 Attribute.prototype.initialize = function(namespaceURI, qname, value, 734 ownerDocument) { 735 // XXX some code duplication here... 736 if (qname.match(/[^a-zA-Z0-9_\-:]/g)) { 737 throw( 738 (new DOMException(DOMException.INVALID_CHARACTER_ERR)) 739 ); 740 }; 741 this._setProtected('ownerDocument', ownerDocument); 742 this._setProtected('namespaceURI', namespaceURI); 743 this._setProtected('nodeValue', value); 744 this._setProtected('childNodes', []); 745 746 // try to ensure integrity by defining getters and setters for certain 747 // properties, since this only works in certain browsers it makes sense to 748 // test your applications on one of those platforms, see also 749 // WARN_ON_PREFIX in the top of the document 750 if (this.__defineSetter__) { 751 this._nodeName = this.nodeName; 752 this.__defineSetter__('nodeName', function() { 753 throw( 754 (new DOMException( 755 DOMException.NO_MODIFICATION_ALLOWED_ERR))) 756 }); 757 this.__defineGetter__('nodeName', 758 function() {return this._nodeName}); 759 this.__defineSetter__('prefix', 760 function(value) { 761 if (WARN_ON_PREFIX) { 762 throw('Setting prefix directly ' + 763 'breaks integrity of the ' + 764 'XML DOM in Internet ' + 765 'Explorer browsers!'); 766 }; 767 this._prefix = value; 768 this._nodeName = this._prefix + 769 this._localName; 770 }); 771 this.__defineGetter__('prefix', function() {return this._prefix}); 772 this._protectAttribute('ownerElement'); 773 }; 774 this._setProtected('ownerElement', null); 775 if (qname.indexOf(':') > -1) { 776 var tup = qname.split(':'); 777 this.setPrefix(tup.shift()); 778 this._setProtected('localName', tup.join(':')); 779 } else { 780 this.setPrefix(null); 781 this._setProtected('localName', qname); 782 }; 783 if (this.prefix) { 784 this._setProtected('nodeName', this.prefix + ':' + this.localName); 785 } else { 786 this._setProtected('nodeName', this.localName); 787 }; 788 }; 789 790 Attribute.prototype.toXML = function() { 791 ret = this.nodeName + '="' + string.entitize(this.nodeValue) + '"'; 792 return ret; 793 }; 794 795 Attribute.prototype.cloneNode = function() { 796 var attr = new Attribute(); 797 attr.initialize(this.namespaceURI, this.nodeName, this.nodeValue, 798 this.ownerDocument); 799 return attr; 800 }; 801 802 Attribute.prototype.toString = function() { 803 return this.nodeValue; 804 }; 805 806 function Document() { 807 /* the document node */ 808 this._setProtected('nodeType', 9); 809 this._setProtected('nodeName', '#document'); 810 }; 811 812 Document.prototype = new Node; 813 this.Document = Document; 814 815 Document.prototype.initialize = function() { 816 this._setProtected('ownerDocument', this); 817 this._setProtected('childNodes', []); 818 this.documentElement = null; 819 this.namespaceToPrefix = {}; 820 }; 821 822 Document.prototype.toXML = function() { 823 return this.documentElement.toXML(); 824 }; 825 826 Document.prototype.appendChild = function(newChild) { 827 if (this.documentElement) { 828 throw( 829 (new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 830 'document already has a document element')) 831 ); 832 }; 833 this._checkModificationAllowed(); 834 this._attach(newChild); 835 this.documentElement = newChild; 836 }; 837 838 839 Document.prototype.createElement = function(nodeName) { 840 return this.createElementNS(this.namespaceURI, nodeName); 841 }; 842 843 Document.prototype.createElementNS = function(namespaceURI, nodeName) { 844 var el = new Element(); 845 el.initialize(namespaceURI, nodeName, this); 846 return el; 847 }; 848 849 Document.prototype.createTextNode = function(data) { 850 var el = new TextNode(); 851 el.initialize(string.deentitize(data), this); 852 return el; 853 }; 854 855 Document.prototype.createAttributeNS = function(namespaceURI, nodeName) { 856 var el = new Attribute(); 857 el.initialize(namespaceURI, nodeName, null, this); 858 return el; 859 }; 860 861 Document.prototype.createAttribute = function(nodeName) { 862 return this.createAttributeNS(undefined, nodeName); 863 }; 864 865 Document.prototype.createComment = function(data) { 866 var el = new CommentNode(); 867 el.initialize(data, this); 868 return el; 869 }; 870 871 Document.prototype.importNode = function(node) { 872 node._setProtected('ownerDocument', this); 873 }; 874 875 function DOMHandler() { 876 /* SAX handler to convert a piece of XML to a DOM */ 877 }; 878 879 this.DOMHandler = DOMHandler; 880 881 DOMHandler.prototype.startDocument = function() { 882 this.document = new Document(); 883 this.document.initialize(); 884 this.current = null; 885 this.namespaces = new Array(); 886 this.namespaceToPrefix = {}; 887 }; 888 889 DOMHandler.prototype.startElement = function(namespaceURI, nodename, 890 attrs) { 891 if (namespaceURI && !array.contains(this.namespaces, namespaceURI)) { 892 this.namespaces.push(namespaceURI); 893 // update the mapping on the document just to be sure, 894 // that one and the one on this handler should always be in 895 // sync if a start tag is encountered, since instantiating a 896 // Element will set the prefix on that element 897 // XXX ?? 898 this.document.namespaceToPrefix = this.namespaceToPrefix; 899 }; 900 var node = this.document.createElementNS(namespaceURI, nodename); 901 var prefix = undefined; 902 if (namespaceURI) { 903 prefix = this.namespaceToPrefix[namespaceURI]; 904 if (prefix) { 905 node.setPrefix(prefix); 906 }; 907 }; 908 for (var ans in attrs) { 909 // XXX can be optimized by using a dict and just setting the key 910 if (ans && ans != '' && !array.contains(this.namespaces, ans)) { 911 this.namespaces.push(ans); 912 }; 913 var nsattrs = attrs[ans]; 914 for (var aname in nsattrs) { 915 if (aname == 'prefix') { 916 continue; 917 }; 918 if (ans) { 919 var attr = this.document.createAttributeNS(ans, aname); 920 attr.setPrefix(this.namespaceToPrefix[ans]); 921 attr.nodeValue = nsattrs[aname]; 922 node.setAttributeNodeNS(attr); 923 } else { 924 var attr = this.document.createAttribute(aname); 925 attr.nodeValue = nsattrs[aname]; 926 node.setAttributeNode(attr); 927 }; 928 }; 929 }; 930 if (!this.current) { 931 this.document.documentElement = node; 932 this.document._setProtected('childNodes', [node]); 933 this.current = node; 934 this.current._setProtected('parentNode', this.document); 935 this.current._setProtected('ownerDocument', this.document); 936 } else { 937 this.current.appendChild(node); 938 this.current = node; 939 }; 940 }; 941 942 DOMHandler.prototype.characters = function(data) { 943 if (!this.current && string.strip(data) == '') { 944 return; 945 }; 946 var node = this.document.createTextNode(data); 947 this.current.appendChild(node); 948 }; 949 950 DOMHandler.prototype.comment = function(data) { 951 if (!this.current && string.strip(data) == '') { 952 return; 953 }; 954 var node = this.document.createComment(data); 955 if (this.current) { 956 this.current.appendChild(node); 957 } else { 958 this.document.comment = node; 959 }; 960 }; 961 962 DOMHandler.prototype.endElement = function(namespaceURI, nodename) { 963 var prefix = this.namespaceToPrefix[namespaceURI]; 964 if (nodename != this.current.localName || 965 namespaceURI != this.current.namespaceURI) { 966 throw('non-matching end tag ' + namespaceURI + ':' + 967 prefix + ':' + nodename + ' for start tag ' + 968 this.current.namespaceURI + ':' + this.current.nodeName); 969 }; 970 this.current = this.current.parentNode; 971 }; 972 973 DOMHandler.prototype.endDocument = function() { 974 }; 975 976 function DOM() { 977 /* The DOM API 978 979 Uses regular expressions to convert <xml> to a simple DOM 980 981 Provides: 982 983 DOM.parseXML(xml) 984 - parse the XML, return a document element 985 986 DOM.createDocument() 987 - contains the document node of the DOM (which in turn contains 988 the documentElement) 989 990 DOM.toXML() 991 - returns a serialized XML string 992 993 DOM.buildFromHandler(handler) 994 - build and return a DOM document built from a MiniSAX handler 995 */ 996 }; 997 998 this.DOM = DOM; 999 1000 DOM.prototype.createDocument = function() { 1001 var document = new Document(); 1002 document.initialize(); 1003 return document; 1004 }; 1005 1006 DOM.prototype.toXML = function(docOrEl, encoding) { 1007 /* serialize to XML */ 1008 var xml = '<?xml version="1.0"'; 1009 if (encoding) { 1010 xml += ' encoding="' + encoding + '"'; 1011 }; 1012 xml += '?>\n'; 1013 return xml + docOrEl.toXML(); 1014 }; 1015 1016 DOM.prototype.parseXML = function(xml) { 1017 /* parse XML into a DOM 1018 1019 returns a Document node 1020 */ 1021 var handler = new DOMHandler(); 1022 var parser = new SAXParser(); 1023 parser.initialize(xml, handler); 1024 parser.parse(); 1025 var document = handler.document; 1026 this._copyNamespaceMapping(document, handler.namespaceToPrefix); 1027 return document; 1028 }; 1029 1030 DOM.prototype.buildFromHandler = function(handler) { 1031 /* create a DOM from a SAX handler */ 1032 var document = handler.document; 1033 this._copyNamespaceMapping(document, handler.namespaceToPrefix); 1034 return document; 1035 }; 1036 1037 DOM.prototype._copyNamespaceMapping = function(document, namespaces) { 1038 document.namespaceToPrefix = namespaces; 1039 }; 1040 1041 // an implementation of an array, exactly the same as the one in JS 1042 // (although incomplete) itself, this because friggin' IE has problems 1043 // using Array as prototype (it won't update .length on mutations) 1044 function BaseArray() { 1045 for (var i=0; i < arguments.length; i++) { 1046 this[i] = arguments[i]; 1047 }; 1048 this.length = arguments.length; 1049 }; 1050 1051 BaseArray.prototype.concat = function() { 1052 throw('Not supported'); 1053 }; 1054 1055 BaseArray.prototype.join = function() { 1056 throw('Not supported'); 1057 }; 1058 1059 BaseArray.prototype.pop = function() { 1060 var item = this[this.length - 1]; 1061 delete this[this.length - 1]; 1062 this.length = this.length - 1; 1063 return item; 1064 }; 1065 1066 BaseArray.prototype.push = function(item) { 1067 this[this.length] = item; 1068 this.length = this.length + 1; 1069 return item; 1070 }; 1071 1072 BaseArray.prototype.reverse = function() { 1073 throw('Not supported'); 1074 }; 1075 1076 BaseArray.prototype.shift = function() { 1077 var item = this[0]; 1078 for (var i=1; i < this.length; i++) { 1079 this[i-1] = this[i]; 1080 }; 1081 delete this[length - 1]; 1082 this.length = this.length - 1; 1083 return item; 1084 }; 1085 1086 BaseArray.prototype.unshift = function(item) { 1087 for (var i=0; i < this.length; i++ ) { 1088 this[this.length - i] = this[(this.length - i) - 1]; 1089 }; 1090 this[0] = item; 1091 this.length = this.length + 1; 1092 return ; 1093 }; 1094 1095 BaseArray.prototype.splice = function() { 1096 // XXX we may want to support this later 1097 throw('Not supported'); 1098 }; 1099 1100 BaseArray.prototype.toString = function() { 1101 var ret = []; 1102 for (var i=1; i < this.length; i++) { 1103 ret.push(this[i].toString()); 1104 }; 1105 return ret.join(', '); 1106 }; 1107 1108 // for subclassing and such... 1109 this.BaseArray = BaseArray; 1110 1111 function NodeList() { 1112 }; 1113 1114 NodeList.prototype = new BaseArray; 1115 this.NodeList = NodeList; 1116 1117 NodeList.prototype.item = function(index) { 1118 return this[index]; 1119 }; 1120 1121 function NamedNodeMap() { 1122 }; 1123 1124 NamedNodeMap.prototype = new BaseArray; 1125 this.NamedNodeMap = NamedNodeMap; 1126 1127 NamedNodeMap.prototype.item = function(index) { 1128 return this[index]; 1129 }; 1130 1131 NamedNodeMap.prototype.getNamedItem = function(name) { 1132 for (var i=0; i < this.length; i++) { 1133 if (this[i].nodeName == name) { 1134 return this[i]; 1135 }; 1136 }; 1137 return undefined; 1138 }; 1139 1140 NamedNodeMap.prototype.setNamedItem = function(arg) { 1141 // this should generate exceptions, but I'm not sure when... 1142 // XXX how 'bout when arg is not the proper type?!? 1143 for (var i=0; i < this.length; i++) { 1144 if (this[i].nodeName == arg.nodeName) { 1145 this[i] = arg; 1146 return; 1147 }; 1148 }; 1149 this.push(arg); 1150 }; 1151 1152 NamedNodeMap.prototype.removeNamedItem = function(name) { 1153 // a bit nasty: deleting an element from an array will not actually 1154 // free the index, instead something like undefined or null will end 1155 // up in its place, so we walk the array here, move every element 1156 // behind the item to remove one up, and pop the last item when 1157 // we're done 1158 var delete_mode = false; 1159 for (var i=0; i < this.length; i++) { 1160 if (this[i] === name) { 1161 delete_mode = true; 1162 }; 1163 if (delete_mode) { 1164 this[i] = this[i + 1]; 1165 }; 1166 }; 1167 if (!delete_mode) { 1168 throw( 1169 (new DOMException(DOMException.NOT_FOUND_ERR)) 1170 ); 1171 }; 1172 // the last element is now in the array twice 1173 this.pop(); 1174 }; 1175}(); 1176 1177// XXX shouldn't we make these local? 1178function createStack() { 1179 // somewhat nasty trick to get a stack trace in Moz 1180 var stack = undefined; 1181 try {notdefined()} catch(e) {stack = e.stack}; 1182 if (stack) { 1183 stack = stack.split('\n'); 1184 stack.shift(); 1185 stack.shift(); 1186 }; 1187 return stack ? stack.join('\n') : ''; 1188}; 1189 1190function getLineNo(stack) { 1191 /* tries to get the line no in Moz */ 1192 if (!stack) { 1193 return; 1194 }; 1195 stack = stack.toString().split('\n'); 1196 var chunks = stack[0].split(':'); 1197 var lineno = chunks[chunks.length - 1]; 1198 if (lineno != '0') { 1199 return lineno; 1200 }; 1201}; 1202 1203function getFileName(stack) { 1204 /* tries to get the filename in Moz */ 1205 if (!stack) { 1206 return; 1207 }; 1208 stack = stack.toString().split('\n'); 1209 var chunks = stack[0].split(':'); 1210 var filename = chunks[chunks.length - 2]; 1211 return filename; 1212}; 1213 1214