d3
Version:
A small, free JavaScript library for manipulating documents based on data.
1,389 lines (1,236 loc) • 273 kB
JavaScript
/*
* 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, "&").
replace(escLtRegEx, "<").
replace(escGtRegEx, ">").
replace(quotRegEx, """).
replace(aposRegEx, "'");
return str;
};
/**
* function __unescapeXML__
* @param str : string - The string to be unescaped
* @return : string - The unescaped string
*/
var unescAmpRegEx = /&/g;
var unescLtRegEx = /</g;
var unescGtRegEx = />/g;
var unquotRegEx = /"/g;
var unaposRegEx = /'/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);