| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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: DOMRangeImpl.cpp 568078 2007-08-21 11:43:25Z amassari $ |
| */ |
| |
| #include "DOMRangeImpl.hpp" |
| #include "DOMDocumentImpl.hpp" |
| #include "DOMDocumentFragmentImpl.hpp" |
| #include "DOMCommentImpl.hpp" |
| #include "DOMProcessingInstructionImpl.hpp" |
| #include "DOMCasts.hpp" |
| |
| #include <xercesc/dom/DOMException.hpp> |
| #include <xercesc/dom/DOMDocument.hpp> |
| #include <xercesc/dom/DOMRangeException.hpp> |
| #include <xercesc/dom/DOMText.hpp> |
| #include <xercesc/dom/DOMProcessingInstruction.hpp> |
| |
| #include <xercesc/framework/XMLBuffer.hpp> |
| #include <xercesc/util/Janitor.hpp> |
| |
| XERCES_CPP_NAMESPACE_BEGIN |
| |
| |
| //--------------------- |
| // C'tor and D'tor |
| //--------------------- |
| |
| DOMRangeImpl::DOMRangeImpl(DOMDocument* doc, MemoryManager* const manager) |
| |
| : fStartContainer(doc), |
| fStartOffset(0), |
| fEndContainer(doc), |
| fEndOffset(0), |
| fCollapsed(true), |
| fDocument(doc), |
| fDetached(false), |
| fRemoveChild(0), |
| fMemoryManager(manager) |
| { |
| } |
| |
| DOMRangeImpl::DOMRangeImpl(const DOMRangeImpl& other) |
| : DOMRange(other), |
| fStartContainer(other.fStartContainer), |
| fStartOffset(other.fStartOffset), |
| fEndContainer(other.fEndContainer), |
| fEndOffset(other.fEndOffset), |
| fCollapsed(other.fCollapsed), |
| fDocument(other.fDocument), |
| fDetached(other.fDetached), |
| fRemoveChild(other.fRemoveChild), |
| fMemoryManager(other.fMemoryManager) |
| { |
| } |
| |
| DOMRangeImpl::~DOMRangeImpl() |
| { |
| } |
| |
| |
| //------------------------------- |
| // Public getter functions |
| //------------------------------- |
| |
| |
| DOMNode* DOMRangeImpl::getStartContainer() const |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| return fStartContainer; |
| } |
| |
| XMLSize_t DOMRangeImpl::getStartOffset() const |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| return fStartOffset; |
| } |
| |
| DOMNode* DOMRangeImpl::getEndContainer() const |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| return fEndContainer; |
| } |
| |
| XMLSize_t DOMRangeImpl::getEndOffset() const |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| return fEndOffset; |
| } |
| |
| |
| |
| bool DOMRangeImpl::getCollapsed() const |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| return ((fStartContainer == fEndContainer) |
| && (fStartOffset == fEndOffset)); |
| } |
| |
| //------------------------------- |
| // Public setter functions |
| //------------------------------- |
| |
| void DOMRangeImpl::setStartContainer(const DOMNode* node) |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| fStartContainer = (DOMNode*) node; |
| } |
| |
| void DOMRangeImpl::setStartOffset(XMLSize_t offset) |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| fStartOffset = offset; |
| } |
| |
| void DOMRangeImpl::setEndContainer(const DOMNode* node) |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| fEndContainer = (DOMNode*) node; |
| |
| } |
| |
| void DOMRangeImpl::setEndOffset(XMLSize_t offset) |
| { |
| if (fDetached) |
| { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| fEndOffset = offset; |
| } |
| |
| void DOMRangeImpl::setStart(const DOMNode* refNode, XMLSize_t offset) |
| { |
| validateNode(refNode); |
| checkIndex(refNode, offset); |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(true); //collapse the range positions to start |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fStartContainer = (DOMNode*) refNode; |
| fStartOffset = offset; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fEndContainer)) |
| collapse(true); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(true); //collapse the range positions to start |
| else |
| fCollapsed = false; |
| } |
| |
| void DOMRangeImpl::setEnd(const DOMNode* refNode, XMLSize_t offset) |
| { |
| validateNode(refNode); |
| checkIndex(refNode, offset); |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(false); //collapse the range positions to end |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fEndContainer = (DOMNode*) refNode; |
| fEndOffset = offset; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fStartContainer)) |
| collapse(false); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(false); //collapse the range positions to end |
| else |
| fCollapsed = false; |
| } |
| |
| void DOMRangeImpl::setStartBefore(const DOMNode* refNode) |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(true); //collapse the range positions to start |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fStartContainer = refNode->getParentNode(); |
| XMLSize_t i = 0; |
| for (DOMNode* n = (DOMNode*) refNode; n!=0; n = n->getPreviousSibling()) { |
| i++; |
| } |
| if (i == 0) |
| fStartOffset = 0; |
| else |
| fStartOffset = i-1; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fEndContainer)) |
| collapse(true); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(true); //collapse the range positions to start |
| else |
| fCollapsed = false; |
| } |
| |
| void DOMRangeImpl::setStartAfter(const DOMNode* refNode) |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(true); //collapse the range positions to start |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fStartContainer = refNode->getParentNode(); |
| XMLSize_t i = 0; |
| for (DOMNode* n = (DOMNode*) refNode; n!=0; n = n->getPreviousSibling()) { |
| i++; |
| } |
| |
| fStartOffset = i; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fEndContainer)) |
| collapse(true); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(true); //collapse the range positions to start |
| else |
| fCollapsed = false; |
| } |
| |
| void DOMRangeImpl::setEndBefore(const DOMNode* refNode) |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(false); //collapse the range positions to end |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fEndContainer = refNode->getParentNode(); |
| XMLSize_t i = 0; |
| for (DOMNode* n = (DOMNode*) refNode; n!=0; n = n->getPreviousSibling(), i++) ; |
| |
| if (i< 1) |
| fEndOffset = 0; |
| else |
| fEndOffset = i-1; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fStartContainer)) |
| collapse(false); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(false); //collapse the range positions to end |
| else |
| fCollapsed = false; |
| } |
| |
| void DOMRangeImpl::setEndAfter(const DOMNode* refNode) |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| if ( !hasLegalRootContainer(refNode) || !isLegalContainedNode(refNode)) { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| // error if not the same owner document |
| if (fDocument != refNode->getOwnerDocument()) { |
| if ( refNode != fDocument ) { |
| collapse(false); //collapse the range positions to end |
| fCollapsed = true; |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| fEndContainer = refNode->getParentNode(); |
| XMLSize_t i = 0; |
| for (DOMNode* n = (DOMNode*) refNode; n!=0; n = n->getPreviousSibling(), i++) ; |
| |
| if (i ==0) |
| fEndOffset = 0; |
| else |
| fEndOffset = i; |
| |
| // they may be of same document, but not same root container |
| // collapse if not the same root container |
| if (!commonAncestorOf(refNode, fStartContainer)) |
| collapse(false); |
| |
| //compare the start and end boundary point |
| //collapse if start point is after the end point |
| if(compareBoundaryPoints(DOMRange::END_TO_START, this) == 1) |
| collapse(false); //collapse the range positions to end |
| else |
| fCollapsed = false; |
| } |
| //------------------------------- |
| // Public Misc. functions |
| //------------------------------- |
| void DOMRangeImpl::detach() |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| ((DOMDocumentImpl *)fDocument)->removeRange(this); |
| |
| fDetached = true; |
| |
| //0ify nodes |
| fStartContainer = 0; |
| fStartOffset = 0; |
| fEndContainer = 0; |
| fEndOffset = 0; |
| fCollapsed = true; |
| |
| fRemoveChild = 0; |
| } |
| |
| void DOMRangeImpl::collapse(bool toStart) |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| if (toStart) { |
| fEndContainer = fStartContainer; |
| fEndOffset = fStartOffset; |
| } else { |
| fStartContainer = fEndContainer; |
| fStartOffset = fEndOffset; |
| } |
| fCollapsed = true; |
| } |
| |
| void DOMRangeImpl::selectNode(const DOMNode* refNode) |
| { |
| validateNode(refNode); |
| if ( !isLegalContainedNode(refNode)) { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| //First check for the text type node |
| short type = refNode->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| //The node itself is the container. |
| fStartContainer = (DOMNode*) refNode; |
| fEndContainer = (DOMNode*) refNode; |
| |
| //Select all the contents of the node |
| fStartOffset = 0; |
| if (type == DOMNode::PROCESSING_INSTRUCTION_NODE) |
| fEndOffset = XMLString::stringLen(((DOMProcessingInstruction*)refNode)->getData()); |
| else |
| fEndOffset = ((DOMText *)refNode)->getLength(); |
| return; |
| } |
| |
| DOMNode* parent = refNode->getParentNode(); |
| if (parent != 0 ) // REVIST: what to do if it IS 0? |
| { |
| fStartContainer = parent; |
| fEndContainer = parent; |
| |
| XMLSize_t i = 0; |
| for (DOMNode* n = parent->getFirstChild(); n!=0 && n!=refNode; n = n->getNextSibling()) { |
| i++; |
| } |
| |
| fStartOffset = i; |
| fEndOffset = fStartOffset+1; |
| } |
| } |
| |
| void DOMRangeImpl::selectNodeContents(const DOMNode* node) |
| { |
| validateNode(node); |
| |
| fStartContainer = (DOMNode*) node; |
| fEndContainer = (DOMNode*) node; |
| |
| fStartOffset = 0; |
| short type = node->getNodeType(); |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE)) { |
| |
| fEndOffset = ((DOMText *)node)->getLength(); |
| return; |
| } |
| if (type == DOMNode::PROCESSING_INSTRUCTION_NODE) { |
| fEndOffset = XMLString::stringLen(((DOMProcessingInstruction*)node)->getData()); |
| return; |
| } |
| |
| DOMNode* first = node->getFirstChild(); |
| if (first == 0) { |
| fEndOffset = 0; |
| return; |
| } |
| XMLSize_t i = 0; |
| for (DOMNode* n = first; n!=0; n = n->getNextSibling()) { |
| i++; |
| } |
| fEndOffset = i; |
| } |
| |
| void DOMRangeImpl::surroundContents(DOMNode* newParent) |
| { |
| if (newParent==0) return; |
| |
| //check for elimination criteria |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| if (newParent->getOwnerDocument() !=fDocument) { |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| |
| int type = newParent->getNodeType(); |
| if ( !isLegalContainedNode(newParent) |
| || type == DOMNode::DOCUMENT_TYPE_NODE) |
| { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| DOMNode* realStart = fStartContainer; |
| DOMNode* realEnd = fEndContainer; |
| |
| type = fStartContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| realStart = fStartContainer->getParentNode(); |
| } |
| type = fEndContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| realEnd = fEndContainer->getParentNode(); |
| } |
| |
| if (realStart != realEnd) { |
| throw DOMRangeException( |
| DOMRangeException::BAD_BOUNDARYPOINTS_ERR, 0, fMemoryManager); |
| } |
| |
| DOMDocumentFragment* frag = (DOMDocumentFragment*) extractContents(); |
| insertNode(newParent); |
| newParent->appendChild(frag); |
| selectNode(newParent); |
| } |
| |
| |
| short DOMRangeImpl::compareBoundaryPoints(DOMRange::CompareHow how, const DOMRange* srcRange) const |
| { |
| if (fDocument != ((DOMRangeImpl*)srcRange)->fDocument) { |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| DOMNode* pointA; |
| DOMNode* pointB; |
| int offsetA, offsetB; |
| |
| switch (how) |
| { |
| case (DOMRange::START_TO_START) : |
| pointB = srcRange->getStartContainer(); |
| pointA = fStartContainer; |
| offsetB = srcRange->getStartOffset(); |
| offsetA = fStartOffset; |
| break; |
| case (DOMRange::START_TO_END) : |
| pointB = srcRange->getStartContainer(); |
| pointA = fEndContainer; |
| offsetB = srcRange->getStartOffset(); |
| offsetA = fEndOffset; |
| break; |
| case (DOMRange::END_TO_START) : |
| pointB = srcRange->getEndContainer(); |
| pointA = fStartContainer; |
| offsetB = srcRange->getEndOffset(); |
| offsetA = fStartOffset; |
| break; |
| case (DOMRange::END_TO_END) : |
| pointB = srcRange->getEndContainer(); |
| pointA = fEndContainer; |
| offsetB = srcRange->getEndOffset(); |
| offsetA = fEndOffset; |
| break; |
| default: |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| // 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 (DOMNode* node = pointA->getFirstChild(); node != 0; 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 (DOMNode* nd = pointB->getFirstChild(); nd != 0; 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. |
| // Instead of literally walking the context tree in pre-order, |
| // we use relative node depth walking which is usually faster |
| |
| int depthDiff = 0; |
| DOMNode* n = 0; |
| for ( n = pointB; n != 0; n = n->getParentNode() ) |
| depthDiff++; |
| for ( n = pointA; n != 0; n = n->getParentNode() ) |
| depthDiff--; |
| while (depthDiff > 0) { |
| pointB = pointB->getParentNode(); |
| depthDiff--; |
| } |
| while (depthDiff < 0) { |
| pointA = pointA->getParentNode(); |
| depthDiff++; |
| } |
| for (DOMNode* pB = pointB->getParentNode(), |
| *pA = pointA->getParentNode(); |
| pB != pA; |
| pB = pB->getParentNode(), pA = pA->getParentNode() ) |
| { |
| pointB = pB; |
| pointA = pA; |
| } |
| for ( n = pointB->getNextSibling(); |
| n != 0; |
| n = n->getNextSibling() ) |
| { |
| if (n == pointA) { |
| return 1; |
| } |
| } |
| return -1; |
| } |
| |
| |
| void DOMRangeImpl:: deleteContents() |
| { |
| traverseContents(DELETE_CONTENTS); |
| } |
| |
| DOMDocumentFragment* DOMRangeImpl::extractContents() |
| { |
| checkReadOnly(fStartContainer, fEndContainer, fStartOffset, fEndOffset); |
| return traverseContents(EXTRACT_CONTENTS); |
| } |
| |
| DOMDocumentFragment* DOMRangeImpl::cloneContents() const |
| { |
| // cast off const. |
| return ((DOMRangeImpl *)this)->traverseContents(CLONE_CONTENTS); |
| } |
| |
| |
| void DOMRangeImpl::insertNode(DOMNode* newNode) |
| { |
| if (newNode == 0) return; //don't have to do anything |
| |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| int type = newNode->getNodeType(); |
| if (type == DOMNode::ATTRIBUTE_NODE |
| || type == DOMNode::ENTITY_NODE |
| || type == DOMNode::NOTATION_NODE |
| || type == DOMNode::DOCUMENT_NODE) |
| { |
| throw DOMRangeException( |
| DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| |
| // Prevent cycles in the tree. |
| //isKidOK() is not checked here as its taken care by insertBefore() function |
| if (isAncestorOf( newNode, fStartContainer)) { |
| throw DOMException( |
| DOMException::HIERARCHY_REQUEST_ERR, 0, fMemoryManager); |
| } |
| |
| for (DOMNode* aNode = fStartContainer; aNode!=0; aNode = aNode->getParentNode()) { |
| if (castToNodeImpl(newNode)->isReadOnly()) { |
| throw DOMException( |
| DOMException::NO_MODIFICATION_ALLOWED_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| if (fDocument != newNode->getOwnerDocument()) { |
| throw DOMException( |
| DOMException::WRONG_DOCUMENT_ERR, 0, fMemoryManager); |
| } |
| |
| |
| DOMNode* parent; |
| DOMNode* next; |
| |
| type = fStartContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| |
| //set 'parent' and 'next' here |
| parent = fStartContainer->getParentNode(); |
| |
| //split the text nodes |
| if (fStartOffset > 0) { |
| if (type == DOMNode::COMMENT_NODE) |
| ((DOMCommentImpl*)fStartContainer)->splitText(fStartOffset); |
| else if (type == DOMNode::PROCESSING_INSTRUCTION_NODE) |
| ((DOMProcessingInstructionImpl*)fStartContainer)->splitText(fStartOffset); |
| else |
| ((DOMText*)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(XMLSize_t i = 0; (i < fStartOffset) && (next != 0); i++) { |
| next=next->getNextSibling(); |
| } |
| } |
| |
| if (parent != 0) { |
| if (next != 0) |
| parent->insertBefore(newNode, next); |
| else |
| parent->appendChild(newNode); |
| } |
| } |
| |
| DOMRange* DOMRangeImpl::cloneRange() const |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| DOMRange* range = fDocument->createRange(); |
| range->setStart(fStartContainer, fStartOffset); |
| range->setEnd(fEndContainer, fEndOffset); |
| |
| return range; |
| } |
| |
| const XMLCh* DOMRangeImpl::toString() const |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| if ((fStartContainer == fEndContainer) && (fEndOffset == fStartOffset)) |
| return XMLUni::fgZeroLenString; |
| |
| DOMNode* node = fStartContainer; |
| DOMNode* stopNode = fEndContainer; |
| |
| XMLBuffer retStringBuf(1023, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| short type = fStartContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| if (fStartContainer == fEndContainer) { |
| XMLCh* tempString; |
| XMLCh temp[4000]; |
| if ((fEndOffset-fStartOffset) >= 3999) |
| tempString = (XMLCh*) fMemoryManager->allocate |
| ( |
| (fEndOffset - fStartOffset + 1) * sizeof(XMLCh) |
| );//new XMLCh[fEndOffset-fStartOffset+1]; |
| else |
| tempString = temp; |
| |
| XMLString::subString(tempString, fStartContainer->getNodeValue(), fStartOffset, fEndOffset, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| const XMLCh* retString = ((DOMDocumentImpl *)fDocument)->getPooledString(tempString); |
| |
| if ((fEndOffset-fStartOffset) >= 3999) |
| fMemoryManager->deallocate(tempString);//delete[] tempString; |
| |
| return retString; |
| } else { |
| XMLSize_t length = XMLString::stringLen(fStartContainer->getNodeValue()); |
| if (length != fStartOffset) { |
| |
| XMLCh* tempString; |
| XMLCh temp[4000]; |
| if ((length - fStartOffset) >= 3999) |
| tempString = (XMLCh*) fMemoryManager->allocate |
| ( |
| (length - fStartOffset + 1) * sizeof(XMLCh) |
| );//new XMLCh[length - fStartOffset+1]; |
| else |
| tempString = temp; |
| |
| XMLString::subString(tempString, fStartContainer->getNodeValue(), fStartOffset, length, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| retStringBuf.append(tempString); |
| |
| if ((length - fStartOffset) >= 3999) |
| fMemoryManager->deallocate(tempString);//delete[] tempString; |
| } |
| |
| 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 |
| XMLSize_t counter = 0; |
| while (counter<fStartOffset && node!=0) { |
| node=node->getNextSibling(); |
| counter++; |
| } |
| } |
| if (node == 0) { |
| node = nextNode(fStartContainer,false); |
| } |
| } |
| |
| type = fEndContainer->getNodeType(); |
| if((type != DOMNode::TEXT_NODE |
| && type != DOMNode::CDATA_SECTION_NODE |
| && type != DOMNode::COMMENT_NODE |
| && type != DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| int i=fEndOffset; |
| stopNode = fEndContainer->getFirstChild(); |
| while( i>0 && stopNode!=0 ){ |
| --i; |
| stopNode = stopNode->getNextSibling(); |
| } |
| if ( stopNode == 0 ) |
| stopNode = nextNode( fEndContainer, false ); |
| } |
| |
| while (node != stopNode) { //look into all kids of the Range |
| if (node == 0) break; |
| type = node->getNodeType(); |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| retStringBuf.append(node->getNodeValue()); |
| } |
| node = nextNode(node, true); |
| } |
| |
| type = fEndContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| |
| if (fEndOffset != 0) { |
| |
| XMLCh* tempString; |
| XMLCh temp[4000]; |
| if (fEndOffset >= 3999) |
| tempString = (XMLCh*) fMemoryManager->allocate |
| ( |
| (fEndOffset+1) * sizeof(XMLCh) |
| );//new XMLCh[fEndOffset+1]; |
| else |
| tempString = temp; |
| |
| XMLString::subString(tempString, fEndContainer->getNodeValue(), 0, fEndOffset, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| retStringBuf.append(tempString); |
| |
| if (fEndOffset >= 3999) |
| fMemoryManager->deallocate(tempString);//delete[] tempString; |
| } |
| } |
| return ((DOMDocumentImpl *)fDocument)->getPooledString(retStringBuf.getRawBuffer()); |
| } |
| |
| DOMDocument* DOMRangeImpl::getDocument() |
| { |
| return fDocument; |
| } |
| |
| const DOMNode* DOMRangeImpl::getCommonAncestorContainer() const |
| { |
| return commonAncestorOf(fStartContainer, fEndContainer); |
| |
| } |
| |
| void DOMRangeImpl::release() |
| { |
| detach(); |
| // for performance reason, do not recycle pointer |
| // chance that this is allocated again and again is not usual |
| } |
| |
| //--------------------- |
| //private functions |
| //--------------------- |
| |
| bool DOMRangeImpl::isValidAncestorType(const DOMNode* node) const |
| { |
| for (DOMNode* aNode = (DOMNode*) node; aNode!=0; aNode = aNode->getParentNode()) { |
| short type = aNode->getNodeType(); |
| if ( type == DOMNode::ENTITY_NODE |
| || type == DOMNode::NOTATION_NODE |
| || type == DOMNode::DOCUMENT_TYPE_NODE) |
| return false; |
| } |
| return true; |
| } |
| |
| bool DOMRangeImpl::isAncestorOf(const DOMNode* a, const DOMNode* b) { |
| for (DOMNode* node = (DOMNode*) b; node != 0; node=node->getParentNode()) { |
| if (node == a) return true; |
| } |
| return false; |
| } |
| |
| bool DOMRangeImpl::hasLegalRootContainer(const DOMNode* node) const { |
| if ( node==0 ) |
| return false; |
| |
| DOMNode* rootContainer = (DOMNode*)node; |
| for (; rootContainer->getParentNode()!=0; rootContainer = rootContainer->getParentNode()) |
| ; |
| |
| switch( rootContainer->getNodeType() ) { |
| case DOMNode::ATTRIBUTE_NODE: |
| case DOMNode::DOCUMENT_NODE: |
| case DOMNode::DOCUMENT_FRAGMENT_NODE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool DOMRangeImpl::isLegalContainedNode(const DOMNode* node ) const { |
| if ( node==0 ) |
| return false; |
| switch( node->getNodeType() ) |
| { |
| case DOMNode::DOCUMENT_NODE: |
| case DOMNode::DOCUMENT_FRAGMENT_NODE: |
| case DOMNode::ATTRIBUTE_NODE: |
| case DOMNode::ENTITY_NODE: |
| case DOMNode::NOTATION_NODE: |
| return false; |
| default: |
| return true; |
| } |
| } |
| |
| XMLSize_t DOMRangeImpl::indexOf(const DOMNode* child, const DOMNode* parent) const |
| { |
| XMLSize_t i = 0; |
| if (child->getParentNode() != parent) return (XMLSize_t)-1; |
| for(DOMNode* node = child->getPreviousSibling(); node!= 0; node=node->getPreviousSibling()) { |
| i++; |
| } |
| return i; |
| } |
| |
| void DOMRangeImpl::validateNode(const DOMNode* node) const |
| { |
| if( fDetached) { |
| throw DOMException( |
| DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| } |
| |
| if ( !isValidAncestorType(node)) { |
| throw DOMRangeException(DOMRangeException::INVALID_NODE_TYPE_ERR, 0, fMemoryManager); |
| } |
| } |
| |
| |
| const DOMNode* DOMRangeImpl::commonAncestorOf(const DOMNode* pointA, const DOMNode* pointB) const |
| { |
| if (fDetached) |
| throw DOMException(DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| |
| //if the containers are same then it itself is its common ancestor. |
| if (pointA == pointB) |
| return pointA; |
| |
| typedef RefVectorOf<DOMNode> VectorNodes; |
| VectorNodes startV(1, false, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| DOMNode* node; |
| |
| for (node=(DOMNode*)pointA; node != 0; node=node->getParentNode()) |
| { |
| startV.addElement(node); |
| } |
| VectorNodes endV(1, false, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| for (node=(DOMNode*)pointB; node != 0; node=node->getParentNode()) |
| { |
| endV.addElement(node); |
| } |
| |
| int s = startV.size()-1; |
| int e = endV.size()-1; |
| |
| DOMNode* commonAncestor = 0; |
| |
| while (s>=0 && e>=0) { |
| if (startV.elementAt(s) == endV.elementAt(e)) { |
| commonAncestor = startV.elementAt(s); |
| } |
| else break; |
| --s; |
| --e; |
| } |
| |
| return commonAncestor; |
| } |
| |
| void DOMRangeImpl::checkIndex(const DOMNode* node, XMLSize_t offset) const |
| { |
| short type = node->getNodeType(); |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) { |
| if (offset > XMLString::stringLen(node->getNodeValue())) |
| throw DOMException( DOMException::INDEX_SIZE_ERR, 0, fMemoryManager ); |
| else return; |
| } |
| |
| DOMNode* child = node->getFirstChild(); |
| XMLSize_t i = 0; |
| for (; child != 0; i++) { |
| child = child->getNextSibling(); |
| } |
| if (i < offset) { |
| throw DOMException( DOMException::INDEX_SIZE_ERR, 0, fMemoryManager ); |
| } |
| |
| } |
| |
| DOMNode* DOMRangeImpl::nextNode(const DOMNode* node, bool visitChildren) const |
| { |
| |
| if (node == 0) return 0; |
| |
| DOMNode* result; |
| if (visitChildren) { |
| result = node->getFirstChild(); |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| // if hasSibling, return sibling |
| result = node->getNextSibling(); |
| if (result != 0) { |
| return result; |
| } |
| |
| |
| // return parent's 1st sibling. |
| DOMNode* parent = node->getParentNode(); |
| |
| |
| while ( (parent != 0) && (parent != fDocument) ) |
| { |
| result = parent->getNextSibling(); |
| if (result != 0) { |
| return result; |
| } else { |
| parent = parent->getParentNode(); |
| } |
| |
| } |
| // end of list, return 0 |
| return 0; |
| } |
| |
| |
| /** 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. |
| */ |
| DOMDocumentFragment* DOMRangeImpl::traverseContents(TraversalType how) |
| { |
| if (fDetached) |
| throw DOMException(DOMException::INVALID_STATE_ERR, 0, fMemoryManager); |
| |
| if (fStartContainer == 0 || fEndContainer == 0) { |
| return 0; // 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 |
| // This can be quickly tested by walking the parent chain of |
| // end container |
| int endContainerDepth = 0; |
| for ( DOMNode* c = fEndContainer, *p = c->getParentNode(); |
| p != 0; |
| c = p, p = p->getParentNode()) |
| { |
| if (p == fStartContainer) |
| return traverseCommonStartContainer( c, how ); |
| ++endContainerDepth; |
| } |
| |
| // case 3: Child C of end container is ancestor of start container |
| // This can be quickly tested by walking the parent chain of A |
| int startContainerDepth = 0; |
| for ( DOMNode* c2 = fStartContainer, *p2 = c2->getParentNode(); |
| p2 != 0; |
| c2 = p2, p2 = p2->getParentNode()) |
| { |
| if (p2 == fEndContainer) |
| return traverseCommonEndContainer( c2, how ); |
| ++startContainerDepth; |
| } |
| |
| // case 4: There is a common ancestor container. Find the |
| // ancestor siblings that are children of that container. |
| int depthDiff = startContainerDepth - endContainerDepth; |
| |
| DOMNode* startNode = fStartContainer; |
| while (depthDiff > 0) { |
| startNode = startNode->getParentNode(); |
| depthDiff--; |
| } |
| |
| DOMNode* endNode = fEndContainer; |
| while (depthDiff < 0) { |
| endNode = endNode->getParentNode(); |
| depthDiff++; |
| } |
| |
| // ascend the ancestor hierarchy until we have a common parent. |
| for( DOMNode* sp = startNode->getParentNode(), *ep = endNode->getParentNode(); |
| sp!=ep; |
| sp = sp->getParentNode(), ep = ep->getParentNode() ) |
| { |
| startNode = sp; |
| endNode = ep; |
| } |
| return traverseCommonAncestors( startNode, endNode, how ); |
| } |
| |
| /** |
| * Visits the nodes selected by this range when we know |
| * a-priori that the start and end containers are the same. |
| * |
| */ |
| DOMDocumentFragment* DOMRangeImpl::traverseSameContainer( int how ) |
| { |
| DOMDocumentFragment* frag = 0; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument->createDocumentFragment(); |
| |
| // If selection is empty, just return the fragment |
| if ( fStartOffset==fEndOffset ) |
| return frag; |
| |
| DOMNode* cloneCurrent = 0; |
| |
| // Text node needs special case handling |
| short type = fStartContainer->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| cloneCurrent = fStartContainer->cloneNode(false); |
| if (fEndOffset == fStartOffset) { |
| cloneCurrent->setNodeValue(XMLUni::fgZeroLenString); |
| } |
| else { |
| XMLCh* tempString; |
| XMLCh temp[4000]; |
| if (fEndOffset >= 3999) |
| tempString = (XMLCh*) fMemoryManager->allocate |
| ( |
| (fEndOffset+1) * sizeof(XMLCh) |
| );//new XMLCh[fEndOffset+1]; |
| else |
| tempString = temp; |
| |
| XMLString::subString(tempString, cloneCurrent->getNodeValue(), fStartOffset, fEndOffset, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| cloneCurrent->setNodeValue(((DOMDocumentImpl *)fDocument)->getPooledString(tempString)); |
| |
| if (fEndOffset >= 3999) |
| fMemoryManager->deallocate(tempString);//delete[] tempString; |
| } |
| |
| // set the original text node to its new value |
| if ( how != CLONE_CONTENTS ) { |
| if(type == DOMNode::PROCESSING_INSTRUCTION_NODE) { |
| ((DOMProcessingInstructionImpl*)fStartContainer)->deleteData(fStartOffset, fEndOffset-fStartOffset); |
| } |
| else |
| ((DOMCharacterData*)fStartContainer)->deleteData(fStartOffset, fEndOffset-fStartOffset); |
| } |
| if ( how != DELETE_CONTENTS) |
| frag->appendChild(cloneCurrent); |
| } |
| else { |
| // Copy nodes between the start/end offsets. |
| DOMNode* n = getSelectedNode( fStartContainer, fStartOffset ); |
| int cnt = fEndOffset - fStartOffset; |
| while( cnt > 0 && n) |
| { |
| DOMNode* sibling = n->getNextSibling(); |
| DOMNode* xferNode = traverseFullySelected( n, how ); |
| if ( frag!=0 ) |
| 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 |
| * |
| */ |
| DOMDocumentFragment* DOMRangeImpl::traverseCommonStartContainer( DOMNode*endAncestor, int how ) |
| { |
| DOMDocumentFragment* frag = 0; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument->createDocumentFragment(); |
| DOMNode*n = traverseRightBoundary( endAncestor, how ); |
| if ( frag!=0 ) |
| 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 ) |
| { |
| DOMNode* sibling = n->getPreviousSibling(); |
| DOMNode* xferNode = traverseFullySelected( n, how ); |
| if ( frag!=0 ) |
| 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 |
| * |
| */ |
| DOMDocumentFragment* DOMRangeImpl::traverseCommonEndContainer( DOMNode*startAncestor, int how ) |
| { |
| DOMDocumentFragment* frag = 0; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument->createDocumentFragment(); |
| DOMNode* n = traverseLeftBoundary( startAncestor, how ); |
| if ( frag!=0 ) |
| frag->appendChild( n ); |
| int startIdx = indexOf( startAncestor, fEndContainer ); |
| ++startIdx; // Because we already traversed it.... |
| |
| int cnt = fEndOffset - startIdx; |
| n = startAncestor->getNextSibling(); |
| while( cnt > 0 ) |
| { |
| DOMNode* sibling = n->getNextSibling(); |
| DOMNode* xferNode = traverseFullySelected( n, how ); |
| if ( frag!=0 ) |
| 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. |
| */ |
| DOMDocumentFragment* DOMRangeImpl::traverseCommonAncestors( DOMNode*startAncestor, DOMNode*endAncestor, int how ) |
| { |
| DOMDocumentFragment* frag = 0; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument->createDocumentFragment(); |
| |
| DOMNode*n = traverseLeftBoundary( startAncestor, how ); |
| if ( frag!=0 ) |
| frag->appendChild( n ); |
| |
| DOMNode*commonParent = startAncestor->getParentNode(); |
| int startOffset = indexOf( startAncestor, commonParent ); |
| int endOffset = indexOf( endAncestor, commonParent ); |
| ++startOffset; |
| |
| int cnt = endOffset - startOffset; |
| DOMNode* sibling = startAncestor->getNextSibling(); |
| |
| while( cnt > 0 ) |
| { |
| DOMNode* nextSibling = sibling->getNextSibling(); |
| n = traverseFullySelected( sibling, how ); |
| if ( frag!=0 ) |
| frag->appendChild( n ); |
| sibling = nextSibling; |
| --cnt; |
| } |
| |
| n = traverseRightBoundary( endAncestor, how ); |
| if ( frag!=0 ) |
| 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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traverseRightBoundary( DOMNode*root, int how ) |
| { |
| DOMNode*next = getSelectedNode( fEndContainer, fEndOffset-1 ); |
| bool isFullySelected = ( next!=fEndContainer ); |
| |
| if ( next==root ) |
| return traverseNode( next, isFullySelected, false, how ); |
| |
| DOMNode*parent = next->getParentNode(); |
| DOMNode*clonedParent = traverseNode( parent, false, false, how ); |
| |
| while( parent!=0 ) |
| { |
| while( next!=0 ) |
| { |
| DOMNode* prevSibling = next->getPreviousSibling(); |
| DOMNode* 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(); |
| DOMNode* clonedGrandParent = traverseNode( parent, false, false, how ); |
| if ( how!=DELETE_CONTENTS ) |
| clonedGrandParent->appendChild( clonedParent ); |
| clonedParent = clonedGrandParent; |
| |
| } |
| |
| // should never occur |
| return 0; |
| } |
| |
| /** |
| * 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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traverseLeftBoundary( DOMNode*root, int how ) |
| { |
| DOMNode*next = getSelectedNode( getStartContainer(), getStartOffset() ); |
| bool isFullySelected = ( next!=getStartContainer() ); |
| |
| if ( next==root ) |
| return traverseNode( next, isFullySelected, true, how ); |
| |
| DOMNode* parent = next->getParentNode(); |
| DOMNode* clonedParent = traverseNode( parent, false, true, how ); |
| |
| while( parent!=0 ) |
| { |
| while( next!=0 ) |
| { |
| DOMNode* nextSibling = next->getNextSibling(); |
| DOMNode* 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(); |
| DOMNode* clonedGrandParent = traverseNode( parent, false, true, how ); |
| if ( how!=DELETE_CONTENTS ) |
| clonedGrandParent->appendChild( clonedParent ); |
| clonedParent = clonedGrandParent; |
| |
| } |
| |
| // should never occur |
| return 0; |
| |
| } |
| |
| /** |
| * 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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traverseNode( DOMNode* n, bool isFullySelected, bool isLeft, int how ) |
| { |
| if ( isFullySelected ) |
| return traverseFullySelected( n, how ); |
| |
| short type = n->getNodeType(); |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traverseFullySelected( DOMNode* n, int how ) |
| { |
| switch( how ) |
| { |
| case CLONE_CONTENTS: |
| return n->cloneNode( true ); |
| case EXTRACT_CONTENTS: |
| return n; |
| case DELETE_CONTENTS: |
| // revisit: |
| // should I release the removed node? |
| // not released in case user still referencing it externally |
| n->getParentNode()->removeChild(n); |
| return 0; |
| } |
| return 0; |
| } |
| |
| /** |
| * Utility method for traversing a single node when |
| * we know a-priori that the node if partially |
| * selected and is not a text node. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traversePartiallySelected( DOMNode*n, int how ) |
| { |
| switch( how ) |
| { |
| case DELETE_CONTENTS: |
| return 0; |
| case CLONE_CONTENTS: |
| case EXTRACT_CONTENTS: |
| return n->cloneNode( false ); |
| } |
| return 0; |
| } |
| |
| /** |
| * 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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::traverseTextNode( DOMNode*n, bool isLeft, int how ) |
| { |
| XMLCh* txtValue = XMLString::replicate(n->getNodeValue(), fMemoryManager); |
| ArrayJanitor<XMLCh> janValue(txtValue, fMemoryManager); |
| |
| if ( isLeft ) |
| { |
| int startLen = XMLString::stringLen(fStartContainer->getNodeValue()); |
| int offset = getStartOffset(); |
| |
| if (offset == 0) { |
| if ( how != CLONE_CONTENTS ) |
| n->setNodeValue(XMLUni::fgZeroLenString); |
| } |
| else { |
| XMLCh* oldNodeValue; |
| XMLCh oldTemp[4000]; |
| |
| if (offset >= 3999) { |
| oldNodeValue = (XMLCh*) fMemoryManager->allocate |
| ( |
| (offset+1) * sizeof(XMLCh) |
| );//new XMLCh[offset+1]; |
| } |
| else { |
| oldNodeValue = oldTemp; |
| } |
| XMLString::subString(oldNodeValue, txtValue, 0, offset, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| |
| if ( how != CLONE_CONTENTS ) |
| n->setNodeValue( ((DOMDocumentImpl *)fDocument)->getPooledString(oldNodeValue) ); |
| |
| if (offset>= 3999) |
| fMemoryManager->deallocate(oldNodeValue);//delete[] oldNodeValue; |
| } |
| |
| if ( how==DELETE_CONTENTS ) |
| return 0; |
| |
| DOMNode* newNode = n->cloneNode( false ); |
| |
| if (startLen == offset) { |
| newNode->setNodeValue(XMLUni::fgZeroLenString); |
| } |
| else { |
| XMLCh* newNodeValue; |
| XMLCh newTemp[4000]; |
| |
| if (offset >= 3999) { |
| newNodeValue = (XMLCh*) fMemoryManager->allocate |
| ( |
| (offset+1) * sizeof(XMLCh) |
| );//new XMLCh[offset+1]; |
| } |
| else { |
| newNodeValue = newTemp; |
| } |
| XMLString::subString(newNodeValue, txtValue, offset, startLen, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| newNode->setNodeValue( ((DOMDocumentImpl *)fDocument)->getPooledString(newNodeValue) ); |
| |
| if (offset>= 3999) |
| fMemoryManager->deallocate(newNodeValue);//delete[] newNodeValue; |
| |
| } |
| return newNode; |
| } |
| else |
| { |
| int endLen = XMLString::stringLen(fEndContainer->getNodeValue()); |
| int offset = getEndOffset(); |
| |
| if (endLen == offset) { |
| if ( how != CLONE_CONTENTS ) |
| n->setNodeValue(XMLUni::fgZeroLenString); |
| } |
| else { |
| XMLCh* oldNodeValue; |
| XMLCh oldTemp[4000]; |
| |
| if (offset >= 3999) { |
| oldNodeValue = (XMLCh*) fMemoryManager->allocate |
| ( |
| (offset+1) * sizeof(XMLCh) |
| );//new XMLCh[offset+1]; |
| } |
| else { |
| oldNodeValue = oldTemp; |
| } |
| XMLString::subString(oldNodeValue, txtValue, offset, endLen, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| |
| if ( how != CLONE_CONTENTS ) |
| n->setNodeValue( ((DOMDocumentImpl *)fDocument)->getPooledString(oldNodeValue) ); |
| |
| if (offset>= 3999) |
| fMemoryManager->deallocate(oldNodeValue);//delete[] oldNodeValue; |
| } |
| |
| if ( how==DELETE_CONTENTS ) |
| return 0; |
| |
| DOMNode* newNode = n->cloneNode( false ); |
| |
| if (offset == 0) { |
| newNode->setNodeValue(XMLUni::fgZeroLenString); |
| } |
| else { |
| XMLCh* newNodeValue; |
| XMLCh newTemp[4000]; |
| |
| if (offset >= 3999) { |
| newNodeValue = (XMLCh*) fMemoryManager->allocate |
| ( |
| (offset+1) * sizeof(XMLCh) |
| );//new XMLCh[offset+1]; |
| } |
| else { |
| newNodeValue = newTemp; |
| } |
| XMLString::subString(newNodeValue, txtValue, 0, offset, ((DOMDocumentImpl *)fDocument)->getMemoryManager()); |
| newNode->setNodeValue( ((DOMDocumentImpl *)fDocument)->getPooledString(newNodeValue) ); |
| |
| if (offset>= 3999) |
| fMemoryManager->deallocate(newNodeValue);//delete[] 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. |
| * |
| */ |
| DOMNode* DOMRangeImpl::getSelectedNode( DOMNode*container, int offset ) |
| { |
| short type = container->getNodeType(); |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| return container; |
| |
| // This case is an important convenience for |
| // traverseRightBoundary() |
| if ( offset<0 ) |
| return container; |
| |
| DOMNode*child = container->getFirstChild(); |
| while( child!=0 && offset > 0 ) |
| { |
| --offset; |
| child = child->getNextSibling(); |
| } |
| if ( child!=0 ) |
| return child; |
| return container; |
| } |
| |
| void DOMRangeImpl::checkReadOnly(DOMNode* start, DOMNode* end, |
| XMLSize_t startOffset, XMLSize_t endOffset) |
| { |
| if ((start == 0) || (end == 0) ) return; |
| DOMNode*sNode = 0; |
| |
| short type = start->getNodeType(); |
| if ( type == DOMNode::DOCUMENT_TYPE_NODE ) |
| { |
| throw DOMException( |
| DOMException::HIERARCHY_REQUEST_ERR, 0, fMemoryManager); |
| } |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (castToNodeImpl(start)->isReadOnly()) { |
| throw DOMException( |
| DOMException::NO_MODIFICATION_ALLOWED_ERR, 0, fMemoryManager); |
| } |
| //if both start and end are text check and return |
| if (start == end) |
| return; |
| |
| sNode = start; |
| } else { |
| //set the start and end nodes to check |
| sNode = start->getFirstChild(); |
| for(XMLSize_t i = 0; i<startOffset; i++) |
| sNode = sNode->getNextSibling(); |
| } |
| |
| DOMNode* eNode; |
| type = end->getNodeType(); |
| if ( type == DOMNode::DOCUMENT_TYPE_NODE ) |
| { |
| throw DOMException( |
| DOMException::HIERARCHY_REQUEST_ERR, 0, fMemoryManager); |
| } |
| |
| if((type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| eNode = end; //need to check only till this node |
| } |
| else { //need to check all the kids that fall before the end offset value |
| eNode = end->getFirstChild(); |
| if (endOffset > 0) { |
| for (XMLSize_t i = 0; i<endOffset-1; i++) |
| eNode = eNode->getNextSibling(); |
| } |
| } |
| //recursivly search if any node is readonly |
| recurseTreeAndCheck(sNode, eNode); |
| } |
| |
| void DOMRangeImpl::recurseTreeAndCheck(DOMNode* start, DOMNode* end) |
| { |
| for(DOMNode* node=start; node != 0 && node !=end; node=node->getNextSibling()) |
| { |
| if ( node->getNodeType()== DOMNode::DOCUMENT_TYPE_NODE ) |
| { |
| throw DOMException( |
| DOMException::HIERARCHY_REQUEST_ERR, 0, fMemoryManager); |
| } |
| |
| if (castToNodeImpl(node)->isReadOnly()) { |
| throw DOMException( |
| DOMException::NO_MODIFICATION_ALLOWED_ERR, 0, fMemoryManager); |
| } |
| |
| if (node->hasChildNodes()) { |
| node = node->getFirstChild(); |
| recurseTreeAndCheck(node, end); |
| } |
| } |
| } |
| |
| |
| DOMNode* DOMRangeImpl::removeChild(DOMNode* parent, DOMNode* child) |
| { |
| fRemoveChild = child; //only a precaution measure not to update this range data before removal |
| DOMNode*n = parent->removeChild(child); |
| fRemoveChild = 0; |
| return n; |
| } |
| |
| |
| // |
| // Mutation functions |
| // |
| |
| |
| /* This function is called from DOM. |
| * The text has already been replaced. |
| * Fix-up any offsets. |
| */ |
| void DOMRangeImpl::receiveReplacedText(DOMNode* node) |
| { |
| if (node == 0) return; |
| |
| short type = fStartContainer->getNodeType(); |
| if (node == fStartContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| fStartOffset = 0; |
| } |
| type = fEndContainer->getNodeType(); |
| if (node == fEndContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| fEndOffset = 0; |
| } |
| } |
| |
| |
| /** This function is called from DOM. |
| * The text has already been deleted. |
| * Fix-up any offsets. |
| */ |
| void DOMRangeImpl::updateRangeForDeletedText(DOMNode* node, XMLSize_t offset, int count) |
| { |
| if (node == 0) return; |
| |
| short type = fStartContainer->getNodeType(); |
| if (node == fStartContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fStartOffset > offset+count) { |
| fStartOffset = fStartOffset-count; |
| } else if (fStartOffset > offset) { |
| fStartOffset = offset; |
| } |
| } |
| type = fEndContainer->getNodeType(); |
| if (node == fEndContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fEndOffset > offset+count) { |
| fEndOffset = fEndOffset-count; |
| } else if (fEndOffset > offset) { |
| fEndOffset = offset; |
| } |
| } |
| } |
| |
| |
| |
| /** This function is called from DOM. |
| * The text has already beeen inserted. |
| * Fix-up any offsets. |
| */ |
| void DOMRangeImpl::updateRangeForInsertedText(DOMNode* node, XMLSize_t offset, int count) |
| { |
| if (node == 0) return; |
| |
| short type = fStartContainer->getNodeType(); |
| if (node == fStartContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fStartOffset > offset) { |
| fStartOffset = offset; |
| } |
| } |
| type = fEndContainer->getNodeType(); |
| if (node == fEndContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fEndOffset > offset) { |
| fEndOffset = fEndOffset+count; |
| } |
| } |
| } |
| |
| |
| |
| /** 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 DOMRangeImpl::updateRangeForDeletedNode(DOMNode* node) |
| { |
| |
| if (node == 0) return; |
| if (fRemoveChild == node) return; |
| |
| if (node->getParentNode() == fStartContainer) { |
| XMLSize_t index = indexOf(node, fStartContainer); |
| if ( fStartOffset > index) { |
| fStartOffset--; |
| } |
| } |
| |
| if (node->getParentNode() == fEndContainer) { |
| XMLSize_t index = indexOf(node, fEndContainer); |
| if ( fEndOffset > index) { |
| fEndOffset--; |
| } |
| } |
| |
| if (node->getParentNode() != fStartContainer |
| || node->getParentNode() != fEndContainer) { |
| if (isAncestorOf(node, fStartContainer)) { |
| DOMNode* tpNode = node->getParentNode(); |
| setStartContainer( tpNode ); |
| fStartOffset = indexOf( node, tpNode); |
| } |
| if (isAncestorOf(node, fEndContainer)) { |
| DOMNode* tpNode = node->getParentNode(); |
| setEndContainer( tpNode ); |
| fEndOffset = indexOf( node, tpNode); |
| } |
| } |
| |
| } |
| |
| void DOMRangeImpl::updateRangeForInsertedNode(DOMNode* node) { |
| if (node == 0) return; |
| |
| if (node->getParentNode() == fStartContainer) { |
| XMLSize_t index = indexOf(node, fStartContainer); |
| if (index < fStartOffset) { |
| fStartOffset++; |
| } |
| } |
| |
| if (node->getParentNode() == fEndContainer) { |
| XMLSize_t index = indexOf(node, fEndContainer); |
| if (index < fEndOffset) { |
| fEndOffset++; |
| } |
| } |
| } |
| |
| |
| void DOMRangeImpl::updateSplitInfo(DOMNode* oldNode, DOMNode* startNode, XMLSize_t offset) |
| { |
| if (startNode == 0) return; |
| |
| short type = fStartContainer->getNodeType(); |
| if (oldNode == fStartContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fStartOffset > offset) { |
| fStartOffset = fStartOffset - offset; |
| fStartContainer = startNode; |
| } |
| } |
| |
| type = fEndContainer->getNodeType(); |
| if (oldNode == fEndContainer |
| && (type == DOMNode::TEXT_NODE |
| || type == DOMNode::CDATA_SECTION_NODE |
| || type == DOMNode::COMMENT_NODE |
| || type == DOMNode::PROCESSING_INSTRUCTION_NODE)) |
| { |
| if (fEndOffset > offset) { |
| fEndContainer = startNode; |
| fEndOffset = fEndOffset - offset; |
| } |
| } |
| } |
| |
| |
| |
| XERCES_CPP_NAMESPACE_END |
| |