| /* |
| * reserved comment block |
| * DO NOT REMOVE OR ALTER! |
| */ |
| /* |
| * Copyright 1999-2005 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. |
| */ |
| |
| package com.sun.org.apache.xerces.internal.dom; |
| |
| import java.util.Vector; |
| |
| import org.w3c.dom.CharacterData; |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.DocumentFragment; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.ranges.Range; |
| import org.w3c.dom.ranges.RangeException; |
| |
| |
| /** The RangeImpl class implements the org.w3c.dom.range.Range interface. |
| * <p> Please see the API documentation for the interface classes |
| * and use the interfaces in your client programs. |
| * |
| * @xerces.internal |
| * |
| */ |
| public class RangeImpl implements Range { |
| |
| // |
| // Constants |
| // |
| |
| |
| // |
| // Data |
| // |
| |
| DocumentImpl fDocument; |
| Node fStartContainer; |
| Node fEndContainer; |
| int fStartOffset; |
| int fEndOffset; |
| boolean fIsCollapsed; |
| boolean fDetach = false; |
| Node fInsertNode = null; |
| Node fDeleteNode = null; |
| Node fSplitNode = null; |
| // Was the Node inserted from the Range or the Document |
| boolean fInsertedFromRange = false; |
| |
| /** The constructor. Clients must use DocumentRange.createRange(), |
| * because it registers the Range with the document, so it can |
| * be fixed-up. |
| */ |
| public RangeImpl(DocumentImpl document) { |
| fDocument = document; |
| fStartContainer = document; |
| fEndContainer = document; |
| fStartOffset = 0; |
| fEndOffset = 0; |
| fDetach = false; |
| } |
| |
| public Node getStartContainer() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| return fStartContainer; |
| } |
| |
| public int getStartOffset() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| return fStartOffset; |
| } |
| |
| public Node getEndContainer() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| return fEndContainer; |
| } |
| |
| public int getEndOffset() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| return fEndOffset; |
| } |
| |
| public boolean getCollapsed() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| return (fStartContainer == fEndContainer |
| && fStartOffset == fEndOffset); |
| } |
| |
| public Node getCommonAncestorContainer() { |
| if ( fDetach ) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| Vector startV = new Vector(); |
| Node node; |
| for (node=fStartContainer; node != null; |
| node=node.getParentNode()) |
| { |
| startV.addElement(node); |
| } |
| Vector endV = new Vector(); |
| for (node=fEndContainer; node != null; |
| node=node.getParentNode()) |
| { |
| endV.addElement(node); |
| } |
| int s = startV.size()-1; |
| int e = endV.size()-1; |
| Object result = null; |
| while (s>=0 && e>=0) { |
| if (startV.elementAt(s) == endV.elementAt(e)) { |
| result = startV.elementAt(s); |
| } else { |
| break; |
| } |
| --s; |
| --e; |
| } |
| return (Node)result; |
| } |
| |
| |
| public void setStart(Node refNode, int offset) |
| throws RangeException, DOMException |
| { |
| if (fDocument.errorChecking) { |
| if ( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !isLegalContainer(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| |
| checkIndex(refNode, offset); |
| |
| fStartContainer = refNode; |
| fStartOffset = offset; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(true); |
| } |
| } |
| |
| public void setEnd(Node refNode, int offset) |
| throws RangeException, DOMException |
| { |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !isLegalContainer(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| |
| checkIndex(refNode, offset); |
| |
| fEndContainer = refNode; |
| fEndOffset = offset; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(false); |
| } |
| } |
| |
| public void setStartBefore(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !hasLegalRootContainer(refNode) || |
| !isLegalContainedNode(refNode) ) |
| { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| |
| fStartContainer = refNode.getParentNode(); |
| int i = 0; |
| for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { |
| i++; |
| } |
| fStartOffset = i-1; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(true); |
| } |
| } |
| |
| public void setStartAfter(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !hasLegalRootContainer(refNode) || |
| !isLegalContainedNode(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| fStartContainer = refNode.getParentNode(); |
| int i = 0; |
| for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { |
| i++; |
| } |
| fStartOffset = i; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(true); |
| } |
| } |
| |
| public void setEndBefore(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !hasLegalRootContainer(refNode) || |
| !isLegalContainedNode(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| fEndContainer = refNode.getParentNode(); |
| int i = 0; |
| for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { |
| i++; |
| } |
| fEndOffset = i-1; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(false); |
| } |
| } |
| |
| public void setEndAfter(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !hasLegalRootContainer(refNode) || |
| !isLegalContainedNode(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| fEndContainer = refNode.getParentNode(); |
| int i = 0; |
| for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { |
| i++; |
| } |
| fEndOffset = i; |
| |
| // If one boundary-point of a Range is set to have a root container |
| // other |
| // than the current one for the Range, the Range should be collapsed to |
| // the new position. |
| // The start position of a Range should never be after the end position. |
| if (getCommonAncestorContainer() == null |
| || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) { |
| collapse(false); |
| } |
| } |
| |
| public void collapse(boolean toStart) { |
| |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| |
| if (toStart) { |
| fEndContainer = fStartContainer; |
| fEndOffset = fStartOffset; |
| } else { |
| fStartContainer = fEndContainer; |
| fStartOffset = fEndOffset; |
| } |
| } |
| |
| public void selectNode(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !isLegalContainer( refNode.getParentNode() ) || |
| !isLegalContainedNode( refNode ) ) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| Node parent = refNode.getParentNode(); |
| if (parent != null ) // REVIST: what to do if it IS null? |
| { |
| fStartContainer = parent; |
| fEndContainer = parent; |
| int i = 0; |
| for (Node n = refNode; n!=null; n = n.getPreviousSibling()) { |
| i++; |
| } |
| fStartOffset = i-1; |
| fEndOffset = fStartOffset+1; |
| } |
| } |
| |
| public void selectNodeContents(Node refNode) |
| throws RangeException |
| { |
| if (fDocument.errorChecking) { |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( !isLegalContainer(refNode)) { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) { |
| throw new DOMException( |
| DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| fStartContainer = refNode; |
| fEndContainer = refNode; |
| Node first = refNode.getFirstChild(); |
| fStartOffset = 0; |
| if (first == null) { |
| fEndOffset = 0; |
| } else { |
| int i = 0; |
| for (Node n = first; n!=null; n = n.getNextSibling()) { |
| i++; |
| } |
| fEndOffset = i; |
| } |
| |
| } |
| |
| public short compareBoundaryPoints(short how, Range sourceRange) |
| throws DOMException |
| { |
| if (fDocument.errorChecking) { |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| // WRONG_DOCUMENT_ERR: Raised if the two Ranges are not in the same Document or DocumentFragment. |
| if ((fDocument != sourceRange.getStartContainer().getOwnerDocument() |
| && fDocument != sourceRange.getStartContainer() |
| && sourceRange.getStartContainer() != null) |
| || (fDocument != sourceRange.getEndContainer().getOwnerDocument() |
| && fDocument != sourceRange.getEndContainer() |
| && sourceRange.getStartContainer() != null)) { |
| throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage( DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| } |
| |
| Node endPointA; |
| Node endPointB; |
| int offsetA; |
| int offsetB; |
| |
| if (how == START_TO_START) { |
| endPointA = sourceRange.getStartContainer(); |
| endPointB = fStartContainer; |
| offsetA = sourceRange.getStartOffset(); |
| offsetB = fStartOffset; |
| } else |
| if (how == START_TO_END) { |
| endPointA = sourceRange.getStartContainer(); |
| endPointB = fEndContainer; |
| offsetA = sourceRange.getStartOffset(); |
| offsetB = fEndOffset; |
| } else |
| if (how == END_TO_START) { |
| endPointA = sourceRange.getEndContainer(); |
| endPointB = fStartContainer; |
| offsetA = sourceRange.getEndOffset(); |
| offsetB = fStartOffset; |
| } else { |
| endPointA = sourceRange.getEndContainer(); |
| endPointB = fEndContainer; |
| offsetA = sourceRange.getEndOffset(); |
| offsetB = fEndOffset; |
| } |
| |
| // The DOM Spec outlines four cases that need to be tested |
| // to compare two range boundary points: |
| // case 1: same container |
| // case 2: Child C of container A is ancestor of B |
| // case 3: Child C of container B is ancestor of A |
| // case 4: preorder traversal of context tree. |
| |
| // case 1: same container |
| if (endPointA == endPointB) { |
| if (offsetA < offsetB) return 1; |
| if (offsetA == offsetB) return 0; |
| return -1; |
| } |
| // case 2: Child C of container A is ancestor of B |
| // This can be quickly tested by walking the parent chain of B |
| for ( Node c = endPointB, p = c.getParentNode(); |
| p != null; |
| c = p, p = p.getParentNode()) |
| { |
| if (p == endPointA) { |
| int index = indexOf(c, endPointA); |
| if (offsetA <= index) return 1; |
| return -1; |
| } |
| } |
| |
| // case 3: Child C of container B is ancestor of A |
| // This can be quickly tested by walking the parent chain of A |
| for ( Node c = endPointA, p = c.getParentNode(); |
| p != null; |
| c = p, p = p.getParentNode()) |
| { |
| if (p == endPointB) { |
| int index = indexOf(c, endPointB); |
| if (index < offsetB) return 1; |
| return -1; |
| } |
| } |
| |
| // 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; |
| for ( Node n = endPointA; n != null; n = n.getParentNode() ) |
| depthDiff++; |
| for ( Node n = endPointB; n != null; n = n.getParentNode() ) |
| depthDiff--; |
| while (depthDiff > 0) { |
| endPointA = endPointA.getParentNode(); |
| depthDiff--; |
| } |
| while (depthDiff < 0) { |
| endPointB = endPointB.getParentNode(); |
| depthDiff++; |
| } |
| for (Node pA = endPointA.getParentNode(), |
| pB = endPointB.getParentNode(); |
| pA != pB; |
| pA = pA.getParentNode(), pB = pB.getParentNode() ) |
| { |
| endPointA = pA; |
| endPointB = pB; |
| } |
| for ( Node n = endPointA.getNextSibling(); |
| n != null; |
| n = n.getNextSibling() ) |
| { |
| if (n == endPointB) { |
| return 1; |
| } |
| } |
| return -1; |
| } |
| |
| public void deleteContents() |
| throws DOMException |
| { |
| traverseContents(DELETE_CONTENTS); |
| } |
| |
| public DocumentFragment extractContents() |
| throws DOMException |
| { |
| return traverseContents(EXTRACT_CONTENTS); |
| } |
| |
| public DocumentFragment cloneContents() |
| throws DOMException |
| { |
| return traverseContents(CLONE_CONTENTS); |
| } |
| |
| public void insertNode(Node newNode) |
| throws DOMException, RangeException |
| { |
| if ( newNode == null ) return; //throw exception? |
| |
| int type = newNode.getNodeType(); |
| |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if ( fDocument != newNode.getOwnerDocument() ) { |
| throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null)); |
| } |
| |
| if (type == Node.ATTRIBUTE_NODE |
| || type == Node.ENTITY_NODE |
| || type == Node.NOTATION_NODE |
| || type == Node.DOCUMENT_NODE) |
| { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| } |
| Node cloneCurrent; |
| Node current; |
| int currentChildren = 0; |
| fInsertedFromRange = true; |
| |
| //boolean MULTIPLE_MODE = false; |
| if (fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| |
| Node parent = fStartContainer.getParentNode(); |
| currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion |
| // split text node: results is 3 nodes.. |
| cloneCurrent = fStartContainer.cloneNode(false); |
| ((TextImpl)cloneCurrent).setNodeValueInternal( |
| (cloneCurrent.getNodeValue()).substring(fStartOffset)); |
| ((TextImpl)fStartContainer).setNodeValueInternal( |
| (fStartContainer.getNodeValue()).substring(0,fStartOffset)); |
| Node next = fStartContainer.getNextSibling(); |
| if (next != null) { |
| if (parent != null) { |
| parent.insertBefore(newNode, next); |
| parent.insertBefore(cloneCurrent, next); |
| } |
| } else { |
| if (parent != null) { |
| parent.appendChild(newNode); |
| parent.appendChild(cloneCurrent); |
| } |
| } |
| //update ranges after the insertion |
| if ( fEndContainer == fStartContainer) { |
| fEndContainer = cloneCurrent; //endContainer is the new Node created |
| fEndOffset -= fStartOffset; |
| } |
| else if ( fEndContainer == parent ) { //endContainer was not a text Node. |
| //endOffset + = number_of_children_added |
| fEndOffset += (parent.getChildNodes().getLength() - currentChildren); |
| } |
| |
| // signal other Ranges to update their start/end containers/offsets |
| signalSplitData(fStartContainer, cloneCurrent, fStartOffset); |
| |
| |
| } else { // ! TEXT_NODE |
| if ( fEndContainer == fStartContainer ) //need to remember number of kids |
| currentChildren= fEndContainer.getChildNodes().getLength(); |
| |
| current = fStartContainer.getFirstChild(); |
| int i = 0; |
| for(i = 0; i < fStartOffset && current != null; i++) { |
| current=current.getNextSibling(); |
| } |
| if (current != null) { |
| fStartContainer.insertBefore(newNode, current); |
| } else { |
| fStartContainer.appendChild(newNode); |
| } |
| //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1 |
| // insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2 |
| if ( fEndContainer == fStartContainer && fEndOffset != 0 ) { //update fEndOffset if not 0 |
| fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren); |
| } |
| } |
| fInsertedFromRange = false; |
| } |
| |
| public void surroundContents(Node newParent) |
| throws DOMException, RangeException |
| { |
| if (newParent==null) return; |
| int type = newParent.getNodeType(); |
| |
| if (fDocument.errorChecking) { |
| if (fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| if (type == Node.ATTRIBUTE_NODE |
| || type == Node.ENTITY_NODE |
| || type == Node.NOTATION_NODE |
| || type == Node.DOCUMENT_TYPE_NODE |
| || type == Node.DOCUMENT_NODE |
| || type == Node.DOCUMENT_FRAGMENT_NODE) |
| { |
| throw new RangeExceptionImpl( |
| RangeException.INVALID_NODE_TYPE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null)); |
| } |
| } |
| |
| Node realStart = fStartContainer; |
| Node realEnd = fEndContainer; |
| if (fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| realStart = fStartContainer.getParentNode(); |
| } |
| if (fEndContainer.getNodeType() == Node.TEXT_NODE) { |
| realEnd = fEndContainer.getParentNode(); |
| } |
| |
| if (realStart != realEnd) { |
| throw new RangeExceptionImpl( |
| RangeException.BAD_BOUNDARYPOINTS_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "BAD_BOUNDARYPOINTS_ERR", null)); |
| } |
| |
| DocumentFragment frag = extractContents(); |
| insertNode(newParent); |
| newParent.appendChild(frag); |
| selectNode(newParent); |
| } |
| |
| public Range cloneRange(){ |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| |
| Range range = fDocument.createRange(); |
| range.setStart(fStartContainer, fStartOffset); |
| range.setEnd(fEndContainer, fEndOffset); |
| return range; |
| } |
| |
| public String toString(){ |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| |
| Node node = fStartContainer; |
| Node stopNode = fEndContainer; |
| StringBuffer sb = new StringBuffer(); |
| if (fStartContainer.getNodeType() == Node.TEXT_NODE |
| || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE |
| ) { |
| if (fStartContainer == fEndContainer) { |
| sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset)); |
| return sb.toString(); |
| } |
| sb.append(fStartContainer.getNodeValue().substring(fStartOffset)); |
| node=nextNode (node,true); //fEndContainer!=fStartContainer |
| |
| } |
| else { //fStartContainer is not a TextNode |
| node=node.getFirstChild(); |
| if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset |
| int counter=0; |
| while (counter<fStartOffset && node!=null) { |
| node=node.getNextSibling(); |
| counter++; |
| } |
| } |
| if (node == null) { |
| node = nextNode(fStartContainer,false); |
| } |
| } |
| if ( fEndContainer.getNodeType()!= Node.TEXT_NODE && |
| fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){ |
| int i=fEndOffset; |
| stopNode = fEndContainer.getFirstChild(); |
| while( i>0 && stopNode!=null ){ |
| --i; |
| stopNode = stopNode.getNextSibling(); |
| } |
| if ( stopNode == null ) |
| stopNode = nextNode( fEndContainer, false ); |
| } |
| while (node != stopNode) { //look into all kids of the Range |
| if (node == null) break; |
| if (node.getNodeType() == Node.TEXT_NODE |
| || node.getNodeType() == Node.CDATA_SECTION_NODE) { |
| sb.append(node.getNodeValue()); |
| } |
| |
| node = nextNode(node, true); |
| } |
| |
| if (fEndContainer.getNodeType() == Node.TEXT_NODE |
| || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) { |
| sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset)); |
| } |
| return sb.toString(); |
| } |
| |
| public void detach() { |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| fDetach = true; |
| fDocument.removeRange(this); |
| } |
| |
| // |
| // Mutation functions |
| // |
| |
| /** Signal other Ranges to update their start/end |
| * containers/offsets. The data has already been split |
| * into the two Nodes. |
| */ |
| void signalSplitData(Node node, Node newNode, int offset) { |
| fSplitNode = node; |
| // notify document |
| fDocument.splitData(node, newNode, offset); |
| fSplitNode = null; |
| } |
| |
| /** Fix up this Range if another Range has split a Text Node |
| * into 2 Nodes. |
| */ |
| void receiveSplitData(Node node, Node newNode, int offset) { |
| if (node == null || newNode == null) return; |
| if (fSplitNode == node) return; |
| |
| if (node == fStartContainer |
| && fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| if (fStartOffset > offset) { |
| fStartOffset = fStartOffset - offset; |
| fStartContainer = newNode; |
| } |
| } |
| if (node == fEndContainer |
| && fEndContainer.getNodeType() == Node.TEXT_NODE) { |
| if (fEndOffset > offset) { |
| fEndOffset = fEndOffset-offset; |
| fEndContainer = newNode; |
| } |
| } |
| |
| } |
| |
| /** This function inserts text into a Node and invokes |
| * a method to fix-up all other Ranges. |
| */ |
| void deleteData(CharacterData node, int offset, int count) { |
| fDeleteNode = node; |
| node.deleteData( offset, count); |
| fDeleteNode = null; |
| } |
| |
| |
| /** This function is called from DOM. |
| * The text has already beeen inserted. |
| * Fix-up any offsets. |
| */ |
| void receiveDeletedText(Node node, int offset, int count) { |
| if (node == null) return; |
| if (fDeleteNode == node) return; |
| if (node == fStartContainer |
| && fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| if (fStartOffset > offset+count) { |
| fStartOffset = offset+(fStartOffset-(offset+count)); |
| } else |
| if (fStartOffset > offset) { |
| fStartOffset = offset; |
| } |
| } |
| if (node == fEndContainer |
| && fEndContainer.getNodeType() == Node.TEXT_NODE) { |
| if (fEndOffset > offset+count) { |
| fEndOffset = offset+(fEndOffset-(offset+count)); |
| } else |
| if (fEndOffset > offset) { |
| fEndOffset = offset; |
| } |
| } |
| |
| } |
| |
| /** This function inserts text into a Node and invokes |
| * a method to fix-up all other Ranges. |
| */ |
| void insertData(CharacterData node, int index, String insert) { |
| fInsertNode = node; |
| node.insertData( index, insert); |
| fInsertNode = null; |
| } |
| |
| |
| /** This function is called from DOM. |
| * The text has already beeen inserted. |
| * Fix-up any offsets. |
| */ |
| void receiveInsertedText(Node node, int index, int len) { |
| if (node == null) return; |
| if (fInsertNode == node) return; |
| if (node == fStartContainer |
| && fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| if (index < fStartOffset) { |
| fStartOffset = fStartOffset+len; |
| } |
| } |
| if (node == fEndContainer |
| && fEndContainer.getNodeType() == Node.TEXT_NODE) { |
| if (index < fEndOffset) { |
| fEndOffset = fEndOffset+len; |
| } |
| } |
| |
| } |
| |
| /** This function is called from DOM. |
| * The text has already beeen replaced. |
| * Fix-up any offsets. |
| */ |
| void receiveReplacedText(Node node) { |
| if (node == null) return; |
| if (node == fStartContainer |
| && fStartContainer.getNodeType() == Node.TEXT_NODE) { |
| fStartOffset = 0; |
| } |
| if (node == fEndContainer |
| && fEndContainer.getNodeType() == Node.TEXT_NODE) { |
| fEndOffset = 0; |
| } |
| |
| } |
| |
| /** This function is called from the DOM. |
| * This node has already been inserted into the DOM. |
| * Fix-up any offsets. |
| */ |
| public void insertedNodeFromDOM(Node node) { |
| if (node == null) return; |
| if (fInsertNode == node) return; |
| if (fInsertedFromRange) return; // Offsets are adjusted in Range.insertNode |
| |
| Node parent = node.getParentNode(); |
| |
| if (parent == fStartContainer) { |
| int index = indexOf(node, fStartContainer); |
| if (index < fStartOffset) { |
| fStartOffset++; |
| } |
| } |
| |
| if (parent == fEndContainer) { |
| int index = indexOf(node, fEndContainer); |
| if (index < fEndOffset) { |
| fEndOffset++; |
| } |
| } |
| |
| } |
| |
| /** This function is called within Range |
| * instead of Node.removeChild, |
| * so that the range can remember that it is actively |
| * removing this child. |
| */ |
| |
| Node fRemoveChild = null; |
| Node removeChild(Node parent, Node child) { |
| fRemoveChild = child; |
| Node n = parent.removeChild(child); |
| fRemoveChild = null; |
| return n; |
| } |
| |
| /** 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 removeNode(Node node) { |
| if (node == null) return; |
| if (fRemoveChild == node) return; |
| |
| Node parent = node.getParentNode(); |
| |
| if (parent == fStartContainer) { |
| int index = indexOf(node, fStartContainer); |
| if (index < fStartOffset) { |
| fStartOffset--; |
| } |
| } |
| |
| if (parent == fEndContainer) { |
| int index = indexOf(node, fEndContainer); |
| if (index < fEndOffset) { |
| fEndOffset--; |
| } |
| } |
| //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted |
| if (parent != fStartContainer |
| || parent != fEndContainer) { |
| if (isAncestorOf(node, fStartContainer)) { |
| fStartContainer = parent; |
| fStartOffset = indexOf( node, parent); |
| } |
| if (isAncestorOf(node, fEndContainer)) { |
| fEndContainer = parent; |
| fEndOffset = indexOf( node, parent); |
| } |
| } |
| |
| } |
| |
| // |
| // Utility functions. |
| // |
| |
| // parameters for traverseContents(int) |
| //REVIST: use boolean, since there are only 2 now... |
| static final int EXTRACT_CONTENTS = 1; |
| static final int CLONE_CONTENTS = 2; |
| static final int DELETE_CONTENTS = 3; |
| |
| /** |
| * 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 |
| * <code>how</code> argument. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a document fragment containing the range's content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but sill |
| * produced cloned content in a document fragment |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes. |
| * </ol> |
| * |
| * @return Returns a document fragment containing any |
| * copied or extracted nodes. If the <code>how</code> |
| * parameter was <code>DELETE_CONTENTS</code>, the |
| * return value is null. |
| */ |
| private DocumentFragment traverseContents( int how ) |
| throws DOMException |
| { |
| if (fStartContainer == null || fEndContainer == null) { |
| return null; // REVIST: Throw exception? |
| } |
| |
| //Check for a detached range. |
| if( fDetach) { |
| throw new DOMException( |
| DOMException.INVALID_STATE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null)); |
| } |
| |
| /* |
| 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 ( Node c = fEndContainer, p = c.getParentNode(); |
| p != null; |
| c = p, p = p.getParentNode()) |
| { |
| if (p == fStartContainer) |
| return traverseCommonStartContainer( c, how ); |
| ++endContainerDepth; |
| } |
| |
| // case 3: Child C of container B is ancestor of A |
| // This can be quickly tested by walking the parent chain of A |
| int startContainerDepth = 0; |
| for ( Node c = fStartContainer, p = c.getParentNode(); |
| p != null; |
| c = p, p = p.getParentNode()) |
| { |
| if (p == fEndContainer) |
| return traverseCommonEndContainer( c, how ); |
| ++startContainerDepth; |
| } |
| |
| // case 4: There is a common ancestor container. Find the |
| // ancestor siblings that are children of that container. |
| int depthDiff = startContainerDepth - endContainerDepth; |
| |
| Node startNode = fStartContainer; |
| while (depthDiff > 0) { |
| startNode = startNode.getParentNode(); |
| depthDiff--; |
| } |
| |
| Node endNode = fEndContainer; |
| while (depthDiff < 0) { |
| endNode = endNode.getParentNode(); |
| depthDiff++; |
| } |
| |
| // ascend the ancestor hierarchy until we have a common parent. |
| for( Node 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. |
| * This method is invoked by the generic <code>traverse</code> |
| * method. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a document fragment containing the range's content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but sill |
| * produced cloned content in a document fragment |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes. |
| * </ol> |
| * |
| * @return Returns a document fragment containing any |
| * copied or extracted nodes. If the <code>how</code> |
| * parameter was <code>DELETE_CONTENTS</code>, the |
| * return value is null. |
| */ |
| private DocumentFragment traverseSameContainer( int how ) |
| { |
| DocumentFragment frag = null; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument.createDocumentFragment(); |
| |
| // If selection is empty, just return the fragment |
| if ( fStartOffset==fEndOffset ) |
| return frag; |
| |
| // Text node needs special case handling |
| if ( fStartContainer.getNodeType()==Node.TEXT_NODE ) |
| { |
| // get the substring |
| String s = fStartContainer.getNodeValue(); |
| String sub = s.substring( fStartOffset, fEndOffset ); |
| |
| // set the original text node to its new value |
| if ( how != CLONE_CONTENTS ) |
| { |
| ((TextImpl)fStartContainer).deleteData(fStartOffset, |
| fEndOffset-fStartOffset) ; |
| // Nothing is partially selected, so collapse to start point |
| collapse( true ); |
| } |
| if ( how==DELETE_CONTENTS) |
| return null; |
| frag.appendChild( fDocument.createTextNode(sub) ); |
| return frag; |
| } |
| |
| // Copy nodes between the start/end offsets. |
| Node n = getSelectedNode( fStartContainer, fStartOffset ); |
| int cnt = fEndOffset - fStartOffset; |
| while( cnt > 0 ) |
| { |
| Node sibling = n.getNextSibling(); |
| 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. This method is invoked by the generic |
| * <code>traverse</code> method. |
| * |
| * @param endAncestor |
| * The ancestor of the end container that is a direct child |
| * of the start container. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a document fragment containing the range's content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but sill |
| * produced cloned content in a document fragment |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes. |
| * </ol> |
| * |
| * @return Returns a document fragment containing any |
| * copied or extracted nodes. If the <code>how</code> |
| * parameter was <code>DELETE_CONTENTS</code>, the |
| * return value is null. |
| */ |
| private DocumentFragment |
| traverseCommonStartContainer( Node endAncestor, int how ) |
| { |
| DocumentFragment frag = null; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument.createDocumentFragment(); |
| 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 ) |
| { |
| Node sibling = n.getPreviousSibling(); |
| 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. This method is invoked by the generic |
| * <code>traverse</code> method. |
| * |
| * @param startAncestor |
| * The ancestor of the start container that is a direct |
| * child of the end container. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a document fragment containing the range's content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but sill |
| * produced cloned content in a document fragment |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes. |
| * </ol> |
| * |
| * @return Returns a document fragment containing any |
| * copied or extracted nodes. If the <code>how</code> |
| * parameter was <code>DELETE_CONTENTS</code>, the |
| * return value is null. |
| */ |
| private DocumentFragment |
| traverseCommonEndContainer( Node startAncestor, int how ) |
| { |
| DocumentFragment frag = null; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument.createDocumentFragment(); |
| 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 ) |
| { |
| Node sibling = n.getNextSibling(); |
| 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. |
| * This method is invoked by |
| * the generic <code>traverse</code> method. |
| * |
| * @param startAncestor |
| * Given a common ancestor of the start and end containers, |
| * this parameter is the ancestor (or self) of the start |
| * container that is a direct child of the common ancestor. |
| * |
| * @param endAncestor |
| * Given a common ancestor of the start and end containers, |
| * this parameter is the ancestor (or self) of the end |
| * container that is a direct child of the common ancestor. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a document fragment containing the range's content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but sill |
| * produced cloned content in a document fragment |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes. |
| * </ol> |
| * |
| * @return Returns a document fragment containing any |
| * copied or extracted nodes. If the <code>how</code> |
| * parameter was <code>DELETE_CONTENTS</code>, the |
| * return value is null. |
| */ |
| private DocumentFragment |
| traverseCommonAncestors( Node startAncestor, Node endAncestor, int how ) |
| { |
| DocumentFragment frag = null; |
| if ( how!=DELETE_CONTENTS) |
| frag = fDocument.createDocumentFragment(); |
| |
| Node n = traverseLeftBoundary( startAncestor, how ); |
| if ( frag!=null ) |
| frag.appendChild( n ); |
| |
| Node commonParent = startAncestor.getParentNode(); |
| int startOffset = indexOf( startAncestor, commonParent ); |
| int endOffset = indexOf( endAncestor, commonParent ); |
| ++startOffset; |
| |
| int cnt = endOffset - startOffset; |
| Node sibling = startAncestor.getNextSibling(); |
| |
| while( cnt > 0 ) |
| { |
| 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 |
| * <code>how</code> parameter. It is a-priori assumed |
| * by this method that the right boundary does |
| * not contain the range's start container. |
| * <p> |
| * A "right boundary" is best visualized by thinking |
| * of a sample tree:<pre> |
| * A |
| * /|\ |
| * / | \ |
| * / | \ |
| * B C D |
| * /|\ /|\ |
| * E F G H I J |
| * </pre> |
| * 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: |
| * <p> |
| * Partially Selected Nodes: B, D<br> |
| * Fully Selected Nodes: F, G, C, H, I |
| * <p> |
| * The "right boundary" is the highest subtree node |
| * that contains the ending container. The root of |
| * this subtree is always partially selected. |
| * <p> |
| * In this example, the nodes that are traversed |
| * as "right boundary" nodes are: H, I, and D. |
| * |
| * @param root The node that is the root of the "right boundary" subtree. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a node containing the boundaries content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * produced cloned content. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes within the boundary. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting nodes. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traverseRightBoundary( Node root, int how ) |
| { |
| Node next = getSelectedNode( fEndContainer, fEndOffset-1 ); |
| boolean isFullySelected = ( next!=fEndContainer ); |
| |
| if ( next==root ) |
| return traverseNode( next, isFullySelected, false, how ); |
| |
| Node parent = next.getParentNode(); |
| Node clonedParent = traverseNode( parent, false, false, how ); |
| |
| while( parent!=null ) |
| { |
| while( next!=null ) |
| { |
| Node prevSibling = next.getPreviousSibling(); |
| 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(); |
| 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 |
| * <code>how</code> parameter. It is a-priori assumed |
| * by this method that the left boundary does |
| * not contain the range's end container. |
| * <p> |
| * A "left boundary" is best visualized by thinking |
| * of a sample tree:<pre> |
| * |
| * A |
| * /|\ |
| * / | \ |
| * / | \ |
| * B C D |
| * /|\ /|\ |
| * E F G H I J |
| * </pre> |
| * 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: |
| * <p> |
| * Partially Selected Nodes: B, D<br> |
| * Fully Selected Nodes: F, G, C, H, I |
| * <p> |
| * The "left boundary" is the highest subtree node |
| * that contains the starting container. The root of |
| * this subtree is always partially selected. |
| * <p> |
| * In this example, the nodes that are traversed |
| * as "left boundary" nodes are: F, G, and B. |
| * |
| * @param root The node that is the root of the "left boundary" subtree. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will produce |
| * a node containing the boundaries content. |
| * Partially selected nodes are copied, but fully |
| * selected nodes are moved. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * produced cloned content. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete from |
| * the context tree of the range, all fully selected |
| * nodes within the boundary. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting nodes. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traverseLeftBoundary( Node root, int how ) |
| { |
| Node next = getSelectedNode( getStartContainer(), getStartOffset() ); |
| boolean isFullySelected = ( next!=getStartContainer() ); |
| |
| if ( next==root ) |
| return traverseNode( next, isFullySelected, true, how ); |
| |
| Node parent = next.getParentNode(); |
| Node clonedParent = traverseNode( parent, false, true, how ); |
| |
| while( parent!=null ) |
| { |
| while( next!=null ) |
| { |
| Node nextSibling = next.getNextSibling(); |
| 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(); |
| 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. |
| * |
| * @param n The node to be traversed. |
| * |
| * @param isFullySelected |
| * Set to true if the node is fully selected. Should be |
| * false otherwise. |
| * Note that although the DOM 2 specification says that a |
| * text node that is boththe start and end container is not |
| * selected, we treat it here as if it were partially |
| * selected. |
| * |
| * @param isLeft Is true if we are traversing the node as part of navigating |
| * the "left boundary" of the range. If this value is false, |
| * it implies we are navigating the "right boundary" of the |
| * range. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will simply |
| * return the original node. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * return a cloned node. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete the |
| * node from it's parent, but will return null. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting the node. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traverseNode( Node n, boolean isFullySelected, boolean isLeft, int how ) |
| { |
| if ( isFullySelected ) |
| return traverseFullySelected( n, how ); |
| if ( n.getNodeType()==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. |
| * |
| * @param n The node to be traversed. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will simply |
| * return the original node. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * return a cloned node. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete the |
| * node from it's parent, but will return null. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting the node. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traverseFullySelected( Node n, int how ) |
| { |
| switch( how ) |
| { |
| case CLONE_CONTENTS: |
| return n.cloneNode( true ); |
| case EXTRACT_CONTENTS: |
| if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE ) |
| { |
| // TBD: This should be a HIERARCHY_REQUEST_ERR |
| throw new DOMException( |
| DOMException.HIERARCHY_REQUEST_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "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. |
| * |
| * @param n The node to be traversed. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will simply |
| * return the original node. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * return a cloned node. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete the |
| * node from it's parent, but will return null. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting the node. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traversePartiallySelected( 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. |
| * |
| * @param n The node to be traversed. |
| * |
| * @param isLeft Is true if we are traversing the node as part of navigating |
| * the "left boundary" of the range. If this value is false, |
| * it implies we are navigating the "right boundary" of the |
| * range. |
| * |
| * @param how Specifies what type of traversal is being |
| * requested (extract, clone, or delete). |
| * Legal values for this argument are: |
| * |
| * <ol> |
| * <li><code>EXTRACT_CONTENTS</code> - will simply |
| * return the original node. |
| * |
| * <li><code>CLONE_CONTENTS</code> - will leave the |
| * context tree of the range undisturbed, but will |
| * return a cloned node. |
| * |
| * <li><code>DELETE_CONTENTS</code> - will delete the |
| * node from it's parent, but will return null. |
| * </ol> |
| * |
| * @return Returns a node that is the result of visiting the node. |
| * If the traversal operation is |
| * <code>DELETE_CONTENTS</code> the return value is null. |
| */ |
| private Node traverseTextNode( Node n, boolean isLeft, int how ) |
| { |
| String txtValue = n.getNodeValue(); |
| String newNodeValue; |
| String oldNodeValue; |
| |
| if ( isLeft ) |
| { |
| int offset = getStartOffset(); |
| newNodeValue = txtValue.substring( offset ); |
| oldNodeValue = txtValue.substring( 0, offset ); |
| } |
| else |
| { |
| int offset = getEndOffset(); |
| newNodeValue = txtValue.substring( 0, offset ); |
| oldNodeValue = txtValue.substring( offset ); |
| } |
| |
| if ( how != CLONE_CONTENTS ) |
| n.setNodeValue( oldNodeValue ); |
| if ( how==DELETE_CONTENTS ) |
| return null; |
| Node newNode = n.cloneNode( false ); |
| newNode.setNodeValue( newNodeValue ); |
| return newNode; |
| } |
| |
| void checkIndex(Node refNode, int offset) throws DOMException |
| { |
| if (offset < 0) { |
| throw new DOMException( |
| DOMException.INDEX_SIZE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); |
| } |
| |
| int type = refNode.getNodeType(); |
| |
| // If the node contains text, ensure that the |
| // offset of the range is <= to the length of the text |
| if (type == Node.TEXT_NODE |
| || type == Node.CDATA_SECTION_NODE |
| || type == Node.COMMENT_NODE |
| || type == Node.PROCESSING_INSTRUCTION_NODE) { |
| if (offset > refNode.getNodeValue().length()) { |
| throw new DOMException(DOMException.INDEX_SIZE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); |
| } |
| } |
| else { |
| // Since the node is not text, ensure that the offset |
| // is valid with respect to the number of child nodes |
| if (offset > refNode.getChildNodes().getLength()) { |
| throw new DOMException(DOMException.INDEX_SIZE_ERR, |
| DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null)); |
| } |
| } |
| } |
| |
| /** |
| * Given a node, calculate what the Range's root container |
| * for that node would be. |
| */ |
| private Node getRootContainer( Node node ) |
| { |
| if ( node==null ) |
| return null; |
| |
| while( node.getParentNode()!=null ) |
| node = node.getParentNode(); |
| return node; |
| } |
| |
| /** |
| * Returns true IFF the given node can serve as a container |
| * for a range's boundary points. |
| */ |
| private boolean isLegalContainer( Node node ) |
| { |
| if ( node==null ) |
| return false; |
| |
| while( node!=null ) |
| { |
| switch( node.getNodeType() ) |
| { |
| case Node.ENTITY_NODE: |
| case Node.NOTATION_NODE: |
| case Node.DOCUMENT_TYPE_NODE: |
| return false; |
| } |
| node = node.getParentNode(); |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * Finds the root container for the given node and determines |
| * if that root container is legal with respect to the |
| * DOM 2 specification. At present, that means the root |
| * container must be either an attribute, a document, |
| * or a document fragment. |
| */ |
| private boolean hasLegalRootContainer( Node node ) |
| { |
| if ( node==null ) |
| return false; |
| |
| Node rootContainer = getRootContainer( node ); |
| switch( rootContainer.getNodeType() ) |
| { |
| case Node.ATTRIBUTE_NODE: |
| case Node.DOCUMENT_NODE: |
| case Node.DOCUMENT_FRAGMENT_NODE: |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true IFF the given node can be contained by |
| * a range. |
| */ |
| private boolean isLegalContainedNode( Node node ) |
| { |
| if ( node==null ) |
| return false; |
| switch( node.getNodeType() ) |
| { |
| case Node.DOCUMENT_NODE: |
| case Node.DOCUMENT_FRAGMENT_NODE: |
| case Node.ATTRIBUTE_NODE: |
| case Node.ENTITY_NODE: |
| case Node.NOTATION_NODE: |
| return false; |
| } |
| return true; |
| } |
| |
| Node nextNode(Node node, boolean visitChildren) { |
| |
| if (node == null) return null; |
| |
| 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. |
| Node parent = node.getParentNode(); |
| while (parent != null |
| && parent != fDocument |
| ) { |
| result = parent.getNextSibling(); |
| if (result != null) { |
| return result; |
| } else { |
| parent = parent.getParentNode(); |
| } |
| |
| } // while (parent != null && parent != fRoot) { |
| |
| // end of list, return null |
| return null; |
| } |
| |
| /** is a an ancestor of b ? */ |
| boolean isAncestorOf(Node a, Node b) { |
| for (Node node=b; node != null; node=node.getParentNode()) { |
| if (node == a) return true; |
| } |
| return false; |
| } |
| |
| /** what is the index of the child in the parent */ |
| int indexOf(Node child, Node parent) { |
| if (child.getParentNode() != parent) return -1; |
| int i = 0; |
| for(Node node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) { |
| i++; |
| } |
| return i; |
| } |
| |
| /** |
| * 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. |
| * |
| * @param container A container node |
| * |
| * @param offset An offset within the container for which a selected node should |
| * be computed. If the offset is less than zero, or if the offset |
| * is greater than the number of children, the container is returned. |
| * |
| * @return Returns either a child node of the container or the |
| * container itself. |
| */ |
| private Node getSelectedNode( Node container, int offset ) |
| { |
| if ( container.getNodeType() == Node.TEXT_NODE ) |
| return container; |
| |
| // This case is an important convenience for |
| // traverseRightBoundary() |
| if ( offset<0 ) |
| return container; |
| |
| Node child = container.getFirstChild(); |
| while( child!=null && offset > 0 ) |
| { |
| --offset; |
| child = child.getNextSibling(); |
| } |
| if ( child!=null ) |
| return child; |
| return container; |
| } |
| |
| } |