diff --git a/src/dom/RangeImpl.cpp b/src/dom/RangeImpl.cpp index 4cf756e7d039968731ba0171c4a9b48da5e0a2a1..50150ee795cbb3027d0de57fb14ee35b263f5bc9 100644 --- a/src/dom/RangeImpl.cpp +++ b/src/dom/RangeImpl.cpp @@ -61,6 +61,7 @@ #include <util/RefVectorOf.hpp> #include "NodeImpl.hpp" #include "RangeImpl.hpp" +#include "TextImpl.hpp" #include "DocumentImpl.hpp" #include "DOM_DOMException.hpp" #include "DOM_Document.hpp" @@ -164,27 +165,52 @@ bool RangeImpl::getCollapsed() const void RangeImpl::setStartContainer(const DOM_Node& node) { + if (fDetached) + { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + fStartContainer = node; } void RangeImpl::setStartOffset(unsigned int offset) { + if (fDetached) + { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + fStartOffset = offset; } void RangeImpl::setEndContainer(const DOM_Node& node) { + if (fDetached) + { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + fEndContainer = node; } void RangeImpl::setEndOffset(unsigned int offset) { + if (fDetached) + { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + fEndOffset = offset; } void RangeImpl::setStart(const DOM_Node& refNode, unsigned int offset) { + validateNode(refNode); checkIndex(refNode, offset); fStartContainer = refNode; @@ -207,6 +233,7 @@ void RangeImpl::setStart(const DOM_Node& refNode, unsigned int offset) void RangeImpl::setEnd(const DOM_Node& refNode, unsigned int offset) { + validateNode(refNode); checkIndex(refNode, offset); fEndContainer = refNode; @@ -229,7 +256,14 @@ void RangeImpl::setEnd(const DOM_Node& refNode, unsigned int offset) void RangeImpl::setStartBefore(const DOM_Node& refNode) { - validateNode(refNode); + if( fDetached) { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { + throw DOM_RangeException( + DOM_RangeException::INVALID_NODE_TYPE_ERR, null); + } fStartContainer = refNode.getParentNode(); unsigned int i = 0; @@ -258,7 +292,14 @@ void RangeImpl::setStartBefore(const DOM_Node& refNode) void RangeImpl::setStartAfter(const DOM_Node& refNode) { - validateNode(refNode); + if( fDetached) { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { + throw DOM_RangeException( + DOM_RangeException::INVALID_NODE_TYPE_ERR, null); + } fStartContainer = refNode.getParentNode(); unsigned int i = 0; @@ -285,7 +326,14 @@ void RangeImpl::setStartAfter(const DOM_Node& refNode) void RangeImpl::setEndBefore(const DOM_Node& refNode) { - validateNode(refNode); + if( fDetached) { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { + throw DOM_RangeException( + DOM_RangeException::INVALID_NODE_TYPE_ERR, null); + } fEndContainer = refNode.getParentNode(); unsigned int i = 0; @@ -313,7 +361,14 @@ void RangeImpl::setEndBefore(const DOM_Node& refNode) void RangeImpl::setEndAfter(const DOM_Node& refNode) { - validateNode(refNode); + if( fDetached) { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { + throw DOM_RangeException( + DOM_RangeException::INVALID_NODE_TYPE_ERR, null); + } fEndContainer = refNode.getParentNode(); unsigned int i = 0; @@ -343,6 +398,11 @@ void RangeImpl::setEndAfter(const DOM_Node& refNode) //------------------------------- void RangeImpl::detach() { + if( fDetached) { + throw DOM_DOMException( + DOM_DOMException::INVALID_STATE_ERR, null); + } + fDetached = true; //nullify nodes @@ -375,18 +435,12 @@ void RangeImpl::collapse(bool toStart) void RangeImpl::selectNode(const DOM_Node& refNode) { validateNode(refNode); - short type = refNode.getNodeType(); - if (type == DOM_Node::ATTRIBUTE_NODE - || type == DOM_Node::ENTITY_NODE - || type == DOM_Node::NOTATION_NODE - || type == DOM_Node::DOCUMENT_TYPE_NODE - || type == DOM_Node::DOCUMENT_FRAGMENT_NODE) { - + if ( !isLegalContainedNode(refNode)) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); } //First check for the text type node - if (type == DOM_Node::TEXT_NODE) + if (refNode.getNodeType() == DOM_Node::TEXT_NODE) { //The node itself is the container. fStartContainer = refNode; @@ -417,14 +471,6 @@ void RangeImpl::selectNode(const DOM_Node& refNode) void RangeImpl::selectNodeContents(const DOM_Node& node) { validateNode(node); - short type = node.getNodeType(); - if (type == DOM_Node::ENTITY_NODE - || type == DOM_Node::NOTATION_NODE - || type == DOM_Node::DOCUMENT_TYPE_NODE) { - - throw DOM_RangeException( - DOM_RangeException::INVALID_NODE_TYPE_ERR, null); - } fStartContainer = node; fEndContainer = node; @@ -463,12 +509,8 @@ void RangeImpl::surroundContents(DOM_Node& newParent) } int type = newParent.getNodeType(); - if (type == DOM_Node::ATTRIBUTE_NODE - || type == DOM_Node::ENTITY_NODE - || type == DOM_Node::NOTATION_NODE - || type == DOM_Node::DOCUMENT_TYPE_NODE - || type == DOM_Node::DOCUMENT_NODE - || type == DOM_Node::DOCUMENT_FRAGMENT_NODE) + if ( !isLegalContainedNode(newParent) + || type == DOM_Node::DOCUMENT_TYPE_NODE) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); @@ -580,190 +622,12 @@ short RangeImpl::compareBoundaryPoints(DOM_Range::CompareHow how, RangeImpl* src void RangeImpl:: deleteContents() { - if ((fStartContainer == null) || (fEndContainer == null) ) return; - checkReadOnly(fStartContainer, fEndContainer, fStartOffset, fEndOffset); - - if( fDetached) { - throw DOM_DOMException( - DOM_DOMException::INVALID_STATE_ERR, null); - } - - DOM_Node current = fStartContainer; - - // if same container, simplify case - if (fStartContainer == fEndContainer) { - if (fStartOffset == fEndOffset) { // already collapsed - return; - } - if (fStartOffset > fEndOffset) - throw DOM_RangeException(DOM_RangeException::BAD_BOUNDARYPOINTS_ERR, null); - - if (fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { - DOMString value = fStartContainer.getNodeValue(); - unsigned int realStart = fStartOffset; - unsigned int realEnd = fEndOffset; - - if (fStartOffset > value.length()) realStart = value.length()-1; - if (fEndOffset > value.length()) realEnd = value.length()-1; - - ((DOM_Text &)fStartContainer).deleteData(realStart, realEnd-realStart); - - } else { - current = fStartContainer.getFirstChild(); - unsigned int i = 0; - //move till the start offset - for(i = 0; i < fStartOffset && current != null; i++) { - current=current.getNextSibling(); - } - - //delete all children after the start offset to end offset - for(i = 0; i < (fEndOffset-fStartOffset) && (current != null); i++) { - DOM_Node newCurrent = current.getNextSibling(); - removeChild(fStartContainer, current); - current = newCurrent; - } - } - collapse(true); - return; - } - - //The case of partial selections and when start container is not the same as end one - bool deleteCurrent = false; - - // initialize current for startContainer. - if (current.getNodeType() == DOM_Node::TEXT_NODE) { - ((DOM_Text &)current).deleteData(fStartOffset, current.getNodeValue().length()-fStartOffset); - } else { - current = current.getFirstChild(); - for (unsigned int i = 0 ; i < fStartOffset && current != null; i++){ - current = current.getNextSibling(); - } - if (current == null) { - current = fStartContainer; - } else if (current != fStartContainer) - deleteCurrent = true; - } - - DOM_Node parent = null; - DOM_Node next; - DOM_Node partialNode = null; - int partialInt = START; - DOM_Node startRoot = null; - - DOM_Node root = getCommonAncestorContainer(); - // traverse up from the startContainer... - // current starts as the node to delete; - while (current != root && current != null) { - - parent = current.getParentNode(); - if (parent == root) { - if (startRoot == null) - startRoot = current; - } else { - if (partialNode == null) { - partialNode = parent; - partialInt = AFTER; - } - } - - if (parent != root) { - next = current.getNextSibling(); - DOM_Node nextnext; - while (next != null) { - nextnext = next.getNextSibling(); - removeChild(parent, next); - next = nextnext; - } - } - - if (deleteCurrent) { - removeChild(parent, current); - deleteCurrent = false; - } - current = parent; - } - - DOM_Node endRoot = null; - // initialize current for endContainer. - current = fEndContainer; - if (current.getNodeType() == DOM_Node::TEXT_NODE) { - ((DOM_Text &)current).deleteData(0, fEndOffset); - } else { - - if (fEndOffset == 0) { // "before" - current = fEndContainer; - } - else { - current = current.getFirstChild(); - for(unsigned int i = 1; i < fEndOffset && current != null; i++) { - current=current.getNextSibling(); - } - if (current==null) { // REVIST: index-out-of-range what to do? - current = fEndContainer.getLastChild(); - } else - if (current != fStartContainer) { - deleteCurrent = true; - } - - } - } - - // traverse up from the endContainer... - while (current != root && current != null) { - - parent = current.getParentNode(); - if (parent == root) { - if (endRoot == null) - endRoot = current; - } else { - if (partialNode==null) { - partialNode = parent; - partialInt = BEFORE; - } - } - - if (parent != root && parent != null) { - next = current.getPreviousSibling(); - DOM_Node nextnext; - while (next != null) { - nextnext = next.getPreviousSibling(); - removeChild(parent, next); - next = nextnext; - } - } - - if (deleteCurrent) { - removeChild(parent, current); - deleteCurrent = false; - } - current = parent; - } - - //if (endRoot == null || startRoot == null) return; //REVIST - current = endRoot.getPreviousSibling(); - DOM_Node prev = null; - while (current != null && current != startRoot ) { - prev = current.getPreviousSibling(); - parent = current.getParentNode(); - if (parent != null) { - removeChild(parent, current); - } - current = prev; - } - - if (partialNode == null) { - collapse(true); - } else if (partialInt == AFTER) { - setStartAfter(partialNode); - setEndAfter(partialNode); - } else if (partialInt == BEFORE) { - setStartBefore(partialNode); - setEndBefore(partialNode); - } + traverseContents(DELETE_CONTENTS); } DOM_DocumentFragment RangeImpl::extractContents() { + checkReadOnly(fStartContainer, fEndContainer, fStartOffset, fEndOffset); return traverseContents(EXTRACT_CONTENTS); } @@ -778,10 +642,12 @@ void RangeImpl::insertNode(DOM_Node& newNode) { if (newNode == null) return; //don't have to do anything - if (fStartContainer.getParentNode().fImpl->isReadOnly()) { + for (DOM_Node aNode = fStartContainer; aNode!=null; aNode = aNode.getParentNode()) { + if (aNode.fImpl->isReadOnly()) { throw DOM_DOMException( DOM_DOMException::NO_MODIFICATION_ALLOWED_ERR, null); } + } if (fDocument != newNode.getOwnerDocument()) { throw DOM_DOMException( @@ -804,8 +670,7 @@ void RangeImpl::insertNode(DOM_Node& newNode) if (type == DOM_Node::ATTRIBUTE_NODE || type == DOM_Node::ENTITY_NODE || type == DOM_Node::NOTATION_NODE - || type == DOM_Node::DOCUMENT_NODE - || type == DOM_Node::DOCUMENT_FRAGMENT_NODE) + || type == DOM_Node::DOCUMENT_NODE) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); @@ -870,43 +735,52 @@ DOMString RangeImpl::toString() const } DOM_Node node = fStartContainer; + DOM_Node stopNode = fEndContainer; DOMString tempString; if ( (fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) || (fStartContainer.getNodeType() == DOM_Node::CDATA_SECTION_NODE) ) { if (fStartContainer == fEndContainer) { - //the substringData returns a substring from start to end all inclusive - //we want a substring begins at fStartOffset inclusive, but ends at fEndOffset exclusive - tempString.appendData(fStartContainer.getNodeValue().substringData(fStartOffset, fEndOffset-1)); + tempString.appendData(fStartContainer.getNodeValue().substringData(fStartOffset, fEndOffset-fStartOffset)); return tempString; } else { int length = fStartContainer.getNodeValue().length(); tempString.appendData(fStartContainer.getNodeValue().substringData(fStartOffset, length - fStartOffset)); + node = nextNode(node, true); } - }else if (node == fEndContainer){ - DOM_Node anode = node.getFirstChild(); - unsigned int i = 0; - for ( ;i<fStartOffset; i++) - anode = anode.getNextSibling(); - for( ; ( i<fEndOffset || anode!=null); anode = anode.getNextSibling(), i++) { - if( (anode.getNodeType() == DOM_Node::TEXT_NODE) - || (anode.getNodeType() == DOM_Node::CDATA_SECTION_NODE)) { - tempString.appendData(anode.getNodeValue()); + }else { //fStartContainer is not a TextNode + node=node.getFirstChild(); + if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset + unsigned int counter = 0; + while (counter<fStartOffset && node!=null) { + node=node.getNextSibling(); + counter++; } } - return tempString; + if (node == null) { + node = nextNode(fStartContainer,false); + } } - DOM_Node root = getCommonAncestorContainer(); - while (node != fEndContainer) { - node = nextNode(node, true); - if ((node == null) || (node == fEndContainer)) break; + if ( fEndContainer.getNodeType()!= DOM_Node.TEXT_NODE && + fEndContainer.getNodeType()!= DOM_Node.CDATA_SECTION_NODE ){ + int i=fEndOffset; + stopNode = fEndContainer.getFirstChild(); + while( i>0 && stopNode!=null ){ + --i; + stopNode = stopNode.getNextSibling(); + } + if ( stopNode == null ) + stopNode = nextNode( fEndContainer, false ); + } - if (node.getNodeType() == DOM_Node::TEXT_NODE - || node.getNodeType() == DOM_Node::CDATA_SECTION_NODE - ) { + while (node != stopNode) { //look into all kids of the Range + if (node == null) break; + if (node.getNodeType() == DOM_Node.TEXT_NODE + || node.getNodeType() == DOM_Node.CDATA_SECTION_NODE) { tempString.appendData(node.getNodeValue()); } + node = nextNode(node, true); } if (fEndContainer.getNodeType() == DOM_Node::TEXT_NODE @@ -950,6 +824,36 @@ bool RangeImpl::isAncestorOf(const DOM_Node& a, const DOM_Node& b) { return false; } +bool RangeImpl::hasLegalRootContainer(const DOM_Node& node) const { + if ( node==null ) + return false; + + DOM_Node rootContainer = node; + for (; rootContainer.getParentNode()!=null; rootContainer = rootContainer.getParentNode()); + switch( rootContainer.getNodeType() ) { + case DOM_Node::ATTRIBUTE_NODE: + case DOM_Node::DOCUMENT_NODE: + case DOM_Node::DOCUMENT_FRAGMENT_NODE: + return true; + } + return false; +} + +bool RangeImpl::isLegalContainedNode(const DOM_Node& node ) const { + if ( node==null ) + return false; + switch( node.getNodeType() ) + { + case DOM_Node::DOCUMENT_NODE: + case DOM_Node::DOCUMENT_FRAGMENT_NODE: + case DOM_Node::ATTRIBUTE_NODE: + case DOM_Node::ENTITY_NODE: + case DOM_Node::NOTATION_NODE: + return false; + } + return true; +} + unsigned short RangeImpl::indexOf(const DOM_Node& child, const DOM_Node& parent) const { unsigned short i = 0; @@ -1022,8 +926,6 @@ const DOM_Node RangeImpl::commonAncestorOf(const DOM_Node& pointA, const DOM_Nod void RangeImpl::checkIndex(const DOM_Node& node, unsigned int offset) const { - validateNode(node); - if (offset < 0) { throw DOM_DOMException( DOM_DOMException::INDEX_SIZE_ERR, null ); } @@ -1091,12 +993,12 @@ DOM_Node RangeImpl::nextNode(const DOM_Node& node, bool visitChildren) const } -/** This is the master traversal function which is used by -* both extractContents and cloneContents(). +/** This is the master routine invoked to visit the nodes +* selected by this range. For each such node, different +* actions are taken depending on the value of the TraversalType argument. */ -DOM_DocumentFragment RangeImpl::traverseContents(TraversalType trvType) +DOM_DocumentFragment RangeImpl::traverseContents(TraversalType how) { - if (fDetached) throw DOM_DOMException(DOM_DOMException::INVALID_STATE_ERR, null); @@ -1104,285 +1006,500 @@ DOM_DocumentFragment RangeImpl::traverseContents(TraversalType trvType) return DOM_DocumentFragment(); // REVIST: Throw exception? } - DOM_DocumentFragment frag = fDocument.createDocumentFragment() ; + /* Traversal is accomplished by first determining the + relationship between the endpoints of the range. + For each of four significant relationships, we will + delegate the traversal call to a method that + can make appropriate assumptions. + */ - DOM_Node current = fStartContainer; - DOM_Node cloneCurrent = null; - DOM_Node cloneParent = null; - DOM_Node partialNode = null; - int partialInt = START; - - // if same container, simplify case - if (fStartContainer == fEndContainer) { - if (fStartOffset == fEndOffset) { // eg collapsed - return frag; // REVIST: what is correct re spec? - } - if (fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { - cloneCurrent = fStartContainer.cloneNode(false); - cloneCurrent.setNodeValue( - cloneCurrent.getNodeValue().substringData(fStartOffset, fEndOffset)); - if (trvType == EXTRACT_CONTENTS) { - ((DOM_Text &)current).deleteData(fStartOffset, fEndOffset-fStartOffset); - } - frag.appendChild(cloneCurrent); - } else { - current = current.getFirstChild(); - unsigned int i = 0; - for(i = 0; i < fStartOffset && current != null; i++) { - current=current.getNextSibling(); - } - unsigned int n = fEndOffset-fStartOffset; - for(i = 0; i < n && current != null ;i++) { - DOM_Node newCurrent=current.getNextSibling(); + // case 1: same container + if ( fStartContainer == fEndContainer ) + return traverseSameContainer( how ); - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - frag.appendChild(cloneCurrent); - } else - if (trvType == EXTRACT_CONTENTS) { - frag.appendChild(current); - } - current = newCurrent; - } - } - if (trvType == EXTRACT_CONTENTS ) { - collapse(true); - } - return frag; + // case 2: Child C of start container is ancestor of end container + for (DOM_Node node = fStartContainer.getFirstChild(); node != null; node=node.getNextSibling()) { + if (isAncestorOf(node, fEndContainer)) + return traverseCommonStartContainer( node, how ); } - //***** END SIMPLE CASE **** - - - DOM_Node root = getCommonAncestorContainer(); - DOM_Node parent = null; - // go up the start tree... - current = fStartContainer; - - bool endAtRoot = false; - - //REVIST: Always clone TEXT_NODE's? - if (current.getNodeType() == DOM_Node::TEXT_NODE) { - cloneCurrent = current.cloneNode(false); - cloneCurrent.setNodeValue( - ((DOM_Text&)cloneCurrent).getNodeValue().substringData(fStartOffset, current.getNodeValue().length() - fStartOffset)); - if (trvType == EXTRACT_CONTENTS) { - ((DOM_Text&)current).deleteData(fStartOffset, current.getNodeValue().length()-fStartOffset); - } - } else { - current = current.getFirstChild(); - for(unsigned int i = 0; i < fStartOffset && current != null; i++) { - current=current.getNextSibling(); - } - // current is now at the offset. - if (current==null) { //"after" - current = fStartContainer; + // case 3: Child C of end container is ancestor of start container + for (DOM_Node nd = fEndContainer.getFirstChild(); nd != null; nd=nd.getNextSibling()) { + if (isAncestorOf(nd, fStartContainer)) + return traverseCommonEndContainer( nd, how ); } - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - } else if (trvType == EXTRACT_CONTENTS ) { - cloneCurrent = current; - } + // case 4: preorder traversal of context tree. + // There is a common ancestor container. Find the + // ancestor siblings that are children of that container. + DOM_Node ancestor = commonAncestorOf(fStartContainer, fEndContainer); + return traverseCommonAncestors( ancestor, ancestor, how ); } - DOM_Node startRoot = null; - DOM_Node endRoot = null; - parent = null; - - - if (root == fEndContainer) { - if (fStartContainer.getParentNode() == fEndContainer) { - //a unique situation when start and end are partial under the same pass - DOM_Node endNode = fEndContainer.getFirstChild(); - for (unsigned int i = 0; - i <= fEndOffset-2; - i++, endNode = endNode.getNextSibling()) ; - - if (cloneParent == null) - cloneParent = root.cloneNode(false); - - cloneParent.appendChild(cloneCurrent); //clone the node from above +/** + * Visits the nodes selected by this range when we know + * a-priori that the start and end containers are the same. + * + */ +DOM_DocumentFragment RangeImpl::traverseSameContainer( int how ) +{ + DOM_DocumentFragment frag = null; + if ( how!=DELETE_CONTENTS) + frag = fDocument.createDocumentFragment(); - for (current= current.getNextSibling(); - current != null, current != endNode.getNextSibling(); - current=current.getNextSibling()) { - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - cloneParent.appendChild(cloneCurrent); - } else if (trvType == EXTRACT_CONTENTS) { - cloneParent.appendChild(current); - } - } - if (trvType == EXTRACT_CONTENTS) { - collapse(true); - } - frag.appendChild(cloneParent); + // If selection is empty, just return the fragment + if ( fStartOffset==fEndOffset ) return frag; - } - } - - // going up in a direct line from boundary point - // through parents to the common ancestor, - // all these nodes are partially selected, and must - // be cloned. - while (current != root) { - parent = current.getParentNode(); - - if (parent == root) { - cloneParent = frag; - startRoot = current; - } else { - //check if (parent == null) case too - cloneParent = parent.cloneNode(false); - if (partialNode==null && parent != root) { - partialNode = parent; - partialInt = AFTER; - } - } - - // The children to the "right" of the "ancestor hierarchy" - // are "fully-selected". - DOM_Node next = null; - - //increment to the next sibling BEFORE doing the appendChild - current = current.getNextSibling(); - //do this appendChild after the increment above. - cloneParent.appendChild(cloneCurrent); + DOM_Node current = fStartContainer; + DOM_Node cloneCurrent = null; + + // Text node needs special case handling + if ( fStartContainer.getNodeType()== DOM_Node::TEXT_NODE ) + { + cloneCurrent = fStartContainer.cloneNode(false); + cloneCurrent.setNodeValue( + cloneCurrent.getNodeValue().substringData(fStartOffset, fEndOffset)); - while (current != null) { - next = current.getNextSibling(); - - if (current != null && parent != root) { - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - cloneParent.appendChild(cloneCurrent); - } else - if (trvType == EXTRACT_CONTENTS) { - cloneParent.appendChild(current); - } + // set the original text node to its new value + if ( how != CLONE_CONTENTS ) + ((DOM_Text &)fStartContainer).deleteData(fStartOffset, fEndOffset-fStartOffset); + if ( how != DELETE_CONTENTS) + frag.appendChild(cloneCurrent); + } + else { + // Copy nodes between the start/end offsets. + DOM_Node n = getSelectedNode( fStartContainer, fStartOffset ); + int cnt = fEndOffset - fStartOffset; + while( cnt > 0 ) + { + DOM_Node sibling = n.getNextSibling(); + DOM_Node xferNode = traverseFullySelected( n, how ); + if ( frag!=null ) + frag.appendChild( xferNode ); + --cnt; + n = sibling; } - current = next; - } - - current = parent; - cloneCurrent = cloneParent; } - // go up the end tree... - current = fEndContainer; - - if (current.getNodeType() == DOM_Node::TEXT_NODE) { - cloneCurrent = current.cloneNode(false); - cloneCurrent.setNodeValue( - (cloneCurrent.getNodeValue()).substringData(0,fEndOffset)); - if (trvType == EXTRACT_CONTENTS) { - ((DOM_Text&)current).deleteData(0, fEndOffset); - } - } else { - if (fEndOffset == 0) { // "before" - current = fEndContainer; + // Nothing is partially selected, so collapse to start point + if ( how != CLONE_CONTENTS ) + collapse(true); + return frag; +} + +/** + * Visits the nodes selected by this range when we know + * a-priori that the start and end containers are not the + * same, but the start container is an ancestor of the end container + * + */ +DOM_DocumentFragment RangeImpl::traverseCommonStartContainer( DOM_Node endAncestor, int how ) +{ + DOM_DocumentFragment frag = null; + if ( how!=DELETE_CONTENTS) + frag = fDocument.createDocumentFragment(); + DOM_Node n = traverseRightBoundary( endAncestor, how ); + if ( frag!=null ) + frag.appendChild( n ); + + int endIdx = indexOf( endAncestor, fStartContainer ); + int cnt = endIdx - fStartOffset; + if ( cnt <=0 ) + { + // Collapse to just before the endAncestor, which + // is partially selected. + if ( how != CLONE_CONTENTS ) + { + setEndBefore( endAncestor ); + collapse( false ); } - else { - current = current.getFirstChild(); - for(unsigned int i = 1; i < fEndOffset && current != null; i++) { - current=current.getNextSibling(); - } - if (current==null) { // REVIST: index-out-of-range what to do? - current = fEndContainer.getLastChild(); + return frag; + } + + n = endAncestor.getPreviousSibling(); + while( cnt > 0 ) + { + DOM_Node sibling = n.getPreviousSibling(); + DOM_Node xferNode = traverseFullySelected( n, how ); + if ( frag!=null ) + frag.insertBefore( xferNode, frag.getFirstChild() ); + --cnt; + n = sibling; + } + // Collapse to just before the endAncestor, which + // is partially selected. + if ( how != CLONE_CONTENTS ) + { + setEndBefore( endAncestor ); + collapse( false ); + } + return frag; +} + +/** + * Visits the nodes selected by this range when we know + * a-priori that the start and end containers are not the + * same, but the end container is an ancestor of the start container + * + */ +DOM_DocumentFragment RangeImpl::traverseCommonEndContainer( DOM_Node startAncestor, int how ) +{ + DOM_DocumentFragment frag = null; + if ( how!=DELETE_CONTENTS) + frag = fDocument.createDocumentFragment(); + DOM_Node n = traverseLeftBoundary( startAncestor, how ); + if ( frag!=null ) + frag.appendChild( n ); + int startIdx = indexOf( startAncestor, fEndContainer ); + ++startIdx; // Because we already traversed it.... + + int cnt = fEndOffset - startIdx; + n = startAncestor.getNextSibling(); + while( cnt > 0 ) + { + DOM_Node sibling = n.getNextSibling(); + DOM_Node xferNode = traverseFullySelected( n, how ); + if ( frag!=null ) + frag.appendChild( xferNode ); + --cnt; + n = sibling; + } + + if ( how != CLONE_CONTENTS ) + { + setStartAfter( startAncestor ); + collapse( true ); + } + + return frag; +} + +/** + * Visits the nodes selected by this range when we know + * a-priori that the start and end containers are not + * the same, and we also know that neither the start + * nor end container is an ancestor of the other. + */ +DOM_DocumentFragment RangeImpl::traverseCommonAncestors( DOM_Node startAncestor, DOM_Node endAncestor, int how ) +{ + DOM_DocumentFragment frag = null; + if ( how!=DELETE_CONTENTS) + frag = fDocument.createDocumentFragment(); + + DOM_Node n = traverseLeftBoundary( startAncestor, how ); + if ( frag!=null ) + frag.appendChild( n ); + + DOM_Node commonParent = startAncestor.getParentNode(); + int startOffset = indexOf( startAncestor, commonParent ); + int endOffset = indexOf( endAncestor, commonParent ); + ++startOffset; + + int cnt = endOffset - startOffset; + DOM_Node sibling = startAncestor.getNextSibling(); + + while( cnt > 0 ) + { + DOM_Node nextSibling = sibling.getNextSibling(); + n = traverseFullySelected( sibling, how ); + if ( frag!=null ) + frag.appendChild( n ); + sibling = nextSibling; + --cnt; + } + + n = traverseRightBoundary( endAncestor, how ); + if ( frag!=null ) + frag.appendChild( n ); + + if ( how != CLONE_CONTENTS ) + { + setStartAfter( startAncestor ); + collapse( true ); + } + return frag; +} + +/** + * Traverses the "right boundary" of this range and + * operates on each "boundary node" according to the + * how parameter. It is a-priori assumed + * by this method that the right boundary does + * not contain the range's start container. + * + * A "right boundary" is best visualized by thinking + * of a sample tree: + * A + * /|\ + * / | \ + * / | \ + * B C D + * /|\ /|\ + * E F G H I J + * + * Imagine first a range that begins between the + * "E" and "F" nodes and ends between the + * "I" and "J" nodes. The start container is + * "B" and the end container is "D". Given this setup, + * the following applies: + * + * Partially Selected Nodes: B, D<br> + * Fully Selected Nodes: F, G, C, H, I + * + * The "right boundary" is the highest subtree node + * that contains the ending container. The root of + * this subtree is always partially selected. + * + * In this example, the nodes that are traversed + * as "right boundary" nodes are: H, I, and D. + * + */ +DOM_Node RangeImpl::traverseRightBoundary( DOM_Node root, int how ) +{ + DOM_Node next = getSelectedNode( fEndContainer, fEndOffset-1 ); + bool isFullySelected = ( next!=fEndContainer ); + + if ( next==root ) + return traverseNode( next, isFullySelected, false, how ); + + DOM_Node parent = next.getParentNode(); + DOM_Node clonedParent = traverseNode( parent, false, false, how ); + + while( parent!=null ) + { + while( next!=null ) + { + DOM_Node prevSibling = next.getPreviousSibling(); + DOM_Node clonedChild = + traverseNode( next, isFullySelected, false, how ); + if ( how!=DELETE_CONTENTS ) + { + clonedParent.insertBefore( + clonedChild, + clonedParent.getFirstChild() + ); } + isFullySelected = true; + next = prevSibling; } - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - } else - if (trvType == EXTRACT_CONTENTS ) { - cloneCurrent = current; - } + if ( parent==root ) + return clonedParent; + + next = parent.getPreviousSibling(); + parent = parent.getParentNode(); + DOM_Node clonedGrandParent = traverseNode( parent, false, false, how ); + if ( how!=DELETE_CONTENTS ) + clonedGrandParent.appendChild( clonedParent ); + clonedParent = clonedGrandParent; + } - - while (current != root && current != null) { - parent = current.getParentNode(); - if (parent == root) { - cloneParent = frag; - endRoot = current; - } else { - cloneParent = parent.cloneNode(false); - if (partialNode==null && parent != root) { - partialNode = parent; - partialInt = BEFORE; - } + + // should never occur + return null; +} + +/** + * Traverses the "left boundary" of this range and + * operates on each "boundary node" according to the + * how parameter. It is a-priori assumed + * by this method that the left boundary does + * not contain the range's end container. + * + * A "left boundary" is best visualized by thinking + * of a sample tree: + * + * A + * /|\ + * / | \ + * / | \ + * B C D + * /|\ /|\ + * E F G H I J + * + * Imagine first a range that begins between the + * "E" and "F" nodes and ends between the + * "I" and "J" nodes. The start container is + * "B" and the end container is "D". Given this setup, + * the following applies: + * + * Partially Selected Nodes: B, D<br> + * Fully Selected Nodes: F, G, C, H, I + * + * The "left boundary" is the highest subtree node + * that contains the starting container. The root of + * this subtree is always partially selected. + * + * In this example, the nodes that are traversed + * as "left boundary" nodes are: F, G, and B. + * + */ +DOM_Node RangeImpl::traverseLeftBoundary( DOM_Node root, int how ) +{ + DOM_Node next = getSelectedNode( getStartContainer(), getStartOffset() ); + bool isFullySelected = ( next!=getStartContainer() ); + + if ( next==root ) + return traverseNode( next, isFullySelected, true, how ); + + DOM_Node parent = next.getParentNode(); + DOM_Node clonedParent = traverseNode( parent, false, true, how ); + + while( parent!=null ) + { + while( next!=null ) + { + DOM_Node nextSibling = next.getNextSibling(); + DOM_Node clonedChild = + traverseNode( next, isFullySelected, true, how ); + if ( how!=DELETE_CONTENTS ) + clonedParent.appendChild(clonedChild); + isFullySelected = true; + next = nextSibling; } - - DOM_Node holdCurrent = current; - - current = parent.getFirstChild(); - - cloneParent.appendChild(cloneCurrent); - - DOM_Node next = null; - while (current != holdCurrent && current != null) { - next = current.getNextSibling(); - // The leftmost children are fully-selected - // and are removed, and appended, not cloned. - if (current != null && parent != root) { - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - cloneParent.appendChild(cloneCurrent); - } else - if (trvType == EXTRACT_CONTENTS) { - //cloneCurrent = current; - cloneParent.appendChild(current); - } - } - current = next; + if ( parent==root ) + return clonedParent; + + next = parent.getNextSibling(); + parent = parent.getParentNode(); + DOM_Node clonedGrandParent = traverseNode( parent, false, true, how ); + if ( how!=DELETE_CONTENTS ) + clonedGrandParent.appendChild( clonedParent ); + clonedParent = clonedGrandParent; + + } + + // should never occur + return null; + +} + +/** + * Utility method for traversing a single node. + * Does not properly handle a text node containing both the + * start and end offsets. Such nodes should + * have been previously detected and been routed to traverseTextNode. + * + */ +DOM_Node RangeImpl::traverseNode( DOM_Node n, bool isFullySelected, bool isLeft, int how ) +{ + if ( isFullySelected ) + return traverseFullySelected( n, how ); + if ( n.getNodeType()== DOM_Node::TEXT_NODE ) + return traverseTextNode( n, isLeft, how ); + return traversePartiallySelected( n, how ); +} + +/** + * Utility method for traversing a single node when + * we know a-priori that the node if fully + * selected. + * + */ +DOM_Node RangeImpl::traverseFullySelected( DOM_Node n, int how ) +{ + switch( how ) + { + case CLONE_CONTENTS: + return n.cloneNode( true ); + case EXTRACT_CONTENTS: + if ( n.getNodeType()== DOM_Node::DOCUMENT_TYPE_NODE ) + { + throw DOM_DOMException( + DOM_DOMException::HIERARCHY_REQUEST_ERR, null); } - - current = parent; - cloneCurrent = cloneParent; - + return n; + case DELETE_CONTENTS: + n.getParentNode().removeChild(n); + return null; } - - // traverse the "fully-selected" middle... - DOM_Node clonedPrevious = frag.getLastChild(); - current = endRoot.getPreviousSibling(); - DOM_Node prev = null; - while (current != startRoot && current != null) { - prev = current.getPreviousSibling(); - - if (trvType == CLONE_CONTENTS) { - cloneCurrent = current.cloneNode(true); - } else - if (trvType == EXTRACT_CONTENTS) { - cloneCurrent = current; - } - - frag.insertBefore(cloneCurrent, clonedPrevious); - - current = prev; - clonedPrevious = cloneCurrent; + return null; +} + +/** + * Utility method for traversing a single node when + * we know a-priori that the node if partially + * selected and is not a text node. + * + */ +DOM_Node RangeImpl::traversePartiallySelected( DOM_Node n, int how ) +{ + switch( how ) + { + case DELETE_CONTENTS: + return null; + case CLONE_CONTENTS: + case EXTRACT_CONTENTS: + return n.cloneNode( false ); } - - // collapse the range... - if (trvType == EXTRACT_CONTENTS ) { - if (partialNode == null) { - collapse(true); - } else - if (partialInt == AFTER) { - setStartAfter(partialNode); - setEndAfter(partialNode); - } - else if (partialInt == BEFORE) { - setStartBefore(partialNode); - setEndBefore(partialNode); - } + return null; +} + +/** + * Utility method for traversing a text node that we know + * a-priori to be on a left or right boundary of the range. + * This method does not properly handle text nodes that contain + * both the start and end points of the range. + * + */ +DOM_Node RangeImpl::traverseTextNode( DOM_Node n, bool isLeft, int how ) +{ + DOMString txtValue = n.getNodeValue(); + DOMString newNodeValue; + DOMString oldNodeValue; + + if ( isLeft ) + { + int offset = getStartOffset(); + newNodeValue = txtValue.substringData( offset , fStartContainer.getNodeValue().length()); + oldNodeValue = txtValue.substringData( 0, offset ); } + else + { + int offset = getEndOffset(); + newNodeValue = txtValue.substringData( 0, offset ); + oldNodeValue = txtValue.substringData( offset , fEndContainer.getNodeValue().length() ); + } + + if ( how != CLONE_CONTENTS ) + n.setNodeValue( oldNodeValue ); + if ( how==DELETE_CONTENTS ) + return null; + DOM_Node newNode = n.cloneNode( false ); + newNode.setNodeValue( newNodeValue ); + return newNode; +} - - return frag; +/** + * Utility method to retrieve a child node by index. This method + * assumes the caller is trying to find out which node is + * selected by the given index. Note that if the index is + * greater than the number of children, this implies that the + * first node selected is the parent node itself. + * + */ +DOM_Node RangeImpl::getSelectedNode( DOM_Node container, int offset ) +{ + if ( container.getNodeType() == DOM_Node::TEXT_NODE ) + return container; + + // This case is an important convenience for + // traverseRightBoundary() + if ( offset<0 ) + return container; + + DOM_Node child = container.getFirstChild(); + while( child!=null && offset > 0 ) + { + --offset; + child = child.getNextSibling(); + } + if ( child!=null ) + return child; + return container; } -void RangeImpl::checkReadOnly(DOM_Node& start, DOM_Node& end, +void RangeImpl::checkReadOnly(DOM_Node& start, DOM_Node& end, unsigned int startOffset, unsigned int endOffset) { if ((start == null) || (end == null) ) return; @@ -1399,7 +1516,7 @@ void RangeImpl::checkReadOnly(DOM_Node& start, DOM_Node& end, DOM_Node sNode = start.getFirstChild(); for(unsigned int i = 0; i<startOffset; i++) sNode = sNode.getNextSibling(); - + DOM_Node eNode; if (end.getNodeType() == DOM_Node::TEXT_NODE) { eNode = end; //need to check only till this node @@ -1415,7 +1532,7 @@ void RangeImpl::checkReadOnly(DOM_Node& start, DOM_Node& end, void RangeImpl::recurseTreeAndCheck(DOM_Node& start, DOM_Node& end) { - for(DOM_Node node=start; node != null, node !=end; node=node.getNextSibling()) + for(DOM_Node node=start; node != null && node !=end; node=node.getNextSibling()) { if (node.fImpl->isReadOnly()) { throw DOM_DOMException( @@ -1512,26 +1629,22 @@ void RangeImpl::updateRangeForDeletedNode(NodeImpl* node) if (node->getParentNode() == fEndContainer.fImpl) { unsigned short index = indexOf(tNode, fEndContainer); - if ( fEndOffset < index) { + if ( fEndOffset > index) { fEndOffset--; } } if (node->getParentNode() != fStartContainer.fImpl - && node->getParentNode() != fEndContainer.fImpl) { + || node->getParentNode() != fEndContainer.fImpl) { if (isAncestorOf(node, fStartContainer)) { - if (( node->getParentNode()->getNodeType() == DOM_Node::DOCUMENT_FRAGMENT_NODE) ) - return; //if the node's up in the heirarchy and its parent is doc-frag ignore DOM_Node tpNode(node->getParentNode()); setStartContainer( tpNode ); - fStartOffset = indexOf( tNode, tpNode)-1; + fStartOffset = indexOf( tNode, tpNode); } if (isAncestorOf(node, fEndContainer)) { - if (( node->getParentNode()->getNodeType() == DOM_Node::DOCUMENT_FRAGMENT_NODE)) - return; DOM_Node tpNode(node->getParentNode()); setEndContainer( tpNode ); - fEndOffset = indexOf( tNode, tpNode)-1; + fEndOffset = indexOf( tNode, tpNode); } } @@ -1541,7 +1654,7 @@ void RangeImpl::updateRangeForInsertedNode(NodeImpl* node) { if (node == null) return; if (node->getParentNode() == fStartContainer.fImpl) { - unsigned int index = indexOf(DOM_Node(node), fStartContainer) -1; + unsigned int index = indexOf(DOM_Node(node), fStartContainer); if (index < fStartOffset) { fStartOffset++; } @@ -1549,10 +1662,7 @@ void RangeImpl::updateRangeForInsertedNode(NodeImpl* node) { if (node->getParentNode() == fEndContainer.fImpl) { unsigned int index = indexOf(DOM_Node(node), fEndContainer); - //if index is equal then the text is inserted before the end of - //range so should get included in the range - - if (index <= fEndOffset) { + if (index < fEndOffset) { fEndOffset++; } } diff --git a/src/dom/RangeImpl.hpp b/src/dom/RangeImpl.hpp index fea960cb96b0bc665ff17ae2c230af12d6f43f8b..80251ee4b442bdb9c7e2532fd6496645a3569808 100644 --- a/src/dom/RangeImpl.hpp +++ b/src/dom/RangeImpl.hpp @@ -133,7 +133,8 @@ public: private: enum TraversalType { EXTRACT_CONTENTS = 1, - CLONE_CONTENTS = 2 + CLONE_CONTENTS = 2, + DELETE_CONTENTS = 3 }; enum TraversePoint { @@ -151,6 +152,8 @@ private: //misc functions void validateNode(const DOM_Node& node) const; bool isValidAncestorType(const DOM_Node& node) const; + bool hasLegalRootContainer(const DOM_Node& node) const; + bool isLegalContainedNode(const DOM_Node& node ) const; void checkIndex(const DOM_Node& node, unsigned int offset) const; static bool isAncestorOf(const DOM_Node& a, const DOM_Node& b); @@ -164,6 +167,18 @@ private: void recurseTreeAndCheck(DOM_Node& start, DOM_Node& end); DOM_Node removeChild(DOM_Node& parent, DOM_Node& child); + DOM_DocumentFragment traverseSameContainer( int how ); + DOM_DocumentFragment traverseCommonStartContainer( DOM_Node endAncestor, int how ); + DOM_DocumentFragment traverseCommonEndContainer( DOM_Node startAncestor, int how ); + DOM_DocumentFragment traverseCommonAncestors( DOM_Node startAncestor, DOM_Node endAncestor, int how ); + DOM_Node traverseRightBoundary( DOM_Node root, int how ); + DOM_Node traverseLeftBoundary( DOM_Node root, int how ); + DOM_Node traverseNode( DOM_Node n, bool isFullySelected, bool isLeft, int how ); + DOM_Node traverseFullySelected( DOM_Node n, int how ); + DOM_Node traversePartiallySelected( DOM_Node n, int how ); + DOM_Node traverseTextNode( DOM_Node n, bool isLeft, int how ); + DOM_Node getSelectedNode( DOM_Node container, int offset ); + //private data DOM_Node fStartContainer; diff --git a/tests/DOM/RangeTest/RangeTest.cpp b/tests/DOM/RangeTest/RangeTest.cpp index 2fdff0a1c5ee3ea8d204e4e2f111279c226458f3..0c3458f4d2de48eb667b1c92649b355089bdf7bd 100644 --- a/tests/DOM/RangeTest/RangeTest.cpp +++ b/tests/DOM/RangeTest/RangeTest.cpp @@ -151,6 +151,7 @@ int main() extractContents toString detach + removeChild */ { @@ -223,13 +224,13 @@ int main() //Tests start here // Initial dom tree looks like : - // <Body><H1>Title</H1><P>Blah xyz</P> - //i.e., Body - // _____________|______________ + // <Body><H1>TitleAnother Text</H1><P>Blah xyz</P></Body> + //i.e., Body(rt) + // _____________|________________ // | | - // H1 P - // | | - // "Title" "Blah xyz" + // ___H1(E11)___ P(E12) + // | | | + // "Title" "Another Text" "Blah xyz" //test for start and end settings of a range @@ -275,6 +276,17 @@ int main() TASSERT(range.getEndContainer() == rt); TASSERT(range.getEndOffset() == 5); + //After above operations, now the tree looks like: + // <Body><Element3/><H1>TitleAnother Text</H1><Element2/><Element1/><P>Blah xyz</P></Body> + //i.e., Body(rt) + // _____________|_______________________________________________________________ + // | | | | | + // Element3(E122) ___H1(E11)___ Element2(E121) Element1(E120) P(E12) + // | | | + // "Title" "Another Text" "Blah xyz" + // + // range has rt as start and end container, and 2 as start offset, 5 as end offset + //changing selection range.selectNode(rt.getLastChild().getPreviousSibling()); TASSERT(range.getStartContainer() == rt); @@ -288,42 +300,86 @@ int main() range.setStart(rt.getFirstChild().getNextSibling().getFirstChild(), 2); TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getFirstChild()); + TASSERT(range.getStartContainer().getNodeValue().equals("Title")); TASSERT(range.getStartOffset() == 2); range.setEnd(rt.getFirstChild().getNextSibling().getFirstChild(), 4); TASSERT(range.getEndContainer() == rt.getFirstChild().getNextSibling().getFirstChild()); + TASSERT(range.getEndContainer().getNodeValue().equals("Title")); TASSERT(range.getEndOffset() == 4); + TASSERT(range.toString().equals("tl")); //inserting text between a text node range.insertNode(E210); //only end offset moves and new node gets into range as being inserted at boundary point - TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getLastChild().getPreviousSibling()); - TASSERT(range.getStartOffset() == 0); + TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getFirstChild()); + TASSERT(range.getStartOffset() == 2); TASSERT(range.getEndContainer() == rt.getFirstChild().getNextSibling().getLastChild().getPreviousSibling()); TASSERT(range.getEndOffset() == 2); - //inserting element node before the selcted text node + //inserting element node before the selected text node range.insertNode(E120); //only end offset moves and new node gets into range as being inserted at boundary point - TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getLastChild().getPreviousSibling()); - TASSERT(range.getStartOffset() == 0); + TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getFirstChild()); + TASSERT(range.getStartContainer().getNodeValue().equals("Ti")); + TASSERT(range.getStartOffset() == 2); TASSERT(range.getEndContainer() == rt.getFirstChild().getNextSibling().getLastChild().getPreviousSibling()); + TASSERT(range.getEndContainer().getNodeValue().equals("tle")); TASSERT(range.getEndOffset() == 2); + TASSERT(E11.getChildNodes().getLength()==6); //checking the text replacment range.getStartContainer().setNodeValue("ReplacedText"); - //collapsed - TASSERT(range.getCollapsed() == true); - - //the offsets are set to 0 + //only the start offset is impact + TASSERT(range.getStartContainer() == rt.getFirstChild().getNextSibling().getFirstChild()); + TASSERT(range.getStartContainer().getNodeValue().equals("ReplacedText")); TASSERT(range.getStartOffset() == 0); - TASSERT(range.getEndOffset() == 0); + TASSERT(range.getEndContainer() == rt.getFirstChild().getNextSibling().getLastChild().getPreviousSibling()); + TASSERT(range.getEndContainer().getNodeValue().equals("tle")); + TASSERT(range.getEndOffset() == 2); + + //After above operations, now the tree looks like: + // <Body><Element3/><H1>ReplacedText<Element1/>insertedTexttleAnother Text</H1><Element2/><P>Blah xyz</P></Body> + //i.e., Body(rt) + // _____________|_______________________________________________________________________________________________ + // | | | | + // Element3(E122) ___H1(E11)___________________________________________________________ Element2(E121) P(E12) + // | | | | | | | + // "ReplacedText" "" Element1(E120) "insertedText"(E210) "tle" "Another Text" "Blah xyz" + // + // range has "ReplacedText" as start container and "tle" as end container + // and 0 as start offset, 2 as end offset //changing the selection. Preparing for 'surround' range.setStart(range.getStartContainer().getParentNode(), 2); range.setEnd(range.getStartContainer(), 5); + TASSERT(range.getStartContainer().getNodeName().equals("H1")); + TASSERT(range.getEndContainer().getNodeName().equals("H1")); + TASSERT(range.toString().equals("insertedTexttle")); + range.surroundContents(E311); + TASSERT(range.getStartContainer().getNodeName().equals("H1")); + TASSERT(range.getStartOffset() == 2); + TASSERT(range.getEndContainer().getNodeName().equals("H1")); + TASSERT(range.getEndOffset() == 3); + TASSERT(E11.getChildNodes().getLength()==4); + TASSERT(E311.getChildNodes().getLength()==3); + TASSERT(range.toString().equals("insertedTexttle")); + + //After above operations, now the tree looks like: + // <Body><Element3/><H1>ReplacedText<SurroundNode1><Element1/>insertedTexttle</SurroundNode1>Another Text</H1><Element2/><P>Blah xyz</P></Body> + //i.e., Body(rt) + // _____________|_________________________________________________________________________ + // | | | | + // Element3(E122) ___H1(E11)___________________________________ Element2(E121) P(E12) + // | | | | | + // "ReplacedText" "" SurroundNode1(E311) "Another Text" "Blah xyz" + // ____________ |_____________________________ + // | | | + // Element1(E120) "insertedText"(E210) "tle" + // + // range has H1 as start and end container and 2 as start offset, 3 as end offset //testing cloning DOM_Range aRange = range.cloneRange(); @@ -339,11 +395,11 @@ int main() short compVal = range.compareBoundaryPoints(DOM_Range::END_TO_END, aRange); TASSERT(compVal == 0); compVal = range.compareBoundaryPoints(DOM_Range::START_TO_START, aRange); - TASSERT(compVal == -1); + TASSERT(compVal == 1); compVal = range.compareBoundaryPoints(DOM_Range::START_TO_END, aRange); - TASSERT(compVal == -1); - compVal = range.compareBoundaryPoints(DOM_Range::END_TO_START, aRange); TASSERT(compVal == 1); + compVal = range.compareBoundaryPoints(DOM_Range::END_TO_START, aRange); + TASSERT(compVal == -1); //testing collapse //not collapsed @@ -351,46 +407,50 @@ int main() TASSERT(range.getStartOffset() == 2); TASSERT(range.getEndOffset() == 3); - //selectNodeContents range.selectNodeContents(rt.getLastChild().getFirstChild()); TASSERT(range.getStartContainer() == rt.getLastChild().getFirstChild()); TASSERT(range.getEndContainer() == rt.getLastChild().getFirstChild()); TASSERT(range.getStartOffset() == 0); TASSERT(range.getEndOffset() == 8); + TASSERT(range.toString().equals("Blah xyz")); //testing collapse range.collapse(true); //collapse to start TASSERT(range.getCollapsed() == true); TASSERT(range.getStartOffset() == 0); TASSERT(range.getEndOffset() == 0); + TASSERT(range.toString().equals("")); TASSERT(aRange.getEndOffset() == 3); //other range is unaffected + TASSERT(aRange.toString().equals("eplacedTextinsertedTexttle")); - - // DOM Tree now looks like this - // <Body> - // |---------|----------|--------------| - // Element3 H1 Element2 P - // | | - // |------|-----------| "Blah xyz" - // "Ti" "insertedText" SurroundNode - // | - // |--------|---------------| - // Element1 "ReplacedText "AnotherText" - - // range has H1 as start and end container and 2 as start and end offset. in collapsed state - // aRange has "Ti" as start with 1 as start offset, H1 as end and 3 as end offset - - + //After above operations, now the tree looks like: + // <Body><Element3/><H1>ReplacedText<SurroundNode1><Element1/>insertedTexttle</SurroundNode1>Another Text</H1><Element2/><P>Blah xyz</P></Body> + //i.e., Body(rt) + // _____________|_________________________________________________________________________ + // | | | | + // Element3(E122) ___H1(E11)___________________________________ Element2(E121) P(E12) + // | | | | | + // "ReplacedText" "" SurroundNode1(E311) "Another Text" "Blah xyz" + // ____________ |_____________________________ + // | | | + // Element1(E120) "insertedText"(E210) "tle" + // + // range has "Blah xyz" as start and end container and 0 as start and end offset (collapsed) + // aRange has "ReplacedText" as start container and H1 as end container + // and 1 as start offset and 3 as end offset DOM_DocumentFragment docFrag = aRange.cloneContents(); TASSERT( docFrag != 0); range.selectNode(rt.getFirstChild()); TASSERT(range.getStartContainer() == rt); + TASSERT(range.getEndContainer() == rt); + TASSERT(range.getStartOffset() == 0); + TASSERT(range.getEndOffset() == 1); //Testing toString() const char* str = aRange.toString().transcode(); - char* str2 = "iinsertedTextReplacedTextAnotherText"; + char* str2 = "eplacedTextinsertedTexttle"; TASSERT(*str == *str2); //start and end before and after tests @@ -413,10 +473,424 @@ int main() //testing extract() DOM_DocumentFragment frag2 = range.extractContents(); TASSERT( frag2 != 0); + + //After above operations, now the tree looks like: + // <Body><Element3/></Body> + //i.e., Body(rt) + // | + // Element3(E122) + // + // aRange has rt as start and end container, and 1 as start and end offset (collapsed) + // range has rt as start and end container, and 1 as start and end offset (collapsed) + // + //and frag2 looks: + // <Body>ReplacedText<SurroundNode1><Element1/>insertedTexttle</SurroundNode1>Another Text</H1><Element2/><P>Blah xyz</P></Body> + //i.e., Body(rt) + // ______________|________________________________________________________ + // | | | + // ___H1(E11)___________________________________ Element2(E121) P(E12) + // | | | | | + //"ReplacedText" "" SurroundNode1(E311) "Another Text" "Blah xyz" + // ____________ |_____________________________ + // | | | + // Element1(E120) "insertedText"(E210) "tle" + // + + //the tree do not have those node anymore after extract + //only Element3 left + TASSERT(rt.getChildNodes().getLength()==1); + + //aRange is collapsed + TASSERT(aRange.getCollapsed() == true); + TASSERT(aRange.getStartContainer() == rt); + TASSERT(aRange.getStartOffset() == 1); + TASSERT(aRange.getEndContainer() == rt); + TASSERT(aRange.getEndOffset() == 1); + + //range is collapsed as well + TASSERT(range.getCollapsed() == true); + TASSERT(range.getStartContainer() == rt); + TASSERT(range.getStartOffset() == 1); + TASSERT(range.getEndContainer() == rt); + TASSERT(range.getEndOffset() == 1); + + //test the document fragment frag2 + TASSERT(frag2.getChildNodes().getLength()==3); + //detaching the other range aRange.detach(); range.detach(); + //*************************************************************** + //another set of test + //TEST createRange, setStart and setEnd, insertnode + //*************************************************************** + DOM_Document doc2 = DOM_Document::createDocument(); + DOM_Element root2 = doc2.createElement("root2"); + doc2.appendChild(root2); + //case 1: simple text node, start==end + // <body>text1</body> + DOM_Element body = doc2.createElement("body"); + DOM_Text text1 = doc2.createTextNode("text1"); + body.appendChild(text1); + root2.appendChild(body); + + //set range + DOM_Range range1 = doc2.createRange(); + range1.setStart(text1,1); + range1.setEnd(text1,3); + + TASSERT(range1.toString().equals("ex")); + TASSERT(range1.getStartOffset()==1); + TASSERT(range1.getStartContainer().getNodeValue().equals("text1")); + TASSERT(range1.getEndOffset()==3); + TASSERT(range1.getEndContainer().getNodeValue().equals("text1")); + + //now insert a text node + //<body>ttext2ext1</body> + DOM_Text text2 = doc2.createTextNode("text2"); + range1.insertNode(text2); + + TASSERT(range1.toString().equals("text2ex")); + TASSERT(range1.getStartOffset()==1); + TASSERT(range1.getStartContainer().getNodeValue().equals("t")); + TASSERT(range1.getEndOffset()==2); + TASSERT(range1.getEndContainer().getNodeValue().equals("ext1")); + + //now insert a non-text node + //<body>t<p1/>text2ext1</body> + DOM_Element p1 = doc2.createElement("p1"); + range1.insertNode(p1); + + TASSERT(range1.toString().equals("text2ex")); + TASSERT(range1.getStartOffset()==1); + TASSERT(range1.getStartContainer().getNodeValue().equals("t")); + TASSERT(range1.getEndOffset()==2); + TASSERT(range1.getEndContainer().getNodeValue().equals("ext1")); + + //case 2: non-text node, start==end + // <head><h1/></head> + DOM_Element head = doc2.createElement("head"); + DOM_Element h1 = doc2.createElement("h1"); + head.appendChild(h1); + root2.appendChild(head); + + //set range + DOM_Range range2 = doc2.createRange(); + range2.setStart(head,0); + range2.setEnd(head,1); + + TASSERT(range2.toString().equals("")); + TASSERT(range2.getStartOffset()==0); + TASSERT(range2.getStartContainer().getNodeName().equals("head")); + TASSERT(range2.getEndOffset()==1); + TASSERT(range2.getEndContainer().getNodeName().equals("head")); + + //now insert a non-text node + //<head><h2/><h1/></head> + DOM_Element h2 = doc2.createElement("h2"); + range2.insertNode(h2); + + TASSERT(range2.toString().equals("")); + TASSERT(range2.getStartOffset()==0); + TASSERT(range2.getStartContainer().getNodeName().equals("head")); + TASSERT(range2.getEndOffset()==2); + TASSERT(range2.getEndContainer().getNodeName().equals("head")); + + //now insert a text node + //<head>text5<h2/><h1/></head> + DOM_Text text5 = doc2.createTextNode("text5"); + range2.insertNode(text5); + + TASSERT(range2.toString().equals("text5")); + TASSERT(range2.getStartOffset()==0); + TASSERT(range2.getStartContainer().getNodeName().equals("head")); + TASSERT(range2.getEndOffset()==3); + TASSERT(range2.getEndContainer().getNodeName().equals("head")); + + //case 3: simple text node, start!=end + // <body2>text3</body2> + DOM_Element body2 = doc2.createElement("body2"); + DOM_Text text3 = doc2.createTextNode("text3"); + body2.appendChild(text3); + root2.appendChild(body2); + + //set range + DOM_Range range3 = doc2.createRange(); + range3.setStart(text3,1); + range3.setEnd(body2,1); + + TASSERT(range3.toString().equals("ext3")); + TASSERT(range3.getStartOffset()==1); + TASSERT(range3.getStartContainer().getNodeValue().equals("text3")); + TASSERT(range3.getEndOffset()==1); + TASSERT(range3.getEndContainer().getNodeName().equals("body2")); + + //now insert a textnode + //<body2>ttext4ext3</body2> + DOM_Text text4 = doc2.createTextNode("text4"); + range3.insertNode(text4); + + TASSERT(range3.toString().equals("")); + TASSERT(range3.getStartOffset()==1); + TASSERT(range3.getStartContainer().getNodeValue().equals("t")); + TASSERT(range3.getEndOffset()==1); + TASSERT(range3.getEndContainer().getNodeName().equals("body2")); + + //now insert a non-text node + //<body2>t<p2/>text4ext3</body2> + DOM_Element p2 = doc2.createElement("p2"); + range3.insertNode(p2); + + //extra empty node caused by splitting 't' + TASSERT(range3.toString().equals("")); + TASSERT(range3.getStartOffset()==1); + TASSERT(range3.getStartContainer().getNodeValue().equals("t")); + TASSERT(range3.getEndOffset()==1); + TASSERT(range3.getEndContainer().getNodeName().equals("body2")); + + //test toString a bit + range3.setStart(body2,1); + range3.setEnd(body2,5); + + TASSERT(range3.toString().equals("text4ext3")); + + range3.setStart(body2,0); + range3.setEnd(body2,5); + + TASSERT(range3.toString().equals("ttext4ext3")); + + //case 4: non-text node, start!=end + // <head2><h3/></head2> + DOM_Element head2 = doc2.createElement("head2"); + DOM_Element h3 = doc2.createElement("h3"); + head2.appendChild(h3); + root2.appendChild(head2); + + //set range + DOM_Range range4 = doc2.createRange(); + range4.setStart(head2,0); + range4.setEnd(h3,0); + + TASSERT(range4.toString().equals("")); + TASSERT(range4.getStartOffset()==0); + TASSERT(range4.getStartContainer().getNodeName().equals("head2")); + TASSERT(range4.getEndOffset()==0); + TASSERT(range4.getEndContainer().getNodeName().equals("h3")); + + //now insert a non-text node + //<head2><h4/><h3/></head2> + DOM_Element h4 = doc2.createElement("h4"); + range4.insertNode(h4); + + TASSERT(range4.toString().equals("")); + TASSERT(range4.getStartOffset()==0); + TASSERT(range4.getStartContainer().getNodeName().equals("head2")); + TASSERT(range4.getEndOffset()==0); + TASSERT(range4.getEndContainer().getNodeName().equals("h3")); + + //now insert a text node + //<head2>text6<h4/><h3/></head2> + DOM_Text text6 = doc2.createTextNode("text6"); + range4.insertNode(text6); + + TASSERT(range4.toString().equals("text6")); + TASSERT(range4.getStartOffset()==0); + TASSERT(range4.getStartContainer().getNodeName().equals("head2")); + TASSERT(range4.getEndOffset()==0); + TASSERT(range4.getEndContainer().getNodeName().equals("h3")); + + //*************************************************************** + // quick test of updating + //*************************************************************** + // <upbody>text1</upbody> + DOM_Element upbody = doc2.createElement("upbody"); + DOM_Text uptext1 = doc2.createTextNode("uptext1"); + upbody.appendChild(uptext1); + root2.appendChild(upbody); + + DOM_Range uprange = doc2.createRange(); + uprange.setStart(upbody,0); + uprange.setEnd(upbody,1); + + TASSERT(uprange.toString().equals("uptext1")); + TASSERT(uprange.getStartOffset()==0); + TASSERT(uprange.getStartContainer().getNodeName().equals("upbody")); + TASSERT(uprange.getEndOffset()==1); + TASSERT(uprange.getEndContainer().getNodeName().equals("upbody")); + + // split text + uptext1.splitText(1); + + TASSERT(uprange.toString().equals("u")); + TASSERT(uprange.getStartOffset()==0); + TASSERT(uprange.getStartContainer().getNodeName().equals("upbody")); + TASSERT(uprange.getEndOffset()==1); + TASSERT(uprange.getEndContainer().getNodeName().equals("upbody")); + + //insert node + DOM_Element upbody2 = doc2.createElement("upbody2"); + DOM_Text uptext2 = doc2.createTextNode("uptext2"); + upbody2.appendChild(uptext2); + root2.appendChild(upbody2); + + DOM_Range uprange2 = doc2.createRange(); + uprange2.setStart(uptext2,1); + uprange2.setEnd(upbody2,1); + + DOM_Range uprange3 = doc2.createRange(); + uprange3.setStart(uptext2,1); + uprange3.setEnd(upbody2,1); + + TASSERT(uprange2.toString().equals("ptext2")); + TASSERT(uprange2.getStartOffset()==1); + TASSERT(uprange2.getStartContainer().getNodeValue().equals("uptext2")); + TASSERT(uprange2.getEndOffset()==1); + TASSERT(uprange2.getEndContainer().getNodeName().equals("upbody2")); + + TASSERT(uprange3.toString().equals("ptext2")); + TASSERT(uprange3.getStartOffset()==1); + TASSERT(uprange3.getStartContainer().getNodeValue().equals("uptext2")); + TASSERT(uprange3.getEndOffset()==1); + TASSERT(uprange3.getEndContainer().getNodeName().equals("upbody2")); + + DOM_Element upp1 = doc2.createElement("upp1"); + uprange2.insertNode(upp1); + + TASSERT(uprange2.toString().equals("")); + TASSERT(uprange2.getStartOffset()==1); + TASSERT(uprange2.getStartContainer().getNodeValue().equals("u")); + TASSERT(uprange2.getEndOffset()==1); + TASSERT(uprange2.getEndContainer().getNodeName().equals("upbody2")); + + TASSERT(uprange3.toString().equals("")); + TASSERT(uprange3.getStartOffset()==1); + TASSERT(uprange3.getStartContainer().getNodeValue().equals("u")); + TASSERT(uprange3.getEndOffset()==1); + TASSERT(uprange3.getEndContainer().getNodeName().equals("upbody2")); + + //*************************************************************** + //another set of test + //<foo><c/><moo><b/></moo>ab<a>Hello cd</a><cool>ef</cool></foo> + // + // ______________________foo_____________________ + // | | | | | + // c moo "ab" a cool + // | | | + // b "Hello cd" "ef" + // + DOM_Document doc3 = DOM_Document::createDocument(); + DOM_Element root3 = doc3.createElement("root"); + doc3.appendChild(root3); + + DOM_Element foo = doc3.createElement("foo"); + DOM_Element moo = doc3.createElement("moo"); + DOM_Element cool = doc3.createElement("cool"); + DOM_Text ab = doc3.createTextNode("ab"); + DOM_Text cd = doc3.createTextNode("Hello cd"); + DOM_Text ef = doc3.createTextNode("ef"); + + DOM_Element a = doc3.createElement("a"); + DOM_Element b = doc3.createElement("b"); + DOM_Element c = doc3.createElement("c"); + + root3.appendChild(foo); + foo.appendChild(c); + foo.appendChild(moo); + foo.appendChild(ab); + foo.appendChild(a); + foo.appendChild(cool); + moo.appendChild(b); + a.appendChild(cd); + cool.appendChild(ef); + + //*************************************************************** + //TEST toString + //*************************************************************** + DOM_Range newtestrange = doc3.createRange(); + //case 1: + //start container is text node + // i) end container is also text node + // a) start==end + // b) start!=end + // ii) end container is not text node + // a) start==end => impossible + // b) start!=end + // + //case 2: + //start container is not text node + // i) end container is text node + // a) start==end => impossible + // b) start!=end + // ii) end container is not text node + // a) start==end + // b) start!=end + + //case 1, i, a + newtestrange.setStart( cd, 1 ); + newtestrange.setEnd( cd, 4 ); + + TASSERT(newtestrange.toString().equals("ell")); + + //case 1, i, b + newtestrange.setStart( cd, 1 ); + newtestrange.setEnd( ef, 2 ); + + TASSERT(newtestrange.toString().equals("ello cdef")); + + //case 1, ii, b + newtestrange.setStart( cd, 1 ); + newtestrange.setEnd( foo, 4 ); + + TASSERT(newtestrange.toString().equals("ello cd")); + + //case 2, i, b + newtestrange.setStart( foo, 1 ); + newtestrange.setEnd( cd, 5 ); + + TASSERT(newtestrange.toString().equals("abHello")); + + //case 2, ii, a + newtestrange.setStart( foo, 1 ); + newtestrange.setEnd( foo, 4 ); + + TASSERT(newtestrange.toString().equals("abHello cd")); + + //case 2, ii, b + newtestrange.setStart( moo, 1 ); + newtestrange.setEnd( foo, 4 ); + + TASSERT(newtestrange.toString().equals("abHello cd")); + + //*************************************************************** + //test removeChild + //*************************************************************** + DOM_Range newrange = doc3.createRange(); + newrange.setStart( moo, 0 ); + newrange.setEnd( foo, 4 ); + + TASSERT(newrange.getStartOffset()==0); + TASSERT(newrange.getStartContainer().getNodeName().equals("moo")); + TASSERT(newrange.getEndOffset()==4); + TASSERT(newrange.getEndContainer().getNodeName().equals("foo")); + TASSERT(newrange.toString().equals("abHello cd")); + + DOM_Node n = newrange.cloneContents(); + DOM_NodeList nol = foo.getChildNodes(); + + //removing moo + foo.removeChild(nol.item(1)); + TASSERT(newrange.getStartOffset()==1); + TASSERT(newrange.getStartContainer().getNodeName().equals("foo")); + TASSERT(newrange.getEndOffset()==3); + TASSERT(newrange.getEndContainer().getNodeName().equals("foo")); + TASSERT(newrange.toString().equals("abHello cd")); + + TASSERT(newtestrange.getStartOffset()==1); + TASSERT(newtestrange.getStartContainer().getNodeName().equals("foo")); + TASSERT(newtestrange.getEndOffset()==3); + TASSERT(newtestrange.getEndContainer().getNodeName().equals("foo")); + TASSERT(newtestrange.toString().equals("abHello cd")); } TESTEPILOG;