UNPKG

d3

Version:

A small, free JavaScript library for manipulating documents based on data.

1,389 lines (1,236 loc) 273 kB
/* * Envjs dom.1.3.pre03 * Pure JavaScript Browser Environment * By John Resig <http://ejohn.org/> and the Envjs Team * Copyright 2008-2010 John Resig, under the MIT License * * Parts of the implementation were originally written by:\ * and Jon van Noort (jon@webarcana.com.au) \ * and David Joham (djoham@yahoo.com)",\ * and Scott Severtson * * This file simply provides the global definitions we need to \ * be able to correctly implement to core browser DOM interfaces." The following are leaked globally intentionally var Attr, CDATASection, CharacterData, Comment, Document, DocumentFragment, DocumentType, DOMException, DOMImplementation, Element, Entity, EntityReference, NamedNodeMap, Namespace, Node, NodeList, Notation, ProcessingInstruction, Text, Range, XMLSerializer, DOMParser, XPathResult, XPathExpression; */ var Envjs = Envjs || require('./platform/core').Envjs, After = After || require('./platform/core').After; /* * Envjs dom.1.3.pre03 * Pure JavaScript Browser Environment * By John Resig <http://ejohn.org/> and the Envjs Team * Copyright 2008-2010 John Resig, under the MIT License */ //CLOSURE_START (function(){ /** * @author john resig */ // Helper method for extending one object with another. function __extend__(a,b) { for ( var i in b ) { if(b.hasOwnProperty(i)){ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i); if ( g || s ) { if ( g ) { a.__defineGetter__(i, g); } if ( s ) { a.__defineSetter__(i, s); } } else { a[i] = b[i]; } } } return a; } /** * @author john resig */ //from jQuery function __setArray__( target, array ) { // Resetting the length to 0, then using the native Array push // is a super-fast way to populate an object with array-like properties target.length = 0; Array.prototype.push.apply( target, array ); } var __findItemIndex__, __insertBefore__, __replaceChild__, __removeChild__, __appendChild__, __addToIndexes__, __removeFromIndexes__, __cloneNodes__; //see namednodemap for these implementations var __addToNamedIndexes__, __removeFromNamedIndexes__; (function(){ var log = Envjs.logger(); Envjs.once('tick', function(){ log = Envjs.logger('Envjs.DOM.NodeList').debug('available'); }); /** * @class NodeList - * provides the abstraction of an ordered collection of nodes * * @param ownerDocument : Document - the ownerDocument * @param parentNode : Node - the node that the NodeList is attached to (or null) */ exports.NodeList = NodeList = function(ownerDocument, parentNode) { this.parentNode = parentNode; this.ownerDocument = ownerDocument; this._readonly = false; }; NodeList.prototype = []; __extend__(NodeList.prototype, { item : function(index) { var ret = null; if ((index >= 0) && (index < this.length)) { // bounds check ret = this[index]; } // if the index is out of bounds, default value null is returned return ret; }, get xml() { var ret = "", j; // create string containing the concatenation of the string values of each child for (j=0; j < this.length; j++) { if(this[j] !== null){ if(this[j].nodeType == Node.TEXT_NODE && j>0 && this[j-1].nodeType == Node.TEXT_NODE){ //add a single space between adjacent text nodes ret += " "+this[j].xml; }else{ ret += this[j].xml; } } } return ret; }, toArray: function () { return this; }, toString: function(){ return "[object NodeList]"; } }); /** * @method __findItemIndex__ * find the item index of the node * @author Jon van Noort (jon@webarcana.com.au) * @param node : Node * @return : int */ __findItemIndex__ = function (nodelist, node) { // if node is not found, default value -1 is returned // return ret; return nodelist.indexOf(node); }; /** * @method __insertBefore__ * insert the specified Node into the NodeList before the specified index * Used by Node.insertBefore(). Note: Node.insertBefore() is responsible * for Node Pointer surgery __insertBefore__ simply modifies the internal * data structure (Array). * @param newChild : Node - the Node to be inserted * @param refChildIndex : int - the array index to insert the Node before */ __insertBefore__ = function(nodelist, newChild, refChildIndex) { if ((refChildIndex >= 0) && (refChildIndex <= nodelist.length)) { // bounds check if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { // node is a DocumentFragment // append the children of DocumentFragment Array.prototype.splice.apply(nodelist, [refChildIndex, 0].concat(newChild.childNodes.toArray())); } else { // append the newChild Array.prototype.splice.apply(nodelist,[refChildIndex, 0, newChild]); __addToIndexes__(newChild, nodelist.parentNode); } } }; /** * @method __replaceChild__ * replace the specified Node in the NodeList at the specified index * Used by Node.replaceChild(). Note: Node.replaceChild() is responsible * for Node Pointer surgery __replaceChild__ simply modifies the internal * data structure (Array). * * @param newChild : Node - the Node to be inserted * @param refChildIndex : int - the array index to hold the Node */ __replaceChild__ = function(nodelist, newChild, refChildIndex) { var ret = null; // bounds check if ((refChildIndex >= 0) && (refChildIndex < nodelist.length)) { // preserve old child for return ret = nodelist[refChildIndex]; if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { // node is a DocumentFragment // get array containing children prior to refChild Array.prototype.splice.apply(nodelist, [refChildIndex, 1].concat(newChild.childNodes.toArray())); } else { // simply replace node in array (links between Nodes are // made at higher level) nodelist[refChildIndex] = newChild; } } // return replaced node return ret; }; /** * @method __removeChild__ * remove the specified Node in the NodeList at the specified index * Used by Node.removeChild(). Note: Node.removeChild() is responsible * for Node Pointer surgery __removeChild__ simply modifies the internal * data structure (Array). * @param refChildIndex : int - the array index holding the Node to be removed */ __removeChild__ = function(nodelist, refChildIndex) { var ret = null; if (refChildIndex > -1) { // found it! // return removed node ret = nodelist[refChildIndex]; // rebuild array without removed child Array.prototype.splice.apply(nodelist,[refChildIndex, 1]); __removeFromIndexes__(ret, nodelist.parentNode); } // return removed node return ret; }; /** * @method __appendChild__ * append the specified Node to the NodeList. Used by Node.appendChild(). * Note: Node.appendChild() is responsible for Node Pointer surgery * __appendChild__ simply modifies the internal data structure (Array). * @param newChild : Node - the Node to be inserted */ __appendChild__ = function(nodelist, newChild) { log.debug('Appending child %s to nodelist %s', newChild.nodeName, nodelist.length); var i; if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { // newChild is a DocumentFragment // append the children of DocumentFragment Array.prototype.push.apply(nodelist, newChild.childNodes.toArray() ); for(i=0;i< newChild.childNodes.length;i++){ __addToIndexes__(newChild.childNodes[i], nodelist.parentNode); } } else { // simply add node to array (links between Nodes are made at higher level) Array.prototype.push.apply(nodelist, [newChild]); __addToIndexes__(newChild, nodelist.parentNode); } }; __addToIndexes__ = function(node, ancestor){ var indexes, index, normalizedName, i, j, descendingIndex, offset, sibling, children, id, name; if(node.nodeType == Node.ELEMENT_NODE){ log.debug('updating node indexes for node %s ancestor %s', node.tagName, ancestor.nodeName); //now we need to walk up all ancestors updating nodelist indexes normalizedName = (node.tagName+'').toLowerCase(); //if the node doesnt have a parentNode yet then it has been imported //into the document, but it is just now being appended. This means we //need to merge the nodes indexes into the ancestors (which will become //the parentNode just after this operation completes) if(!node.parentNode){ indexes = node._indexes_; for(name in indexes){ //this is the index of all descendants of the ancestor with the given tagname if(!ancestor._indexes_.hasOwnProperty(name) && name != normalizedName ){ ancestor._indexes_[name] = []; for(j=0;j<indexes[name].length;j++){ ancestor._indexes_[name].push(indexes[name][j]); } }else if(name != '*' && name != normalizedName){ offset = 0; //this is the index of all descendants with the given tagname index = ancestor._indexes_[name]; children = ancestor.childNodes; for(i=0;i<children.length;i++){ sibling = children[i]; if(sibling !== node && sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === node.tagName){ //find the first child, if any, that exist in this index so we can determine //the document offset at which to update this index offset += (sibling._indexes_[name]?sibling._indexes_[name].length:0)+1; }else if(sibling === node){ break; } } //the descending index is additively a part of the new index descendingIndex = node._indexes_[name]?node._indexes_[name]:[]; Array.prototype.splice.apply( index, Array.prototype.concat.apply( [offset, 0], descendingIndex.slice(0,descendingIndex.length) ) ); log.debug('added %s to index %s -> %s', node.tagName, ancestor.tagName||'document', offset); } } } //now we basically need to crawl up the ancestor chain, merging indexes //using some smarts while(ancestor){ //these are all the indexes already built on the ancestor indexes = ancestor._indexes_; if(!(normalizedName in indexes)){ //make sure we have an index for this particular tagname indexes[normalizedName] = new NodeList(node.ownerDocument, ancestor); } offset = 1; //this is the index of all descendants with the given tagname index = indexes[normalizedName]; children = ancestor.childNodes; for(i=0;i<children.length;i++){ sibling = children[i]; if(sibling !== node && sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === node.tagName){ //find the first child, if any, that exist in this index so we can determine //the document offset at which to update this index offset += (sibling._indexes_[normalizedName]?sibling._indexes_[normalizedName].length:0)+1; }else if(sibling === node){ break; } } //the descending index is additively a part of the new index descendingIndex = node._indexes_[normalizedName]?node._indexes_[normalizedName]:[]; Array.prototype.splice.apply( index, [offset, 0, node].concat( descendingIndex.slice(0,descendingIndex.length) ) ); log.debug('added %s to index %s -> %s', node.tagName, ancestor.tagName||'document', offset); offset = 0; //this is the index of all descendants with the given tagname, so simply follow //the same procedure as above but use the '*' index index = indexes['*']; children = ancestor.childNodes; for(i=0;i<children.length;i++){ sibling = children[i]; if(sibling !== node && sibling.nodeType === Node.ELEMENT_NODE ){ offset += (sibling._indexes_['*']?sibling._indexes_['*'].length:0)+1; }else if(sibling === node){ break; } } descendingIndex = node._indexes_['*']?node._indexes_['*']:[]; Array.prototype.splice.apply( index, [offset, 0, node].concat( descendingIndex.slice(0,descendingIndex.length) ) ); //console.log('added %s to index * -> %s', node.tagName, offset); //handle input type elements and their ancestor form elements //So far we dont bother with maintaining document order for this index if('FORM' == ancestor.nodeName){ switch (node.nodeName) { case 'BUTTON': case 'FIELDSET': case 'INPUT': case 'KEYGEN': case 'OBJECT': case 'OUTPUT': case 'SELECT': case 'TEXTAREA': if(!indexes.hasOwnProperty('$elements')){ //make sure we have an index for the form.elements indexes.$elements = new NodeList(node.ownerDocument, ancestor); } Array.prototype.push.apply(indexes.$elements, [node]); name = node.getAttribute('name'); if( name && !ancestor[name] ){ //<form name='foo'><input name='bar' is available via document.foo.bar ancestor[name] = node; } } } //walk straight up the dom updating other indexes log.debug('walking up node chain, node %s has parentNode %s', ancestor.nodeName, ancestor.parentNode); ancestor = ancestor.parentNode; } } }; __removeFromIndexes__ = function(node, ancestor){ var indexes, index, normalizedName, i, length, offset, id, name, doc; if(node.nodeType == Node.ELEMENT_NODE){ normalizedName = (node.tagName+'').toLowerCase(); //console.log('removing node from live indexes for node %s', node.tagName); id = node.getAttribute('id'); if(id){ node.ownerDocument._indexes_["#"+id] = null; } name = node.getAttribute('name'); if(name){ __removeFromNamedIndexes__('name', name, node); } while(ancestor){ indexes = ancestor._indexes_; if(!(normalizedName in indexes)){ indexes[normalizedName] = new NodeList(node.ownerDocument, ancestor); //the index did not exist on the ancestor until now so //dont bother cleaning it, just move up the ancestor chain ancestor = ancestor.parentNode; continue; } index = indexes[normalizedName]; i = Array.prototype.indexOf.apply(index, [node]); if(i>-1){ offset = node._indexes_[normalizedName]; offset = offset?offset.length:0; length = 1+offset; //console.log('removing %s[%s] from index %s -> %s', node.tagName, i, ancestor.tagName, index.toArray()); Array.prototype.splice.apply(index, [i,length]); } index = indexes['*']; i = Array.prototype.indexOf.apply(index, [node]); if(i>-1){ offset = node._indexes_['*']; offset = offset?offset.length:0; length = 1+offset; //console.log('removing %s from index * -> %s', node.tagName, index.toArray()); Array.prototype.splice.apply(index, [i,length]); } //handle input type elements and their ancestor form elements //So far we dont bother with maintaining document order for this index if('FORM' == ancestor.nodeName){ switch (node.nodeName) { case 'BUTTON': case 'FIELDSET': case 'INPUT': case 'KEYGEN': case 'OBJECT': case 'OUTPUT': case 'SELECT': case 'TEXTAREA': doc = node.ownerDocument; if(!indexes.hasOwnProperty('$elements')){ //make sure we have an index for the form.elements indexes.$elements = new NodeList(node.ownerDocument, ancestor); } offset = Array.prototype.indexOf.apply(doc._indexes_.$elements, [node]); if(index > -1){ Array.prototype.splice.apply(doc._indexes_.$elements,[offset,1]); } name = node.getAttribute('name'); if( name && ancestor[name] == node ){ //<form name='foo'><input name='bar' is no longer available via document.foo.bar delete ancestor[name]; } } } ancestor = ancestor.parentNode; } } }; /** * @method __cloneNodes__ - * Returns a NodeList containing clones of the Nodes in this NodeList * @param deep : boolean - * If true, recursively clone the subtree under each of the nodes; * if false, clone only the nodes themselves (and their attributes, * if it is an Element). * @param parentNode : Node - the new parent of the cloned NodeList * @return : NodeList - NodeList containing clones of the Nodes in this NodeList */ __cloneNodes__ = function(nodelist, deep, parentNode) { var cloneNodeList = new NodeList(nodelist.ownerDocument, parentNode); // create list containing clones of each child for (var i=0; i < nodelist.length; i++) { __appendChild__(cloneNodeList, nodelist[i].cloneNode(deep)); } return cloneNodeList; }; }(/*Envjs.DOM.Nodelist*/)); var __ownerDocument__ = function(node){ return (node.nodeType == Node.DOCUMENT_NODE)?node:node.ownerDocument; }, __recursivelyGatherText__, __isAncestor__, __escapeXML__, __unescapeXML__, __getElementsByTagNameRecursive__, __getElementsByTagNameNSRecursive__; (function(){ var log = Envjs.logger(); Envjs.once('tick', function(){ log = Envjs.logger('Envjs.DOM.Node').debug('available'); }); /** * @class Node - * The Node interface is the primary datatype for the entire * Document Object Model. It represents a single node in the * document tree. * @param ownerDocument : Document - The Document object associated with this node. */ exports.Node = Node = function(ownerDocument) { this.baseURI = 'about:blank'; this.namespaceURI = null; this.nodeName = ""; this.nodeValue = null; // A NodeList that contains all children of this node. If there are no // children, this is a NodeList containing no nodes. The content of the // returned NodeList is "live" in the sense that, for instance, changes to // the children of the node object that it was created from are immediately // reflected in the nodes returned by the NodeList accessors; it is not a // static snapshot of the content of the node. This is true for every // NodeList, including the ones returned by the getElementsByTagName method. this.childNodes = new NodeList(ownerDocument, this); // The first child of this node. If there is no such node, this is null this.firstChild = null; // The last child of this node. If there is no such node, this is null. this.lastChild = null; // The node immediately preceding this node. If there is no such node, // this is null. this.previousSibling = null; // The node immediately following this node. If there is no such node, // this is null. this.nextSibling = null; this.attributes = null; // The namespaces in scope for this node this._namespaces = new NamespaceNodeMap(ownerDocument, this); this._readonly = false; //IMPORTANT: These must come last so rhino will not iterate parent // properties before child properties. (qunit.equiv issue) // The parent of this node. All nodes, except Document, DocumentFragment, // and Attr may have a parent. However, if a node has just been created // and not yet added to the tree, or if it has been removed from the tree, // this is null this.parentNode = null; // The Document object associated with this node this.ownerDocument = ownerDocument; this._indexes_ = { 'ancestors' : new NodeList(ownerDocument, this), '*': new NodeList(ownerDocument, this) }; }; // nodeType constants Node.ELEMENT_NODE = 1; Node.ATTRIBUTE_NODE = 2; Node.TEXT_NODE = 3; Node.CDATA_SECTION_NODE = 4; Node.ENTITY_REFERENCE_NODE = 5; Node.ENTITY_NODE = 6; Node.PROCESSING_INSTRUCTION_NODE = 7; Node.COMMENT_NODE = 8; Node.DOCUMENT_NODE = 9; Node.DOCUMENT_TYPE_NODE = 10; Node.DOCUMENT_FRAGMENT_NODE = 11; Node.NOTATION_NODE = 12; Node.NAMESPACE_NODE = 13; Node.DOCUMENT_POSITION_EQUAL = 0x00; Node.DOCUMENT_POSITION_DISCONNECTED = 0x01; Node.DOCUMENT_POSITION_PRECEDING = 0x02; Node.DOCUMENT_POSITION_FOLLOWING = 0x04; Node.DOCUMENT_POSITION_CONTAINS = 0x08; Node.DOCUMENT_POSITION_CONTAINED_BY = 0x10; Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20; __extend__(Node.prototype, { get localName(){ return this.prefix? this.nodeName.substring(this.prefix.length+1, this.nodeName.length): this.nodeName; }, get prefix(){ return this.nodeName.split(':').length>1? this.nodeName.split(':')[0]: null; }, set prefix(value){ if(value === null){ this.nodeName = this.localName; }else{ this.nodeName = value+':'+this.localName; } }, hasAttributes : function() { return this.attributes.length ? true : false ; }, get textContent(){ return __recursivelyGatherText__(this); }, set textContent(newText){ log.debug('setText %s', newText); while(this.firstChild){ this.removeChild( this.firstChild ); } var text = this.ownerDocument.createTextNode(newText); this.appendChild(text); }, insertBefore : function(newChild, refChild) { log.debug('insert %s Before %s', newChild.nodeName, refChild.nodeName); var prevNode; if(!newChild){ return newChild; } if(!refChild){ this.appendChild(newChild); return this.newChild; } // test for exceptions if (__ownerDocument__(this).implementation.errorChecking) { // throw Exception if Node is readonly if (this._readonly) { throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)); } // throw Exception if newChild was not created by this Document if (__ownerDocument__(this) != __ownerDocument__(newChild)) { throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR)); } // throw Exception if the node is an ancestor if (__isAncestor__(this, newChild)) { throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR)); } } // if refChild is specified, insert before it if (refChild) { // find index of refChild var itemIndex = __findItemIndex__(this.childNodes, refChild); // throw Exception if there is no child node with this id if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) { throw(new DOMException(DOMException.NOT_FOUND_ERR)); } // if the newChild is already in the tree, var newChildParent = newChild.parentNode; if (newChildParent) { // remove it newChildParent.removeChild(newChild); } // insert newChild into childNodes __insertBefore__(this.childNodes, newChild, itemIndex); // do node pointer surgery prevNode = refChild.previousSibling; // handle DocumentFragment if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { if (newChild.childNodes.length > 0) { // set the parentNode of DocumentFragment's children for (var ind = 0; ind < newChild.childNodes.length; ind++) { newChild.childNodes[ind].parentNode = this; } // link refChild to last child of DocumentFragment refChild.previousSibling = newChild.childNodes[newChild.childNodes.length-1]; } }else { // set the parentNode of the newChild newChild.parentNode = this; // link refChild to newChild refChild.previousSibling = newChild; } }else { // otherwise, append to end prevNode = this.lastChild; this.appendChild(newChild); } if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { // do node pointer surgery for DocumentFragment if (newChild.childNodes.length > 0) { if (prevNode) { prevNode.nextSibling = newChild.childNodes[0]; }else { // this is the first child in the list this.firstChild = newChild.childNodes[0]; } newChild.childNodes[0].previousSibling = prevNode; newChild.childNodes[newChild.childNodes.length-1].nextSibling = refChild; } }else { // do node pointer surgery for newChild if (prevNode) { prevNode.nextSibling = newChild; }else { // this is the first child in the list this.firstChild = newChild; } newChild.previousSibling = prevNode; newChild.nextSibling = refChild; } return newChild; }, replaceChild : function(newChild, oldChild) { log.debug('replaceChild %s with %s', oldChild.nodeName, newChild.nodeName); var ret = null; if(!newChild || !oldChild ){ return oldChild; } // test for exceptions if (__ownerDocument__(this).implementation.errorChecking) { // throw Exception if Node is readonly if (this._readonly) { throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)); } // throw Exception if newChild was not created by this Document if (__ownerDocument__(this) != __ownerDocument__(newChild)) { throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR)); } // throw Exception if the node is an ancestor if (__isAncestor__(this, newChild)) { throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR)); } } // get index of oldChild var index = __findItemIndex__(this.childNodes, oldChild); // throw Exception if there is no child node with this id if (__ownerDocument__(this).implementation.errorChecking && (index < 0)) { throw(new DOMException(DOMException.NOT_FOUND_ERR)); } // if the newChild is already in the tree, var newChildParent = newChild.parentNode; if (newChildParent) { // remove it newChildParent.removeChild(newChild); } // add newChild to childNodes ret = __replaceChild__(this.childNodes,newChild, index); if (newChild.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { // do node pointer surgery for Document Fragment if (newChild.childNodes.length > 0) { for (var ind = 0; ind < newChild.childNodes.length; ind++) { newChild.childNodes[ind].parentNode = this; } if (oldChild.previousSibling) { oldChild.previousSibling.nextSibling = newChild.childNodes[0]; } else { this.firstChild = newChild.childNodes[0]; } if (oldChild.nextSibling) { oldChild.nextSibling.previousSibling = newChild; } else { this.lastChild = newChild.childNodes[newChild.childNodes.length-1]; } newChild.childNodes[0].previousSibling = oldChild.previousSibling; newChild.childNodes[newChild.childNodes.length-1].nextSibling = oldChild.nextSibling; } } else { // do node pointer surgery for newChild newChild.parentNode = this; if (oldChild.previousSibling) { oldChild.previousSibling.nextSibling = newChild; }else{ this.firstChild = newChild; } if (oldChild.nextSibling) { oldChild.nextSibling.previousSibling = newChild; }else{ this.lastChild = newChild; } newChild.previousSibling = oldChild.previousSibling; newChild.nextSibling = oldChild.nextSibling; } return ret; }, removeChild : function(oldChild) { log.debug('removeChild %s from %s', oldChild.nodeName, this.nodeName); if(!oldChild){ return null; } // throw Exception if NamedNodeMap is readonly if (__ownerDocument__(this).implementation.errorChecking && (this._readonly || oldChild._readonly)) { throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)); } // get index of oldChild var itemIndex = __findItemIndex__(this.childNodes, oldChild); // throw Exception if there is no child node with this id if (__ownerDocument__(this).implementation.errorChecking && (itemIndex < 0)) { throw(new DOMException(DOMException.NOT_FOUND_ERR)); } // remove oldChild from childNodes __removeChild__(this.childNodes, itemIndex); // do node pointer surgery oldChild.parentNode = null; if (oldChild.previousSibling) { oldChild.previousSibling.nextSibling = oldChild.nextSibling; }else { this.firstChild = oldChild.nextSibling; } if (oldChild.nextSibling) { oldChild.nextSibling.previousSibling = oldChild.previousSibling; }else { this.lastChild = oldChild.previousSibling; } oldChild.previousSibling = null; oldChild.nextSibling = null; return oldChild; }, appendChild : function(newChild) { log.debug('appendChild %s to %s', newChild.nodeName, this.nodeName); if(!newChild){ return null; } // test for exceptions if (__ownerDocument__(this).implementation.errorChecking) { // throw Exception if Node is readonly if (this._readonly) { throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR)); } // throw Exception if arg was not created by this Document if (__ownerDocument__(this) != __ownerDocument__(this)) { throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR)); } // throw Exception if the node is an ancestor if (__isAncestor__(this, newChild)) { throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR)); } } // if the newChild is already in the tree, var newChildParent = newChild.parentNode; if (newChildParent) { // remove it //console.debug('removing node %s', newChild); newChildParent.removeChild(newChild); } // add newChild to childNodes __appendChild__(this.childNodes, newChild); if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { // do node pointer surgery for DocumentFragment if (newChild.childNodes.length > 0) { for (var ind = 0; ind < newChild.childNodes.length; ind++) { newChild.childNodes[ind].parentNode = this; } if (this.lastChild) { this.lastChild.nextSibling = newChild.childNodes[0]; newChild.childNodes[0].previousSibling = this.lastChild; this.lastChild = newChild.childNodes[newChild.childNodes.length-1]; } else { this.lastChild = newChild.childNodes[newChild.childNodes.length-1]; this.firstChild = newChild.childNodes[0]; } } } else { // do node pointer surgery for newChild if (this.lastChild) { this.lastChild.nextSibling = newChild; newChild.previousSibling = this.lastChild; this.lastChild = newChild; } else { this.lastChild = newChild; this.firstChild = newChild; } } newChild.parentNode = this; return newChild; }, hasChildNodes : function() { return (this.childNodes.length > 0); }, cloneNode: function(deep) { log.debug('cloneNode %s', deep); // use importNode to clone this Node //do not throw any exceptions try { return __ownerDocument__(this).importNode(this, deep); } catch (e) { //there shouldn't be any exceptions, but if there are, return null // may want to warn: $debug("could not clone node: "+e.code); return null; } }, normalize : function() { log.debug('normalize'); var i; var inode; var nodesToRemove = new NodeList(); if (this.nodeType == Node.ELEMENT_NODE || this.nodeType == Node.DOCUMENT_NODE) { var adjacentTextNode = null; // loop through all childNodes for(i = 0; i < this.childNodes.length; i++) { inode = this.childNodes.item(i); if (inode.nodeType == Node.TEXT_NODE) { // this node is a text node if (inode.length < 1) { // this text node is empty // add this node to the list of nodes to be remove __appendChild__(nodesToRemove, inode); }else { if (adjacentTextNode) { // previous node was also text adjacentTextNode.appendData(inode.data); // merge the data in adjacent text nodes // add this node to the list of nodes to be removed __appendChild__(nodesToRemove, inode); } else { // remember this node for next cycle adjacentTextNode = inode; } } } else { // (soon to be) previous node is not a text node adjacentTextNode = null; // normalize non Text childNodes inode.normalize(); } } // remove redundant Text Nodes for(i = 0; i < nodesToRemove.length; i++) { inode = nodesToRemove.item(i); inode.parentNode.removeChild(inode); } } }, isSupported : function(feature, version) { // use Implementation.hasFeature to determine if this feature is supported return __ownerDocument__(this).implementation.hasFeature(feature, version); }, getElementsByTagName : function(tagname) { // delegate to _getElementsByTagNameRecursive // recurse childNodes log.debug('getElementsByTagName %s',tagname); var normalizedName = (tagname+'').toLowerCase(); if(!this._indexes_[normalizedName]){ this._indexes_[normalizedName] = new NodeList(__ownerDocument__(this)); } return this._indexes_[normalizedName]; }, getElementsByTagNameNS : function(namespaceURI, localName) { // delegate to _getElementsByTagNameNSRecursive log.debug('getElementsByTagNameNS %s %s',namespaceURI, localName); var nodelist = new NodeList(__ownerDocument__(this)); for (var i = 0; i < this.childNodes.length; i++) { __getElementsByTagNameNSRecursive__( this.childNodes.item(i), namespaceURI, localName, nodelist ); } return nodelist; }, importNode : function(importedNode, deep) { log.debug('importNode %s %s', importedNode.nodeName, deep); var i, importNode; //there is no need to perform namespace checks since everything has already gone through them //in order to have gotten into the DOM in the first place. The following line //turns namespace checking off in ._isValidNamespace __ownerDocument__(this).importing = true; if (importedNode.nodeType == Node.ELEMENT_NODE) { if (!__ownerDocument__(this).implementation.namespaceAware) { // create a local Element (with the name of the importedNode) importNode = __ownerDocument__(this).createElement(importedNode.tagName); // create attributes matching those of the importedNode for(i = 0; i < importedNode.attributes.length; i++) { importNode.setAttribute(importedNode.attributes.item(i).name, importedNode.attributes.item(i).value); } } else { // create a local Element (with the name & namespaceURI of the importedNode) importNode = __ownerDocument__(this).createElementNS(importedNode.namespaceURI, importedNode.nodeName); // create attributes matching those of the importedNode for(i = 0; i < importedNode.attributes.length; i++) { importNode.setAttributeNS(importedNode.attributes.item(i).namespaceURI, importedNode.attributes.item(i).name, importedNode.attributes.item(i).value); } // create namespace definitions matching those of the importedNode for(i = 0; i < importedNode._namespaces.length; i++) { importNode._namespaces[i] = __ownerDocument__(this).createNamespace(importedNode._namespaces.item(i).localName); importNode._namespaces[i].value = importedNode._namespaces.item(i).value; } } } else if (importedNode.nodeType == Node.ATTRIBUTE_NODE) { if (!__ownerDocument__(this).implementation.namespaceAware) { // create a local Attribute (with the name of the importedAttribute) importNode = __ownerDocument__(this).createAttribute(importedNode.name); } else { // create a local Attribute (with the name & namespaceURI of the importedAttribute) importNode = __ownerDocument__(this).createAttributeNS(importedNode.namespaceURI, importedNode.nodeName); // create namespace definitions matching those of the importedAttribute for(i = 0; i < importedNode._namespaces.length; i++) { importNode._namespaces[i] = __ownerDocument__(this).createNamespace(importedNode._namespaces.item(i).localName); importNode._namespaces[i].value = importedNode._namespaces.item(i).value; } } // set the value of the local Attribute to match that of the importedAttribute importNode.value = importedNode.value; } else if (importedNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { // create a local DocumentFragment importNode = __ownerDocument__(this).createDocumentFragment(); } else if (importedNode.nodeType == Node.NAMESPACE_NODE) { // create a local NamespaceNode (with the same name & value as the importedNode) importNode = __ownerDocument__(this).createNamespace(importedNode.nodeName); importNode.value = importedNode.value; } else if (importedNode.nodeType == Node.TEXT_NODE) { // create a local TextNode (with the same data as the importedNode) importNode = __ownerDocument__(this).createTextNode(importedNode.data); } else if (importedNode.nodeType == Node.CDATA_SECTION_NODE) { // create a local CDATANode (with the same data as the importedNode) importNode = __ownerDocument__(this).createCDATASection(importedNode.data); } else if (importedNode.nodeType == Node.PROCESSING_INSTRUCTION_NODE) { // create a local ProcessingInstruction (with the same target & data as the importedNode) importNode = __ownerDocument__(this).createProcessingInstruction(importedNode.target, importedNode.data); } else if (importedNode.nodeType == Node.COMMENT_NODE) { // create a local Comment (with the same data as the importedNode) importNode = __ownerDocument__(this).createComment(importedNode.data); } else { // throw Exception if nodeType is not supported throw(new DOMException(DOMException.NOT_SUPPORTED_ERR)); } if (deep) { // recurse childNodes for(i = 0; i < importedNode.childNodes.length; i++) { importNode.appendChild(__ownerDocument__(this).importNode(importedNode.childNodes.item(i), true)); } } //reset importing __ownerDocument__(this).importing = false; return importNode; }, contains : function(node){ log.debug("this %s contains %s ?", this.nodeName, node.nodeName); while(node && node != this ){ node = node.parentNode; } return !!node; }, compareDocumentPosition : function(b){ log.debug("compareDocumentPosition of this %s to %s", this.nodeName, b.nodeName); var i, length, a = this, parent, aparents, bparents; //handle a couple simpler case first if(a === b) { return Node.DOCUMENT_POSITION_EQUAL; } if(a.ownerDocument !== b.ownerDocument) { return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC| Node.DOCUMENT_POSITION_FOLLOWING| Node.DOCUMENT_POSITION_DISCONNECTED; } if(a.parentNode === b.parentNode){ length = a.parentNode.childNodes.length; for(i=0;i<length;i++){ if(a.parentNode.childNodes[i] === a){ return Node.DOCUMENT_POSITION_FOLLOWING; }else if(a.parentNode.childNodes[i] === b){ return Node.DOCUMENT_POSITION_PRECEDING; } } } if(a.contains(b)) { return Node.DOCUMENT_POSITION_CONTAINED_BY| Node.DOCUMENT_POSITION_FOLLOWING; } if(b.contains(a)) { return Node.DOCUMENT_POSITION_CONTAINS| Node.DOCUMENT_POSITION_PRECEDING; } aparents = []; parent = a.parentNode; while(parent){ aparents[aparents.length] = parent; parent = parent.parentNode; } bparents = []; parent = b.parentNode; while(parent){ i = aparents.indexOf(parent); if(i < 0){ bparents[bparents.length] = parent; parent = parent.parentNode; }else{ //i cant be 0 since we already checked for equal parentNode if(bparents.length > aparents.length){ return Node.DOCUMENT_POSITION_FOLLOWING; }else if(bparents.length < aparents.length){ return Node.DOCUMENT_POSITION_PRECEDING; }else{ //common ancestor diverge point if (i === 0) { return Node.DOCUMENT_POSITION_FOLLOWING; } else { parent = aparents[i-1]; } return parent.compareDocumentPosition(bparents.pop()); } } } return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC| Node.DOCUMENT_POSITION_DISCONNECTED; }, toString : function() { return '[object Node]'; } }); }(/*Envjs.DOM.Node*/)); /** * @method __getElementsByTagNameRecursive__ - implements getElementsByTagName() * @param elem : Element - The element which are checking and then recursing into * @param tagname : string - The name of the tag to match on. The special value "*" matches all tags * @param nodeList : NodeList - The accumulating list of matching nodes * * @return : NodeList */ __getElementsByTagNameRecursive__ = function (elem, tagname, nodeList) { if (elem.nodeType == Node.ELEMENT_NODE || elem.nodeType == Node.DOCUMENT_NODE) { if(elem.nodeType !== Node.DOCUMENT_NODE && ((elem.nodeName.toUpperCase() == tagname.toUpperCase()) || (tagname == "*")) ){ // add matching node to nodeList __appendChild__(nodeList, elem); } // recurse childNodes for(var i = 0; i < elem.childNodes.length; i++) { nodeList = __getElementsByTagNameRecursive__(elem.childNodes.item(i), tagname, nodeList); } } return nodeList; }; /** * @method __getElementsByTagNameNSRecursive__ * implements getElementsByTagName() * * @param elem : Element - The element which are checking and then recursing into * @param namespaceURI : string - the namespace URI of the required node * @param localName : string - the local name of the required node * @param nodeList : NodeList - The accumulating list of matching nodes * * @return : NodeList */ __getElementsByTagNameNSRecursive__ = function(elem, namespaceURI, localName, nodeList) { if (elem.nodeType == Node.ELEMENT_NODE || elem.nodeType == Node.DOCUMENT_NODE) { if (((elem.namespaceURI == namespaceURI) || (namespaceURI == "*")) && ((elem.localName == localName) || (localName == "*"))) { // add matching node to nodeList __appendChild__(nodeList, elem); } // recurse childNodes for(var i = 0; i < elem.childNodes.length; i++) { nodeList = __getElementsByTagNameNSRecursive__( elem.childNodes.item(i), namespaceURI, localName, nodeList); } } return nodeList; }; /** * @method __isAncestor__ - returns true if node is ancestor of target * @param target : Node - The node we are using as context * @param node : Node - The candidate ancestor node * @return : boolean */ __isAncestor__ = function(target, node) { // if this node matches, return true, // otherwise recurse up (if there is a parentNode) return ((target == node) || ((target.parentNode) && (__isAncestor__(target.parentNode, node)))); }; __recursivelyGatherText__ = function(aNode) { var accumulateText = "", idx, node; for (idx=0;idx < aNode.childNodes.length;idx++){ node = aNode.childNodes.item(idx); if(node.nodeType == Node.TEXT_NODE){ accumulateText += node.data; }else{ accumulateText += __recursivelyGatherText__(node); } } return accumulateText; }; /** * function __escapeXML__ * @param str : string - The string to be escaped * @return : string - The escaped string */ var escAmpRegEx = /&(?!(amp;|lt;|gt;|quot|apos;))/g; var escLtRegEx = /</g; var escGtRegEx = />/g; var quotRegEx = /"/g; var aposRegEx = /'/g; __escapeXML__ = function(str) { str = str.replace(escAmpRegEx, "&amp;"). replace(escLtRegEx, "&lt;"). replace(escGtRegEx, "&gt;"). replace(quotRegEx, "&quot;"). replace(aposRegEx, "&apos;"); return str; }; /** * function __unescapeXML__ * @param str : string - The string to be unescaped * @return : string - The unescaped string */ var unescAmpRegEx = /&amp;/g; var unescLtRegEx = /&lt;/g; var unescGtRegEx = /&gt;/g; var unquotRegEx = /&quot;/g; var unaposRegEx = /&apos;/g; __unescapeXML__ = function(str) { str = str.replace(unescAmpRegEx, "&"). replace(unescLtRegEx, "<"). replace(unescGtRegEx, ">"). replace(unquotRegEx, "\""). replace(unaposRegEx, "'"); return str; }; var __findNamedItemIndex__, __findNamedItemNSIndex__, __hasAttribute__, __hasAttributeNS__, __cloneNamedNodes__; //see nodelist for these declarations /*var __addToNamedIndexes__, __removeFromNamedIndexes__;*/ (function(){ var log = Envjs.logger(); Envjs.once('tick', function(){ log = Envjs.logger('Envjs.DOM.NamedNodeMap').debug('available'); }); /** * @class NamedNodeMap - * used to represent collections of nodes that can be accessed by name * typically a set of Element attributes * * @extends NodeList - * note W3C spec says that this is not the case, but we need an item() * method identical to NodeList's, so why not? * @param ownerDocument : Document - the ownerDocument * @param parentNode : Node - the node that the NamedNodeMap is attached to (or null) */ exports.NamedNodeMap = NamedNodeMap = function(ownerDocument, parentNode) { NodeList.apply(this, arguments); }; NamedNodeMap.prototype = new NodeList(); __extend__(NamedNodeMap.prototype, { add: function(name) { this[this.length] = name; }, getNamedItem: function(name) { var ret = null; log.debug('getNamedItem %s', name); // test that Named Node exists var itemIndex = __findNamedItemIndex__(this, name);