/* * Copyright 1999-2002,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * $Id: RangeImpl.cpp 176278 2005-01-07 14:38:22Z amassari $ */ #include #include "NodeImpl.hpp" #include "RangeImpl.hpp" #include "TextImpl.hpp" #include "DocumentImpl.hpp" #include "DOM_DOMException.hpp" #include "DOM_Document.hpp" #include "DocumentFragmentImpl.hpp" #include "DOM_Document.hpp" #include "DOM_RangeException.hpp" #include "DOM_DOMException.hpp" #include "DOM_Text.hpp" XERCES_CPP_NAMESPACE_BEGIN //--------------------- // C'tor and D'tor //--------------------- RangeImpl::RangeImpl(DOM_Document doc) : fStartContainer(doc), fStartOffset(0), fEndContainer(doc), fEndOffset(0), fCollapsed(true), fDocument(doc), fDetached(false), fRemoveChild(0) { } RangeImpl::RangeImpl(const RangeImpl& other) : RefCountedImpl() { fDocument = other.fDocument; fStartContainer = other.fStartContainer; fStartOffset = other.fStartOffset; fEndContainer = other.fEndContainer; fEndOffset = other.fEndOffset; fDetached = other.fDetached; fCollapsed = other.fCollapsed; fRemoveChild = other.fRemoveChild; } RangeImpl::~RangeImpl() { } void RangeImpl::unreferenced() { if (((DocumentImpl*)fDocument.fImpl)->ranges != 0L) { int sz = ((DocumentImpl*)fDocument.fImpl)->ranges->size(); for (int i=0; i< sz; i++) { if (((DocumentImpl*)fDocument.fImpl)->ranges->elementAt(i) == this) { ((DocumentImpl*)fDocument.fImpl)->ranges->removeElementAt(i); break; } } } // delete this; RangeImpl* ptr = this; delete ptr; }; //------------------------------- // Public getter functions //------------------------------- DOM_Node RangeImpl::getStartContainer() const { return fStartContainer; } unsigned int RangeImpl::getStartOffset() const { return fStartOffset; } DOM_Node RangeImpl::getEndContainer() const { return fEndContainer; } unsigned int RangeImpl::getEndOffset() const { return fEndOffset; } bool RangeImpl::getCollapsed() const { if (fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } return ((fStartContainer == fEndContainer) && (fStartOffset == fEndOffset)); } //------------------------------- // Public getter functions //------------------------------- 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; fStartOffset = offset; if ((fDocument != refNode.getOwnerDocument() ) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(true); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(true); //collapse the range positions to start else fCollapsed = false; } void RangeImpl::setEnd(const DOM_Node& refNode, unsigned int offset) { validateNode(refNode); checkIndex(refNode, offset); fEndContainer = refNode; fEndOffset = offset; if ((fDocument != refNode.getOwnerDocument() ) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(false); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(false); //collapse the range positions to end else fCollapsed = false; } void RangeImpl::setStartBefore(const DOM_Node& 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; for (DOM_Node n = refNode; n!=null; n = n.getPreviousSibling()) { i++; } if (i == 0) fStartOffset = 0; else fStartOffset = i-1; if ((fDocument != refNode.getOwnerDocument()) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(true); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(true); //collapse the range positions to start else fCollapsed = false; } void RangeImpl::setStartAfter(const DOM_Node& 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; for (DOM_Node n = refNode; n!=null; n = n.getPreviousSibling()) { i++; } fStartOffset = i; if ((fDocument != refNode.getOwnerDocument() ) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(true); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(true); //collapse the range positions to start else fCollapsed = false; } void RangeImpl::setEndBefore(const DOM_Node& 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; for (DOM_Node n = refNode; n!=null; n = n.getPreviousSibling(), i++) ; if (i< 1) fEndOffset = 0; else fEndOffset = i-1; if ((fDocument != refNode.getOwnerDocument() ) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(true); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(false); //collapse the range positions to end else fCollapsed = false; } void RangeImpl::setEndAfter(const DOM_Node& 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; for (DOM_Node n = refNode; n!=null; n = n.getPreviousSibling(), i++) ; if (i ==0) fEndOffset = 0; else fEndOffset = i; if ((fDocument != refNode.getOwnerDocument() ) && (refNode.getOwnerDocument().fImpl != 0) ) { fDocument = refNode.getOwnerDocument(); collapse(true); } //compare the start and end boundary point //collapse if start point is after the end point if(compareBoundaryPoints(DOM_Range::END_TO_START, this) == 1) collapse(false); //collapse the range positions to end else fCollapsed = false; } //------------------------------- // Public Misc. functions //------------------------------- void RangeImpl::detach() { if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } fDetached = true; //nullify nodes fStartContainer = 0; fStartOffset = 0; fEndContainer = 0; fEndOffset = 0; fCollapsed = true; fRemoveChild = 0; } void RangeImpl::collapse(bool toStart) { if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } if (toStart) { fEndContainer = fStartContainer; fEndOffset = fStartOffset; } else { fStartContainer = fEndContainer; fStartOffset = fEndOffset; } fCollapsed = true; } void RangeImpl::selectNode(const DOM_Node& refNode) { validateNode(refNode); if ( !isLegalContainedNode(refNode)) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); } //First check for the text type node if (refNode.getNodeType() == DOM_Node::TEXT_NODE) { //The node itself is the container. fStartContainer = refNode; fEndContainer = refNode; //Select all the contents of the node fStartOffset = 0; fEndOffset = ((DOM_Text &)refNode).getLength(); return; } DOM_Node parent = refNode.getParentNode(); if (parent != null ) // REVIST: what to do if it IS null? { fStartContainer = parent; fEndContainer = parent; unsigned int i = 0; for (DOM_Node n = parent.getFirstChild(); n!=null, n!=refNode; n = n.getNextSibling()) { i++; } fStartOffset = i; fEndOffset = fStartOffset+1; } } void RangeImpl::selectNodeContents(const DOM_Node& node) { validateNode(node); fStartContainer = node; fEndContainer = node; fStartOffset = 0; if (node.getNodeType() == DOM_Node::TEXT_NODE ) { fEndOffset = ((DOM_Text &)node).getLength(); return; } DOM_Node first = node.getFirstChild(); if (first == null) { fEndOffset = 0; return; } unsigned int i = 0; for (DOM_Node n = first; n!=null; n = n.getNextSibling()) { i++; } fEndOffset = i; } void RangeImpl::surroundContents(DOM_Node& newParent) { if (newParent==null) return; //check for elimination criteria if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } if (newParent.getOwnerDocument() !=fDocument) { throw DOM_DOMException( DOM_DOMException::WRONG_DOCUMENT_ERR, null); } int type = newParent.getNodeType(); if ( !isLegalContainedNode(newParent) || type == DOM_Node::DOCUMENT_TYPE_NODE) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); } DOM_Node root = getCommonAncestorContainer(); DOM_Node realStart = fStartContainer; DOM_Node realEnd = fEndContainer; if (fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { realStart = fStartContainer.getParentNode(); } if (fEndContainer.getNodeType() == DOM_Node::TEXT_NODE) { realEnd = fEndContainer.getParentNode(); } if (realStart != realEnd) { throw DOM_RangeException( DOM_RangeException::BAD_BOUNDARYPOINTS_ERR, null); } DOM_DocumentFragment frag = extractContents(); insertNode(newParent); newParent.appendChild(frag); selectNode(newParent); } short RangeImpl::compareBoundaryPoints(DOM_Range::CompareHow how, RangeImpl* srcRange) const { if (fDocument != srcRange->fDocument) { throw DOM_DOMException( DOM_DOMException::WRONG_DOCUMENT_ERR, null); } if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } DOM_Node pointA, pointB; int offsetA, offsetB; switch (how) { case (DOM_Range::START_TO_START) : pointB = srcRange->getStartContainer(); pointA = fStartContainer; offsetB = srcRange->getStartOffset(); offsetA = fStartOffset; break; case (DOM_Range::START_TO_END) : pointB = srcRange->getStartContainer(); pointA = fEndContainer; offsetB = srcRange->getStartOffset(); offsetA = fEndOffset; break; case (DOM_Range::END_TO_START) : pointB = srcRange->getEndContainer(); pointA = fStartContainer; offsetB = srcRange->getEndOffset(); offsetA = fStartOffset; break; case (DOM_Range::END_TO_END) : pointB = srcRange->getEndContainer(); pointA = fEndContainer; offsetB = srcRange->getEndOffset(); offsetA = fEndOffset; break; default: throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } // case 1: same container if (pointA == pointB) { if (offsetA < offsetB) return -1; //A before B if (offsetA == offsetB) return 0; //A equal to B return 1; // A after B } // case 2: Child C of container A is ancestor of B for (DOM_Node node = pointA.getFirstChild(); node != null; node=node.getNextSibling()) { if (isAncestorOf(node, pointB)) { int index = indexOf(node, pointA); if (offsetA <= index) return -1; return 1; } } // case 3: Child C of container B is ancestor of A for (DOM_Node nd = pointB.getFirstChild(); nd != null; nd=nd.getNextSibling()) { if (isAncestorOf(nd, pointA)) { int index = indexOf(nd, pointB); if (index < offsetB ) return -1; return 1; //B strictly before A } } // case 4: preorder traversal of context tree. DOM_Node ancestor = commonAncestorOf(pointA, pointB); DOM_Node current = ancestor; do { if (current == pointA) return -1; if (current == pointB) return 1; current = nextNode(current, true); } while (current!=null && current!=ancestor); return -2; // this should never happen } void RangeImpl:: deleteContents() { traverseContents(DELETE_CONTENTS); } DOM_DocumentFragment RangeImpl::extractContents() { checkReadOnly(fStartContainer, fEndContainer, fStartOffset, fEndOffset); return traverseContents(EXTRACT_CONTENTS); } DOM_DocumentFragment RangeImpl::cloneContents() const { // cast off const. return ((RangeImpl *)this)->traverseContents(CLONE_CONTENTS); } void RangeImpl::insertNode(DOM_Node& newNode) { if (newNode == null) return; //don't have to do anything 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( DOM_DOMException::WRONG_DOCUMENT_ERR, null); } // Prevent cycles in the tree. //isKidOK() is not checked here as its taken care by insertBefore() function if (isAncestorOf( newNode, fStartContainer)) { throw DOM_DOMException( DOM_DOMException::HIERARCHY_REQUEST_ERR, null); } if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } int type = newNode.getNodeType(); if (type == DOM_Node::ATTRIBUTE_NODE || type == DOM_Node::ENTITY_NODE || type == DOM_Node::NOTATION_NODE || type == DOM_Node::DOCUMENT_NODE) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); } DOM_Node parent; DOM_Node next; if (fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { //set 'parent' and 'next' here parent = fStartContainer.getParentNode(); //split the text nodes if (fStartOffset > 0) ((DOM_Text &)fStartContainer).splitText(fStartOffset); //update the new start information later. After inserting the first newNode if (fStartOffset == 0) next = fStartContainer; else next = fStartContainer.getNextSibling(); } // end of text handling else { parent = fStartContainer; next = fStartContainer.getFirstChild(); for(unsigned int i = 0; (i < fStartOffset) && (next != null); i++) { next=next.getNextSibling(); } } if (parent != null) { if (next != null) parent.insertBefore(newNode, next); else parent.appendChild(newNode); } } RangeImpl* RangeImpl::cloneRange() const { if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } RangeImpl* range = ((DocumentImpl*)fDocument.fImpl)->createRange(); range->setStart(fStartContainer, fStartOffset); range->setEnd(fEndContainer, fEndOffset); return range; } DOMString RangeImpl::toString() const { if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } 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) { 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 { //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 (counter0 && stopNode!=null ){ --i; stopNode = stopNode.getNextSibling(); } if ( stopNode == null ) stopNode = nextNode( fEndContainer, false ); } 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 || fEndContainer.getNodeType() == DOM_Node::CDATA_SECTION_NODE) { tempString.appendData(fEndContainer.getNodeValue().substringData(0,fEndOffset)); } return tempString; } DOM_Document RangeImpl::getDocument() { return fDocument; } const DOM_Node RangeImpl::getCommonAncestorContainer() const { return commonAncestorOf(fStartContainer, fEndContainer); } //--------------------- //private functions //--------------------- bool RangeImpl::isValidAncestorType(const DOM_Node& node) const { for (DOM_Node aNode = node; aNode!=null; aNode = aNode.getParentNode()) { short type = aNode.getNodeType(); if ( type == DOM_Node::ENTITY_NODE || type == DOM_Node::NOTATION_NODE || type == DOM_Node::DOCUMENT_TYPE_NODE) return false; } return true; } bool RangeImpl::isAncestorOf(const DOM_Node& a, const DOM_Node& b) { for (DOM_Node node=b; node != null; node=node.getParentNode()) { if (node == a) return true; } 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; if (child.getParentNode() != parent) return (unsigned short)-1; for(DOM_Node node = child.getPreviousSibling(); node!= null; node=node.getPreviousSibling()) { i++; } return i; } void RangeImpl::validateNode(const DOM_Node& node) const { if( fDetached) { throw DOM_DOMException( DOM_DOMException::INVALID_STATE_ERR, null); } if ( !isValidAncestorType(node)) { throw DOM_RangeException( DOM_RangeException::INVALID_NODE_TYPE_ERR, null); } } const DOM_Node RangeImpl::commonAncestorOf(const DOM_Node& pointA, const DOM_Node& pointB) const { if (fDetached) throw DOM_DOMException(DOM_DOMException::INVALID_STATE_ERR, null); if (pointA.getOwnerDocument() != pointB.getOwnerDocument()) throw DOM_DOMException( DOM_DOMException::WRONG_DOCUMENT_ERR, null ); //if the containers are same then it itself is its common ancestor. if (pointA == pointB) return pointA; typedef RefVectorOf VectorNodes; VectorNodes* startV= new (((DocumentImpl*)fDocument.fImpl)->getMemoryManager()) VectorNodes(1, false, ((DocumentImpl*)fDocument.fImpl)->getMemoryManager()); DOM_Node node; for (node=fStartContainer; node != null; node=node.getParentNode()) { startV->addElement(node.fImpl); } VectorNodes* endV = new (((DocumentImpl*)fDocument.fImpl)->getMemoryManager()) VectorNodes(1, false, ((DocumentImpl*)fDocument.fImpl)->getMemoryManager()); for (node=fEndContainer; node != null; node=node.getParentNode()) { endV->addElement(node.fImpl); } int s = startV->size()-1; int e = endV->size()-1; NodeImpl* commonAncestor = 0; while (s>=0 && e>=0) { if (startV->elementAt(s) == endV->elementAt(e)) { commonAncestor = startV->elementAt(s); } else break; --s; --e; } delete startV; delete endV; return DOM_Node(commonAncestor); } void RangeImpl::checkIndex(const DOM_Node& node, unsigned int offset) const { short type = node.getNodeType(); if((type == DOM_Node::TEXT_NODE || type == DOM_Node::CDATA_SECTION_NODE || type == DOM_Node::COMMENT_NODE || type == DOM_Node::PROCESSING_INSTRUCTION_NODE)) { if (offset > node.getNodeValue().length()) throw DOM_DOMException( DOM_DOMException::INDEX_SIZE_ERR, null ); else return; } DOM_Node child = node.getFirstChild(); unsigned int i = 0; for (; child != null; i++) { child = child.getNextSibling(); } if (i < offset) { throw DOM_DOMException( DOM_DOMException::INDEX_SIZE_ERR, null ); } } DOM_Node RangeImpl::nextNode(const DOM_Node& node, bool visitChildren) const { if (node == null) return null; DOM_Node result; if (visitChildren) { result = node.getFirstChild(); if (result != null) { return result; } } // if hasSibling, return sibling result = node.getNextSibling(); if (result != null) { return result; } // return parent's 1st sibling. DOM_Node parent = node.getParentNode(); while ( (parent != null) && (parent != fDocument) ) { result = parent.getNextSibling(); if (result != null) { return result; } else { parent = parent.getParentNode(); if (parent == fEndContainer) return parent; } } // end of list, return null return null; } /** 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 how) { if (fDetached) throw DOM_DOMException(DOM_DOMException::INVALID_STATE_ERR, null); if (fStartContainer == null || fEndContainer == null) { return DOM_DocumentFragment(); // REVIST: Throw exception? } /* 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. */ // case 1: same container if ( fStartContainer == fEndContainer ) return traverseSameContainer( how ); // 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 ); } // 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 ); } // 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 ); } /** * 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(); // If selection is empty, just return the fragment if ( fStartOffset==fEndOffset ) return frag; 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 - fStartOffset)); // 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; } } // 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 ); } 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
* 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 ( 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; } // 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
* 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; } 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); } return n; case DELETE_CONTENTS: n.getParentNode().removeChild(n); return null; } 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 ); } 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()-offset); oldNodeValue = txtValue.substringData( 0, offset ); } else { int offset = getEndOffset(); newNodeValue = txtValue.substringData( 0, offset ); oldNodeValue = txtValue.substringData( offset , fEndContainer.getNodeValue().length()-offset ); } if ( how != CLONE_CONTENTS ) n.setNodeValue( oldNodeValue ); if ( how==DELETE_CONTENTS ) return null; DOM_Node newNode = n.cloneNode( false ); newNode.setNodeValue( newNodeValue ); return newNode; } /** * 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, unsigned int startOffset, unsigned int endOffset) { if ((start == null) || (end == null) ) return; //if both start and end are text check and return if (start.getNodeType() == DOM_Node::TEXT_NODE) { if (start.fImpl->isReadOnly()) { throw DOM_DOMException( DOM_DOMException::NO_MODIFICATION_ALLOWED_ERR, null); } if (start == end) return; } //set the start and end nodes to check DOM_Node sNode = start.getFirstChild(); for(unsigned int i = 0; iisReadOnly()) { throw DOM_DOMException( DOM_DOMException::NO_MODIFICATION_ALLOWED_ERR, null); } if (node.hasChildNodes()) { node = node.getFirstChild(); recurseTreeAndCheck(node, end); } } } DOM_Node RangeImpl::removeChild(DOM_Node& parent, DOM_Node& child) { fRemoveChild = child; //only a precaution measure not to update this range data before removal DOM_Node n = parent.removeChild(child); fRemoveChild = null; return n; } // // Mutation functions // /* This function is called from DOM. * The text has already beeen replaced. * Fix-up any offsets. */ void RangeImpl::receiveReplacedText(NodeImpl* node) { if (node == null) return; DOM_Node anode(node); if (anode == fStartContainer && fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { fStartOffset = 0; } if (anode == fEndContainer && fEndContainer.getNodeType() == DOM_Node::TEXT_NODE) { fEndOffset = 0; } } /** This function is called from DOM. * The text has already beeen inserted. * Fix-up any offsets. */ void RangeImpl::updateRangeForDeletedText(DOM_Node& node, unsigned int offset, int count) { if (node == null) return; if (node == fStartContainer && fStartContainer.getNodeType() == DOM_Node::TEXT_NODE) { if (fStartOffset > offset+count) { fStartOffset = fStartOffset-count; } else if (fStartOffset > offset) { fStartOffset = offset; } } if (node == fEndContainer && fEndContainer.getNodeType() == DOM_Node::TEXT_NODE) { if (fEndOffset > offset+count) { fEndOffset = fEndOffset-count; } else if (fEndOffset > offset) { fEndOffset = offset; } } } /** This function must be called by the DOM _BEFORE_ * a node is deleted, because at that time it is * connected in the DOM tree, which we depend on. */ void RangeImpl::updateRangeForDeletedNode(NodeImpl* node) { if (node == null) return; if (fRemoveChild == node) return; DOM_Node tNode(node); if (node->getParentNode() == fStartContainer.fImpl) { unsigned short index = indexOf(tNode, fStartContainer); if ( fStartOffset > index) { fStartOffset--; } } if (node->getParentNode() == fEndContainer.fImpl) { unsigned short index = indexOf(tNode, fEndContainer); if ( fEndOffset > index) { fEndOffset--; } } if (node->getParentNode() != fStartContainer.fImpl || node->getParentNode() != fEndContainer.fImpl) { if (isAncestorOf(node, fStartContainer)) { DOM_Node tpNode(node->getParentNode()); setStartContainer( tpNode ); fStartOffset = indexOf( tNode, tpNode); } if (isAncestorOf(node, fEndContainer)) { DOM_Node tpNode(node->getParentNode()); setEndContainer( tpNode ); fEndOffset = indexOf( tNode, tpNode); } } } void RangeImpl::updateRangeForInsertedNode(NodeImpl* node) { if (node == null) return; if (node->getParentNode() == fStartContainer.fImpl) { unsigned int index = indexOf(DOM_Node(node), fStartContainer); if (index < fStartOffset) { fStartOffset++; } } if (node->getParentNode() == fEndContainer.fImpl) { unsigned int index = indexOf(DOM_Node(node), fEndContainer); if (index < fEndOffset) { fEndOffset++; } } } void RangeImpl::updateSplitInfo(TextImpl* oldNode, TextImpl* startNode, unsigned int offset) { if (startNode == null) return; DOM_Text oldText(oldNode); DOM_Text newText(startNode); if (fStartContainer == oldText && fStartOffset > offset) { fStartOffset = fStartOffset - offset; fStartContainer = newText; } if (fEndContainer == oldText && fEndOffset > offset) { fEndContainer = newText; fEndOffset = fEndOffset - offset; } } XERCES_CPP_NAMESPACE_END