0001: /*
0002: * The Apache Software License, Version 1.1
0003: *
0004: *
0005: * Copyright (c) 1999 The Apache Software Foundation. All rights
0006: * reserved.
0007: *
0008: * Redistribution and use in source and binary forms, with or without
0009: * modification, are permitted provided that the following conditions
0010: * are met:
0011: *
0012: * 1. Redistributions of source code must retain the above copyright
0013: * notice, this list of conditions and the following disclaimer.
0014: *
0015: * 2. Redistributions in binary form must reproduce the above copyright
0016: * notice, this list of conditions and the following disclaimer in
0017: * the documentation and/or other materials provided with the
0018: * distribution.
0019: *
0020: * 3. The end-user documentation included with the redistribution,
0021: * if any, must include the following acknowledgment:
0022: * "This product includes software developed by the
0023: * Apache Software Foundation (http://www.apache.org/)."
0024: * Alternately, this acknowledgment may appear in the software itself,
0025: * if and wherever such third-party acknowledgments normally appear.
0026: *4dorse or promote products derived from this
0027: * software without prior written permission. For written
0028: * permission, please contact apache@apache.org.
0029: *
0030: * 5. Products derived from this software may not be called "Apache",
0031: * nor may "Apache" appear in their name, without prior written
0032: * permission of the Apache Software Foundation.
0033: *
0034: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0035: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0036: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0037: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0038: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0039: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0040: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0041: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0042: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0043: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0044: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0045: * SUCH DAMAGE.
0046: * ====================================================================
0047: *
0048: * This software consists of voluntary contributions made by many
0049: * individuals on behalf of the Apache Software Foundation and was
0050: * originally based on software copyright (c) 1999, International
0051: * Business Machines, Inc., http://www.apache.org. For more
0052: * information on the Apache Software Foundation, please see
0053: * <http://www.apache.org/>.
0054: */
0055:
0056: package org.apache.xerces.dom;
0057:
0058: import java.util.Vector;
0059:
0060: import org.w3c.dom.DOMException;
0061: import org.w3c.dom.DocumentFragment;
0062: import org.w3c.dom.Document;
0063: import org.w3c.dom.Node;
0064: import org.w3c.dom.CharacterData;
0065: import org.w3c.dom.ranges.Range;
0066: import org.w3c.dom.ranges.RangeException;
0067:
0068: /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
0069: * <p> Please see the API documentation for the interface classes
0070: * and use the interfaces in your client programs.
0071: */
0072: public class RangeImpl implements Range {
0073:
0074: //
0075: // Constants
0076: //
0077:
0078: //
0079: // Data
0080: //
0081:
0082: DocumentImpl fDocument;
0083: Node fStartContainer;
0084: Node fEndContainer;
0085: int fStartOffset;
0086: int fEndOffset;
0087: boolean fIsCollapsed;
0088: boolean fDetach = false;
0089: Node fInsertNode = null;
0090: Node fDeleteNode = null;
0091: Node fSplitNode = null;
0092:
0093: /** The constructor. Clients must use DocumentRange.createRange(),
0094: * because it registers the Range with the document, so it can
0095: * be fixed-up.
0096: */
0097: public RangeImpl(DocumentImpl document) {
0098: fDocument = document;
0099: fStartContainer = document;
0100: fEndContainer = document;
0101: fStartOffset = 0;
0102: fEndOffset = 0;
0103: fDetach = false;
0104: }
0105:
0106: public Node getStartContainer() {
0107: return fStartContainer;
0108: }
0109:
0110: public int getStartOffset() {
0111: return fStartOffset;
0112: }
0113:
0114: public Node getEndContainer() {
0115: return fEndContainer;
0116: }
0117:
0118: public int getEndOffset() {
0119: return fEndOffset;
0120: }
0121:
0122: public boolean getCollapsed() {
0123: return (fStartContainer == fEndContainer && fStartOffset == fEndOffset);
0124: }
0125:
0126: public Node getCommonAncestorContainer() {
0127: Vector startV = new Vector();
0128: Node node;
0129: for (node = fStartContainer; node != null; node = node
0130: .getParentNode()) {
0131: startV.addElement(node);
0132: }
0133: Vector endV = new Vector();
0134: for (node = fEndContainer; node != null; node = node
0135: .getParentNode()) {
0136: endV.addElement(node);
0137: }
0138: int s = startV.size() - 1;
0139: int e = endV.size() - 1;
0140: Object result = null;
0141: while (s >= 0 && e >= 0) {
0142: if (startV.elementAt(s) == endV.elementAt(e)) {
0143: result = startV.elementAt(s);
0144: } else {
0145: break;
0146: }
0147: --s;
0148: --e;
0149: }
0150: return (Node) result;
0151: }
0152:
0153: public void setStart(Node refNode, int offset)
0154: throws RangeException, DOMException {
0155: if (fDetach) {
0156: throw new DOMException(DOMException.INVALID_STATE_ERR,
0157: "DOM011 Invalid state");
0158: }
0159: if (!isLegalContainer(refNode)) {
0160: throw new RangeExceptionImpl(
0161: RangeException.INVALID_NODE_TYPE_ERR,
0162: "DOM012 Invalid node type");
0163: }
0164:
0165: checkIndex(refNode, offset);
0166:
0167: fStartContainer = refNode;
0168: fStartOffset = offset;
0169: }
0170:
0171: public void setEnd(Node refNode, int offset) throws RangeException,
0172: DOMException {
0173: if (fDetach) {
0174: throw new DOMException(DOMException.INVALID_STATE_ERR,
0175: "DOM011 Invalid state");
0176: }
0177: if (!isLegalContainer(refNode)) {
0178: throw new RangeExceptionImpl(
0179: RangeException.INVALID_NODE_TYPE_ERR,
0180: "DOM012 Invalid node type");
0181: }
0182:
0183: checkIndex(refNode, offset);
0184:
0185: fEndContainer = refNode;
0186: fEndOffset = offset;
0187: }
0188:
0189: public void setStartBefore(Node refNode) throws RangeException {
0190: if (fDetach) {
0191: throw new DOMException(DOMException.INVALID_STATE_ERR,
0192: "DOM011 Invalid state");
0193: }
0194: if (!hasLegalRootContainer(refNode)
0195: || !isLegalContainedNode(refNode)) {
0196: throw new RangeExceptionImpl(
0197: RangeException.INVALID_NODE_TYPE_ERR,
0198: "DOM012 Invalid node type");
0199: }
0200: fStartContainer = refNode.getParentNode();
0201: int i = 0;
0202: for (Node n = refNode; n != null; n = n.getPreviousSibling()) {
0203: i++;
0204: }
0205: fStartOffset = i - 1;
0206: }
0207:
0208: public void setStartAfter(Node refNode) throws RangeException {
0209: if (fDetach) {
0210: throw new DOMException(DOMException.INVALID_STATE_ERR,
0211: "DOM011 Invalid state");
0212: }
0213: if (!hasLegalRootContainer(refNode)
0214: || !isLegalContainedNode(refNode)) {
0215: throw new RangeExceptionImpl(
0216: RangeException.INVALID_NODE_TYPE_ERR,
0217: "DOM012 Invalid node type");
0218: }
0219: fStartContainer = refNode.getParentNode();
0220: int i = 0;
0221: for (Node n = refNode; n != null; n = n.getPreviousSibling()) {
0222: i++;
0223: }
0224: fStartOffset = i;
0225: }
0226:
0227: public void setEndBefore(Node refNode) throws RangeException {
0228: if (fDetach) {
0229: throw new DOMException(DOMException.INVALID_STATE_ERR,
0230: "DOM011 Invalid state");
0231: }
0232: if (!hasLegalRootContainer(refNode)
0233: || !isLegalContainedNode(refNode)) {
0234: throw new RangeExceptionImpl(
0235: RangeException.INVALID_NODE_TYPE_ERR,
0236: "DOM012 Invalid node type");
0237: }
0238: fEndContainer = refNode.getParentNode();
0239: int i = 0;
0240: for (Node n = refNode; n != null; n = n.getPreviousSibling()) {
0241: i++;
0242: }
0243: fEndOffset = i - 1;
0244: }
0245:
0246: public void setEndAfter(Node refNode) throws RangeException {
0247: if (fDetach) {
0248: throw new DOMException(DOMException.INVALID_STATE_ERR,
0249: "DOM011 Invalid state");
0250: }
0251: if (!hasLegalRootContainer(refNode)
0252: || !isLegalContainedNode(refNode)) {
0253: throw new RangeExceptionImpl(
0254: RangeException.INVALID_NODE_TYPE_ERR,
0255: "DOM012 Invalid node type");
0256: }
0257: fEndContainer = refNode.getParentNode();
0258: int i = 0;
0259: for (Node n = refNode; n != null; n = n.getPreviousSibling()) {
0260: i++;
0261: }
0262: fEndOffset = i;
0263: }
0264:
0265: public void collapse(boolean toStart) {
0266:
0267: if (fDetach) {
0268: throw new DOMException(DOMException.INVALID_STATE_ERR,
0269: "DOM011 Invalid state");
0270: }
0271:
0272: if (toStart) {
0273: fEndContainer = fStartContainer;
0274: fEndOffset = fStartOffset;
0275: } else {
0276: fStartContainer = fEndContainer;
0277: fStartOffset = fEndOffset;
0278: }
0279: }
0280:
0281: public void selectNode(Node refNode) throws RangeException {
0282: if (fDetach) {
0283: throw new DOMException(DOMException.INVALID_STATE_ERR,
0284: "DOM011 Invalid state");
0285: }
0286: if (!isLegalContainer(refNode.getParentNode())
0287: || !isLegalContainedNode(refNode)) {
0288: throw new RangeExceptionImpl(
0289: RangeException.INVALID_NODE_TYPE_ERR,
0290: "DOM012 Invalid node type");
0291: }
0292: Node parent = refNode.getParentNode();
0293: if (parent != null) // REVIST: what to do if it IS null?
0294: {
0295: fStartContainer = parent;
0296: fEndContainer = parent;
0297: int i = 0;
0298: for (Node n = refNode; n != null; n = n
0299: .getPreviousSibling()) {
0300: i++;
0301: }
0302: fStartOffset = i - 1;
0303: fEndOffset = fStartOffset + 1;
0304: }
0305: }
0306:
0307: public void selectNodeContents(Node refNode) throws RangeException {
0308: if (fDetach) {
0309: throw new DOMException(DOMException.INVALID_STATE_ERR,
0310: "DOM011 Invalid state");
0311: }
0312: if (!isLegalContainer(refNode)) {
0313: throw new RangeExceptionImpl(
0314: RangeException.INVALID_NODE_TYPE_ERR,
0315: "DOM012 Invalid node type");
0316: }
0317: fStartContainer = refNode;
0318: fEndContainer = refNode;
0319: Node first = refNode.getFirstChild();
0320: fStartOffset = 0;
0321: if (first == null) {
0322: fEndOffset = 0;
0323: } else {
0324: int i = 0;
0325: for (Node n = first; n != null; n = n.getNextSibling()) {
0326: i++;
0327: }
0328: fEndOffset = i;
0329: }
0330:
0331: }
0332:
0333: public short compareBoundaryPoints(short how, Range sourceRange)
0334: throws DOMException {
0335: if (fDetach) {
0336: throw new DOMException(DOMException.INVALID_STATE_ERR,
0337: "DOM011 Invalid state");
0338: }
0339:
0340: Node endPointA;
0341: Node endPointB;
0342: int offsetA;
0343: int offsetB;
0344:
0345: if (how == START_TO_START) {
0346: endPointA = sourceRange.getStartContainer();
0347: endPointB = fStartContainer;
0348: offsetA = sourceRange.getStartOffset();
0349: offsetB = fStartOffset;
0350: } else if (how == START_TO_END) {
0351: endPointA = sourceRange.getStartContainer();
0352: endPointB = fEndContainer;
0353: offsetA = sourceRange.getStartOffset();
0354: offsetB = fEndOffset;
0355: } else if (how == END_TO_START) {
0356: endPointA = sourceRange.getEndContainer();
0357: endPointB = fStartContainer;
0358: offsetA = sourceRange.getEndOffset();
0359: offsetB = fStartOffset;
0360: } else {
0361: endPointA = sourceRange.getEndContainer();
0362: endPointB = fEndContainer;
0363: offsetA = sourceRange.getEndOffset();
0364: offsetB = fEndOffset;
0365: }
0366:
0367: // The DOM Spec outlines four cases that need to be tested
0368: // to compare two range boundary points:
0369: // case 1: same container
0370: // case 2: Child C of container A is ancestor of B
0371: // case 3: Child C of container B is ancestor of A
0372: // case 4: preorder traversal of context tree.
0373:
0374: // case 1: same container
0375: if (endPointA == endPointB) {
0376: if (offsetA < offsetB)
0377: return 1;
0378: if (offsetA == offsetB)
0379: return 0;
0380: return -1;
0381: }
0382: // case 2: Child C of container A is ancestor of B
0383: // This can be quickly tested by walking the parent chain of B
0384: for (Node c = endPointB, p = c.getParentNode(); p != null; c = p, p = p
0385: .getParentNode()) {
0386: if (p == endPointA) {
0387: int index = indexOf(c, endPointA);
0388: if (offsetA <= index)
0389: return 1;
0390: return -1;
0391: }
0392: }
0393:
0394: // case 3: Child C of container B is ancestor of A
0395: // This can be quickly tested by walking the parent chain of A
0396: for (Node c = endPointA, p = c.getParentNode(); p != null; c = p, p = p
0397: .getParentNode()) {
0398: if (p == endPointB) {
0399: int index = indexOf(c, endPointB);
0400: if (index < offsetB)
0401: return 1;
0402: return -1;
0403: }
0404: }
0405:
0406: // case 4: preorder traversal of context tree.
0407: // Instead of literally walking the context tree in pre-order,
0408: // we use relative node depth walking which is usually faster
0409:
0410: int depthDiff = 0;
0411: for (Node n = endPointA; n != null; n = n.getParentNode())
0412: depthDiff++;
0413: for (Node n = endPointB; n != null; n = n.getParentNode())
0414: depthDiff--;
0415: while (depthDiff > 0) {
0416: endPointA = endPointA.getParentNode();
0417: depthDiff--;
0418: }
0419: while (depthDiff < 0) {
0420: endPointB = endPointB.getParentNode();
0421: depthDiff++;
0422: }
0423: for (Node pA = endPointA.getParentNode(), pB = endPointB
0424: .getParentNode(); pA != pB; pA = pA.getParentNode(), pB = pB
0425: .getParentNode()) {
0426: endPointA = pA;
0427: endPointB = pB;
0428: }
0429: for (Node n = endPointA.getNextSibling(); n != null; n = n
0430: .getNextSibling()) {
0431: if (n == endPointB) {
0432: return 1;
0433: }
0434: }
0435: return -1;
0436: }
0437:
0438: public void deleteContents() throws DOMException {
0439: traverseContents(DELETE_CONTENTS);
0440: }
0441:
0442: public DocumentFragment extractContents() throws DOMException {
0443: return traverseContents(EXTRACT_CONTENTS);
0444: }
0445:
0446: public DocumentFragment cloneContents() throws DOMException {
0447: return traverseContents(CLONE_CONTENTS);
0448: }
0449:
0450: public void insertNode(Node newNode) throws DOMException,
0451: RangeException {
0452: if (newNode == null)
0453: return; //throw exception?
0454:
0455: if (fDetach) {
0456: throw new DOMException(DOMException.INVALID_STATE_ERR,
0457: "DOM011 Invalid state");
0458: }
0459: if (fDocument != newNode.getOwnerDocument()) {
0460: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
0461: "DOM004 Wrong document");
0462: }
0463:
0464: int type = newNode.getNodeType();
0465: if (type == Node.ATTRIBUTE_NODE || type == Node.ENTITY_NODE
0466: || type == Node.NOTATION_NODE
0467: || type == Node.DOCUMENT_NODE) {
0468: throw new RangeExceptionImpl(
0469: RangeException.INVALID_NODE_TYPE_ERR,
0470: "DOM012 Invalid node type");
0471: }
0472: Node cloneCurrent;
0473: Node current;
0474: int currentChildren = 0;
0475:
0476: //boolean MULTIPLE_MODE = false;
0477: if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
0478:
0479: Node parent = fStartContainer.getParentNode();
0480: currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
0481: // split text node: results is 3 nodes..
0482: cloneCurrent = fStartContainer.cloneNode(false);
0483: ((TextImpl) cloneCurrent)
0484: .setNodeValueInternal((cloneCurrent.getNodeValue())
0485: .substring(fStartOffset));
0486: ((TextImpl) fStartContainer)
0487: .setNodeValueInternal((fStartContainer
0488: .getNodeValue()).substring(0, fStartOffset));
0489: Node next = fStartContainer.getNextSibling();
0490: if (next != null) {
0491: if (parent != null) {
0492: parent.insertBefore(newNode, next);
0493: parent.insertBefore(cloneCurrent, next);
0494: }
0495: } else {
0496: if (parent != null) {
0497: parent.appendChild(newNode);
0498: parent.appendChild(cloneCurrent);
0499: }
0500: }
0501: //update ranges after the insertion
0502: if (fEndContainer == fStartContainer) {
0503: fEndContainer = cloneCurrent; //endContainer is the new Node created
0504: fEndOffset -= fStartOffset;
0505: } else if (fEndContainer == parent) { //endContainer was not a text Node.
0506: //endOffset + = number_of_children_added
0507: fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
0508: }
0509:
0510: // signal other Ranges to update their start/end containers/offsets
0511: signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
0512:
0513: } else { // ! TEXT_NODE
0514: if (fEndContainer == fStartContainer) //need to remember number of kids
0515: currentChildren = fEndContainer.getChildNodes()
0516: .getLength();
0517:
0518: current = fStartContainer.getFirstChild();
0519: int i = 0;
0520: for (i = 0; i < fStartOffset && current != null; i++) {
0521: current = current.getNextSibling();
0522: }
0523: if (current != null) {
0524: fStartContainer.insertBefore(newNode, current);
0525: } else {
0526: fStartContainer.appendChild(newNode);
0527: }
0528: //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
0529: // insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
0530: if (fEndContainer == fStartContainer) { //update fEndOffset
0531: fEndOffset += (fEndContainer.getChildNodes()
0532: .getLength() - currentChildren);
0533: }
0534:
0535: }
0536: }
0537:
0538: public void surroundContents(Node newParent) throws DOMException,
0539: RangeException {
0540: if (newParent == null)
0541: return;
0542:
0543: if (fDetach) {
0544: throw new DOMException(DOMException.INVALID_STATE_ERR,
0545: "DOM011 Invalid state");
0546: }
0547: int type = newParent.getNodeType();
0548: if (type == Node.ATTRIBUTE_NODE || type == Node.ENTITY_NODE
0549: || type == Node.NOTATION_NODE
0550: || type == Node.DOCUMENT_TYPE_NODE
0551: || type == Node.DOCUMENT_NODE
0552: || type == Node.DOCUMENT_FRAGMENT_NODE) {
0553: throw new RangeExceptionImpl(
0554: RangeException.INVALID_NODE_TYPE_ERR,
0555: "DOM012 Invalid node type");
0556: }
0557:
0558: Node root = getCommonAncestorContainer();
0559:
0560: Node realStart = fStartContainer;
0561: Node realEnd = fEndContainer;
0562: if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
0563: realStart = fStartContainer.getParentNode();
0564: }
0565: if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
0566: realEnd = fEndContainer.getParentNode();
0567: }
0568:
0569: if (realStart != realEnd) {
0570: throw new RangeExceptionImpl(
0571: RangeException.BAD_BOUNDARYPOINTS_ERR,
0572: "DOM013 Bad boundary points");
0573: }
0574:
0575: DocumentFragment frag = extractContents();
0576: insertNode(newParent);
0577: newParent.appendChild(frag);
0578: selectNode(newParent);
0579: }
0580:
0581: public Range cloneRange() {
0582: if (fDetach) {
0583: throw new DOMException(DOMException.INVALID_STATE_ERR,
0584: "DOM011 Invalid state");
0585: }
0586:
0587: Range range = fDocument.createRange();
0588: range.setStart(fStartContainer, fStartOffset);
0589: range.setEnd(fEndContainer, fEndOffset);
0590: return range;
0591: }
0592:
0593: public String toString() {
0594: if (fDetach) {
0595: throw new DOMException(DOMException.INVALID_STATE_ERR,
0596: "DOM011 Invalid state");
0597: }
0598:
0599: Node node = fStartContainer;
0600: Node stopNode = fEndContainer;
0601: StringBuffer sb = new StringBuffer();
0602: if (fStartContainer.getNodeType() == Node.TEXT_NODE
0603: || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
0604: if (fStartContainer == fEndContainer) {
0605: sb.append(fStartContainer.getNodeValue().substring(
0606: fStartOffset, fEndOffset));
0607: return sb.toString();
0608: }
0609: sb.append(fStartContainer.getNodeValue().substring(
0610: fStartOffset));
0611: node = nextNode(node, true); //fEndContainer!=fStartContainer
0612:
0613: } else { //fStartContainer is not a TextNode
0614: node = node.getFirstChild();
0615: if (fStartOffset > 0) { //find a first node within a range, specified by fStartOffset
0616: int counter = 0;
0617: while (counter < fStartOffset && node != null) {
0618: node = node.getNextSibling();
0619: counter++;
0620: }
0621: }
0622: if (node == null) {
0623: node = nextNode(fStartContainer, false);
0624: }
0625: }
0626: if (fEndContainer.getNodeType() != Node.TEXT_NODE
0627: && fEndContainer.getNodeType() != Node.CDATA_SECTION_NODE) {
0628: int i = fEndOffset;
0629: stopNode = fEndContainer.getFirstChild();
0630: while (i > 0 && stopNode != null) {
0631: --i;
0632: stopNode = stopNode.getNextSibling();
0633: }
0634: if (stopNode == null)
0635: stopNode = nextNode(fEndContainer, false);
0636: }
0637: while (node != stopNode) { //look into all kids of the Range
0638: if (node == null)
0639: break;
0640: if (node.getNodeType() == Node.TEXT_NODE
0641: || node.getNodeType() == Node.CDATA_SECTION_NODE) {
0642: sb.append(node.getNodeValue());
0643: }
0644:
0645: node = nextNode(node, true);
0646: }
0647:
0648: if (fEndContainer.getNodeType() == Node.TEXT_NODE
0649: || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
0650: sb.append(fEndContainer.getNodeValue().substring(0,
0651: fEndOffset));
0652: }
0653: return sb.toString();
0654: }
0655:
0656: public void detach() {
0657: fDetach = true;
0658: fDocument.removeRange(this );
0659: }
0660:
0661: //
0662: // Mutation functions
0663: //
0664:
0665: /** Signal other Ranges to update their start/end
0666: * containers/offsets. The data has already been split
0667: * into the two Nodes.
0668: */
0669: void signalSplitData(Node node, Node newNode, int offset) {
0670: fSplitNode = node;
0671: // notify document
0672: fDocument.splitData(node, newNode, offset);
0673: fSplitNode = null;
0674: }
0675:
0676: /** Fix up this Range if another Range has split a Text Node
0677: * into 2 Nodes.
0678: */
0679: void receiveSplitData(Node node, Node newNode, int offset) {
0680: if (node == null || newNode == null)
0681: return;
0682: if (fSplitNode == node)
0683: return;
0684:
0685: if (node == fStartContainer
0686: && fStartContainer.getNodeType() == Node.TEXT_NODE) {
0687: if (fStartOffset > offset) {
0688: fStartOffset = fStartOffset - offset;
0689: fStartContainer = newNode;
0690: }
0691: }
0692: if (node == fEndContainer
0693: && fEndContainer.getNodeType() == Node.TEXT_NODE) {
0694: if (fEndOffset > offset) {
0695: fEndOffset = fEndOffset - offset;
0696: fEndContainer = newNode;
0697: }
0698: }
0699:
0700: }
0701:
0702: /** This function inserts text into a Node and invokes
0703: * a method to fix-up all other Ranges.
0704: */
0705: void deleteData(CharacterData node, int offset, int count) {
0706: fDeleteNode = node;
0707: node.deleteData(offset, count);
0708: fDeleteNode = null;
0709: }
0710:
0711: /** This function is called from DOM.
0712: * The text has already beeen inserted.
0713: * Fix-up any offsets.
0714: */
0715: void receiveDeletedText(Node node, int offset, int count) {
0716: if (node == null)
0717: return;
0718: if (fDeleteNode == node)
0719: return;
0720: if (node == fStartContainer
0721: && fStartContainer.getNodeType() == Node.TEXT_NODE) {
0722: if (fStartOffset > offset + count) {
0723: fStartOffset = offset
0724: + (fStartOffset - (offset + count));
0725: } else if (fStartOffset > offset) {
0726: fStartOffset = offset;
0727: }
0728: }
0729: if (node == fEndContainer
0730: && fEndContainer.getNodeType() == Node.TEXT_NODE) {
0731: if (fEndOffset > offset + count) {
0732: fEndOffset = offset + (fEndOffset - (offset + count));
0733: } else if (fEndOffset > offset) {
0734: fEndOffset = offset;
0735: }
0736: }
0737:
0738: }
0739:
0740: /** This function inserts text into a Node and invokes
0741: * a method to fix-up all other Ranges.
0742: */
0743: void insertData(CharacterData node, int index, String insert) {
0744: fInsertNode = node;
0745: node.insertData(index, insert);
0746: fInsertNode = null;
0747: }
0748:
0749: /** This function is called from DOM.
0750: * The text has already beeen inserted.
0751: * Fix-up any offsets.
0752: */
0753: void receiveInsertedText(Node node, int index, int len) {
0754: if (node == null)
0755: return;
0756: if (fInsertNode == node)
0757: return;
0758: if (node == fStartContainer
0759: && fStartContainer.getNodeType() == Node.TEXT_NODE) {
0760: if (index < fStartOffset) {
0761: fStartOffset = fStartOffset + len;
0762: }
0763: }
0764: if (node == fEndContainer
0765: && fEndContainer.getNodeType() == Node.TEXT_NODE) {
0766: if (index < fEndOffset) {
0767: fEndOffset = fEndOffset + len;
0768: }
0769: }
0770:
0771: }
0772:
0773: /** This function is called from DOM.
0774: * The text has already beeen replaced.
0775: * Fix-up any offsets.
0776: */
0777: void receiveReplacedText(Node node) {
0778: if (node == null)
0779: return;
0780: if (node == fStartContainer
0781: && fStartContainer.getNodeType() == Node.TEXT_NODE) {
0782: fStartOffset = 0;
0783: }
0784: if (node == fEndContainer
0785: && fEndContainer.getNodeType() == Node.TEXT_NODE) {
0786: fEndOffset = 0;
0787: }
0788:
0789: }
0790:
0791: /** This function is called from the DOM.
0792: * This node has already been inserted into the DOM.
0793: * Fix-up any offsets.
0794: */
0795: public void insertedNodeFromDOM(Node node) {
0796: if (node == null)
0797: return;
0798: if (fInsertNode == node)
0799: return;
0800:
0801: Node parent = node.getParentNode();
0802:
0803: if (parent == fStartContainer) {
0804: int index = indexOf(node, fStartContainer);
0805: if (index < fStartOffset) {
0806: fStartOffset++;
0807: }
0808: }
0809:
0810: if (parent == fEndContainer) {
0811: int index = indexOf(node, fEndContainer);
0812: if (index < fEndOffset) {
0813: fEndOffset++;
0814: }
0815: }
0816:
0817: }
0818:
0819: /** This function is called within Range
0820: * instead of Node.removeChild,
0821: * so that the range can remember that it is actively
0822: * removing this child.
0823: */
0824:
0825: Node fRemoveChild = null;
0826:
0827: Node removeChild(Node parent, Node child) {
0828: fRemoveChild = child;
0829: Node n = parent.removeChild(child);
0830: fRemoveChild = null;
0831: return n;
0832: }
0833:
0834: /** This function must be called by the DOM _BEFORE_
0835: * a node is deleted, because at that time it is
0836: * connected in the DOM tree, which we depend on.
0837: */
0838: void removeNode(Node node) {
0839: if (node == null)
0840: return;
0841: if (fRemoveChild == node)
0842: return;
0843:
0844: Node parent = node.getParentNode();
0845:
0846: if (parent == fStartContainer) {
0847: int index = indexOf(node, fStartContainer);
0848: if (index < fStartOffset) {
0849: fStartOffset--;
0850: }
0851: }
0852:
0853: if (parent == fEndContainer) {
0854: int index = indexOf(node, fEndContainer);
0855: if (index < fEndOffset) {
0856: fEndOffset--;
0857: }
0858: }
0859: //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted
0860: if (parent != fStartContainer || parent != fEndContainer) {
0861: if (isAncestorOf(node, fStartContainer)) {
0862: fStartContainer = parent;
0863: fStartOffset = indexOf(node, parent);
0864: }
0865: if (isAncestorOf(node, fEndContainer)) {
0866: fEndContainer = parent;
0867: fEndOffset = indexOf(node, parent);
0868: }
0869: }
0870:
0871: }
0872:
0873: //
0874: // Utility functions.
0875: //
0876:
0877: // parameters for traverseContents(int)
0878: //REVIST: use boolean, since there are only 2 now...
0879: static final int EXTRACT_CONTENTS = 1;
0880: static final int CLONE_CONTENTS = 2;
0881: static final int DELETE_CONTENTS = 3;
0882:
0883: /**
0884: * This is the master routine invoked to visit the nodes
0885: * selected by this range. For each such node, different
0886: * actions are taken depending on the value of the
0887: * <code>how</code> argument.
0888: *
0889: * @param how Specifies what type of traversal is being
0890: * requested (extract, clone, or delete).
0891: * Legal values for this argument are:
0892: *
0893: * <ol>
0894: * <li><code>EXTRACT_CONTENTS</code> - will produce
0895: * a document fragment containing the range's content.
0896: * Partially selected nodes are copied, but fully
0897: * selected nodes are moved.
0898: *
0899: * <li><code>CLONE_CONTENTS</code> - will leave the
0900: * context tree of the range undisturbed, but sill
0901: * produced cloned content in a document fragment
0902: *
0903: * <li><code>DELETE_CONTENTS</code> - will delete from
0904: * the context tree of the range, all fully selected
0905: * nodes.
0906: * </ol>
0907: *
0908: * @return Returns a document fragment containing any
0909: * copied or extracted nodes. If the <code>how</code>
0910: * parameter was <code>DELETE_CONTENTS</code>, the
0911: * return value is null.
0912: */
0913: private DocumentFragment traverseContents(int how)
0914: throws DOMException {
0915: if (fStartContainer == null || fEndContainer == null) {
0916: return null; // REVIST: Throw exception?
0917: }
0918:
0919: //Check for a detached range.
0920: if (fDetach) {
0921: throw new DOMException(DOMException.INVALID_STATE_ERR,
0922: "DOM011 Invalid state");
0923: }
0924:
0925: /*
0926: Traversal is accomplished by first determining the
0927: relationship between the endpoints of the range.
0928: For each of four significant relationships, we will
0929: delegate the traversal call to a method that
0930: can make appropriate assumptions.
0931: */
0932:
0933: // case 1: same container
0934: if (fStartContainer == fEndContainer)
0935: return traverseSameContainer(how);
0936:
0937: // case 2: Child C of start container is ancestor of end container
0938: // This can be quickly tested by walking the parent chain of
0939: // end container
0940: int endContainerDepth = 0;
0941: for (Node c = fEndContainer, p = c.getParentNode(); p != null; c = p, p = p
0942: .getParentNode()) {
0943: if (p == fStartContainer)
0944: return traverseCommonStartContainer(c, how);
0945: ++endContainerDepth;
0946: }
0947:
0948: // case 3: Child C of container B is ancestor of A
0949: // This can be quickly tested by walking the parent chain of A
0950: int startContainerDepth = 0;
0951: for (Node c = fStartContainer, p = c.getParentNode(); p != null; c = p, p = p
0952: .getParentNode()) {
0953: if (p == fEndContainer)
0954: return traverseCommonEndContainer(c, how);
0955: ++startContainerDepth;
0956: }
0957:
0958: // case 4: There is a common ancestor container. Find the
0959: // ancestor siblings that are children of that container.
0960: int depthDiff = startContainerDepth - endContainerDepth;
0961:
0962: Node startNode = fStartContainer;
0963: while (depthDiff > 0) {
0964: startNode = startNode.getParentNode();
0965: depthDiff--;
0966: }
0967:
0968: Node endNode = fEndContainer;
0969: while (depthDiff < 0) {
0970: endNode = endNode.getParentNode();
0971: depthDiff++;
0972: }
0973:
0974: // ascend the ancestor hierarchy until we have a common parent.
0975: for (Node sp = startNode.getParentNode(), ep = endNode
0976: .getParentNode(); sp != ep; sp = sp.getParentNode(), ep = ep
0977: .getParentNode()) {
0978: startNode = sp;
0979: endNode = ep;
0980: }
0981: return traverseCommonAncestors(startNode, endNode, how);
0982: }
0983:
0984: /**
0985: * Visits the nodes selected by this range when we know
0986: * a-priori that the start and end containers are the same.
0987: * This method is invoked by the generic <code>traverse</code>
0988: * method.
0989: *
0990: * @param how Specifies what type of traversal is being
0991: * requested (extract, clone, or delete).
0992: * Legal values for this argument are:
0993: *
0994: * <ol>
0995: * <li><code>EXTRACT_CONTENTS</code> - will produce
0996: * a document fragment containing the range's content.
0997: * Partially selected nodes are copied, but fully
0998: * selected nodes are moved.
0999: *
1000: * <li><code>CLONE_CONTENTS</code> - will leave the
1001: * context tree of the range undisturbed, but sill
1002: * produced cloned content in a document fragment
1003: *
1004: * <li><code>DELETE_CONTENTS</code> - will delete from
1005: * the context tree of the range, all fully selected
1006: * nodes.
1007: * </ol>
1008: *
1009: * @return Returns a document fragment containing any
1010: * copied or extracted nodes. If the <code>how</code>
1011: * parameter was <code>DELETE_CONTENTS</code>, the
1012: * return value is null.
1013: */
1014: private DocumentFragment traverseSameContainer(int how) {
1015: DocumentFragment frag = null;
1016: if (how != DELETE_CONTENTS)
1017: frag = fDocument.createDocumentFragment();
1018:
1019: // If selection is empty, just return the fragment
1020: if (fStartOffset == fEndOffset)
1021: return frag;
1022:
1023: // Text node needs special case handling
1024: if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
1025: // get the substring
1026: String s = fStartContainer.getNodeValue();
1027: String sub = s.substring(fStartOffset, fEndOffset);
1028:
1029: // set the original text node to its new value
1030: if (how != CLONE_CONTENTS) {
1031: fStartContainer.setNodeValue(s.substring(0,
1032: fStartOffset)
1033: + s.substring(fEndOffset));
1034:
1035: // Nothing is partially selected, so collapse to start point
1036: collapse(true);
1037: }
1038: if (how == DELETE_CONTENTS)
1039: return null;
1040: frag.appendChild(fDocument.createTextNode(sub));
1041: return frag;
1042: }
1043:
1044: // Copy nodes between the start/end offsets.
1045: Node n = getSelectedNode(fStartContainer, fStartOffset);
1046: int cnt = fEndOffset - fStartOffset;
1047: while (cnt > 0) {
1048: Node sibling = n.getNextSibling();
1049: Node xferNode = traverseFullySelected(n, how);
1050: if (frag != null)
1051: frag.appendChild(xferNode);
1052: --cnt;
1053: n = sibling;
1054: }
1055:
1056: // Nothing is partially selected, so collapse to start point
1057: if (how != CLONE_CONTENTS)
1058: collapse(true);
1059: return frag;
1060: }
1061:
1062: /**
1063: * Visits the nodes selected by this range when we know
1064: * a-priori that the start and end containers are not the
1065: * same, but the start container is an ancestor of the
1066: * end container. This method is invoked by the generic
1067: * <code>traverse</code> method.
1068: *
1069: * @param endAncestor
1070: * The ancestor of the end container that is a direct child
1071: * of the start container.
1072: *
1073: * @param how Specifies what type of traversal is being
1074: * requested (extract, clone, or delete).
1075: * Legal values for this argument are:
1076: *
1077: * <ol>
1078: * <li><code>EXTRACT_CONTENTS</code> - will produce
1079: * a document fragment containing the range's content.
1080: * Partially selected nodes are copied, but fully
1081: * selected nodes are moved.
1082: *
1083: * <li><code>CLONE_CONTENTS</code> - will leave the
1084: * context tree of the range undisturbed, but sill
1085: * produced cloned content in a document fragment
1086: *
1087: * <li><code>DELETE_CONTENTS</code> - will delete from
1088: * the context tree of the range, all fully selected
1089: * nodes.
1090: * </ol>
1091: *
1092: * @return Returns a document fragment containing any
1093: * copied or extracted nodes. If the <code>how</code>
1094: * parameter was <code>DELETE_CONTENTS</code>, the
1095: * return value is null.
1096: */
1097: private DocumentFragment traverseCommonStartContainer(
1098: Node endAncestor, int how) {
1099: DocumentFragment frag = null;
1100: if (how != DELETE_CONTENTS)
1101: frag = fDocument.createDocumentFragment();
1102: Node n = traverseRightBoundary(endAncestor, how);
1103: if (frag != null)
1104: frag.appendChild(n);
1105:
1106: int endIdx = indexOf(endAncestor, fStartContainer);
1107: int cnt = endIdx - fStartOffset;
1108: if (cnt <= 0) {
1109: // Collapse to just before the endAncestor, which
1110: // is partially selected.
1111: if (how != CLONE_CONTENTS) {
1112: setEndBefore(endAncestor);
1113: collapse(false);
1114: }
1115: return frag;
1116: }
1117:
1118: n = endAncestor.getPreviousSibling();
1119: while (cnt > 0) {
1120: Node sibling = n.getPreviousSibling();
1121: Node xferNode = traverseFullySelected(n, how);
1122: if (frag != null)
1123: frag.insertBefore(xferNode, frag.getFirstChild());
1124: --cnt;
1125: n = sibling;
1126: }
1127: // Collapse to just before the endAncestor, which
1128: // is partially selected.
1129: if (how != CLONE_CONTENTS) {
1130: setEndBefore(endAncestor);
1131: collapse(false);
1132: }
1133: return frag;
1134: }
1135:
1136: /**
1137: * Visits the nodes selected by this range when we know
1138: * a-priori that the start and end containers are not the
1139: * same, but the end container is an ancestor of the
1140: * start container. This method is invoked by the generic
1141: * <code>traverse</code> method.
1142: *
1143: * @param startAncestor
1144: * The ancestor of the start container that is a direct
1145: * child of the end container.
1146: *
1147: * @param how Specifies what type of traversal is being
1148: * requested (extract, clone, or delete).
1149: * Legal values for this argument are:
1150: *
1151: * <ol>
1152: * <li><code>EXTRACT_CONTENTS</code> - will produce
1153: * a document fragment containing the range's content.
1154: * Partially selected nodes are copied, but fully
1155: * selected nodes are moved.
1156: *
1157: * <li><code>CLONE_CONTENTS</code> - will leave the
1158: * context tree of the range undisturbed, but sill
1159: * produced cloned content in a document fragment
1160: *
1161: * <li><code>DELETE_CONTENTS</code> - will delete from
1162: * the context tree of the range, all fully selected
1163: * nodes.
1164: * </ol>
1165: *
1166: * @return Returns a document fragment containing any
1167: * copied or extracted nodes. If the <code>how</code>
1168: * parameter was <code>DELETE_CONTENTS</code>, the
1169: * return value is null.
1170: */
1171: private DocumentFragment traverseCommonEndContainer(
1172: Node startAncestor, int how) {
1173: DocumentFragment frag = null;
1174: if (how != DELETE_CONTENTS)
1175: frag = fDocument.createDocumentFragment();
1176: Node n = traverseLeftBoundary(startAncestor, how);
1177: if (frag != null)
1178: frag.appendChild(n);
1179: int startIdx = indexOf(startAncestor, fEndContainer);
1180: ++startIdx; // Because we already traversed it....
1181:
1182: int cnt = fEndOffset - startIdx;
1183: n = startAncestor.getNextSibling();
1184: while (cnt > 0) {
1185: Node sibling = n.getNextSibling();
1186: Node xferNode = traverseFullySelected(n, how);
1187: if (frag != null)
1188: frag.appendChild(xferNode);
1189: --cnt;
1190: n = sibling;
1191: }
1192:
1193: if (how != CLONE_CONTENTS) {
1194: setStartAfter(startAncestor);
1195: collapse(true);
1196: }
1197:
1198: return frag;
1199: }
1200:
1201: /**
1202: * Visits the nodes selected by this range when we know
1203: * a-priori that the start and end containers are not
1204: * the same, and we also know that neither the start
1205: * nor end container is an ancestor of the other.
1206: * This method is invoked by
1207: * the generic <code>traverse</code> method.
1208: *
1209: * @param startAncestor
1210: * Given a common ancestor of the start and end containers,
1211: * this parameter is the ancestor (or self) of the start
1212: * container that is a direct child of the common ancestor.
1213: *
1214: * @param endAncestor
1215: * Given a common ancestor of the start and end containers,
1216: * this parameter is the ancestor (or self) of the end
1217: * container that is a direct child of the common ancestor.
1218: *
1219: * @param how Specifies what type of traversal is being
1220: * requested (extract, clone, or delete).
1221: * Legal values for this argument are:
1222: *
1223: * <ol>
1224: * <li><code>EXTRACT_CONTENTS</code> - will produce
1225: * a document fragment containing the range's content.
1226: * Partially selected nodes are copied, but fully
1227: * selected nodes are moved.
1228: *
1229: * <li><code>CLONE_CONTENTS</code> - will leave the
1230: * context tree of the range undisturbed, but sill
1231: * produced cloned content in a document fragment
1232: *
1233: * <li><code>DELETE_CONTENTS</code> - will delete from
1234: * the context tree of the range, all fully selected
1235: * nodes.
1236: * </ol>
1237: *
1238: * @return Returns a document fragment containing any
1239: * copied or extracted nodes. If the <code>how</code>
1240: * parameter was <code>DELETE_CONTENTS</code>, the
1241: * return value is null.
1242: */
1243: private DocumentFragment traverseCommonAncestors(
1244: Node startAncestor, Node endAncestor, int how) {
1245: DocumentFragment frag = null;
1246: if (how != DELETE_CONTENTS)
1247: frag = fDocument.createDocumentFragment();
1248:
1249: Node n = traverseLeftBoundary(startAncestor, how);
1250: if (frag != null)
1251: frag.appendChild(n);
1252:
1253: Node commonParent = startAncestor.getParentNode();
1254: int startOffset = indexOf(startAncestor, commonParent);
1255: int endOffset = indexOf(endAncestor, commonParent);
1256: ++startOffset;
1257:
1258: int cnt = endOffset - startOffset;
1259: Node sibling = startAncestor.getNextSibling();
1260:
1261: while (cnt > 0) {
1262: Node nextSibling = sibling.getNextSibling();
1263: n = traverseFullySelected(sibling, how);
1264: if (frag != null)
1265: frag.appendChild(n);
1266: sibling = nextSibling;
1267: --cnt;
1268: }
1269:
1270: n = traverseRightBoundary(endAncestor, how);
1271: if (frag != null)
1272: frag.appendChild(n);
1273:
1274: if (how != CLONE_CONTENTS) {
1275: setStartAfter(startAncestor);
1276: collapse(true);
1277: }
1278: return frag;
1279: }
1280:
1281: /**
1282: * Traverses the "right boundary" of this range and
1283: * operates on each "boundary node" according to the
1284: * <code>how</code> parameter. It is a-priori assumed
1285: * by this method that the right boundary does
1286: * not contain the range's start container.
1287: * <p>
1288: * A "right boundary" is best visualized by thinking
1289: * of a sample tree:<pre>
1290: * A
1291: * /|\
1292: * / | \
1293: * / | \
1294: * B C D
1295: * /|\ /|\
1296: * E F G H I J
1297: * </pre>
1298: * Imagine first a range that begins between the
1299: * "E" and "F" nodes and ends between the
1300: * "I" and "J" nodes. The start container is
1301: * "B" and the end container is "D". Given this setup,
1302: * the following applies:
1303: * <p>
1304: * Partially Selected Nodes: B, D<br>
1305: * Fully Selected Nodes: F, G, C, H, I
1306: * <p>
1307: * The "right boundary" is the highest subtree node
1308: * that contains the ending container. The root of
1309: * this subtree is always partially selected.
1310: * <p>
1311: * In this example, the nodes that are traversed
1312: * as "right boundary" nodes are: H, I, and D.
1313: *
1314: * @param root The node that is the root of the "right boundary" subtree.
1315: *
1316: * @param how Specifies what type of traversal is being
1317: * requested (extract, clone, or delete).
1318: * Legal values for this argument are:
1319: *
1320: * <ol>
1321: * <li><code>EXTRACT_CONTENTS</code> - will produce
1322: * a node containing the boundaries content.
1323: * Partially selected nodes are copied, but fully
1324: * selected nodes are moved.
1325: *
1326: * <li><code>CLONE_CONTENTS</code> - will leave the
1327: * context tree of the range undisturbed, but will
1328: * produced cloned content.
1329: *
1330: * <li><code>DELETE_CONTENTS</code> - will delete from
1331: * the context tree of the range, all fully selected
1332: * nodes within the boundary.
1333: * </ol>
1334: *
1335: * @return Returns a node that is the result of visiting nodes.
1336: * If the traversal operation is
1337: * <code>DELETE_CONTENTS</code> the return value is null.
1338: */
1339: private Node traverseRightBoundary(Node root, int how) {
1340: Node next = getSelectedNode(fEndContainer, fEndOffset - 1);
1341: boolean isFullySelected = (next != fEndContainer);
1342:
1343: if (next == root)
1344: return traverseNode(next, isFullySelected, false, how);
1345:
1346: Node parent = next.getParentNode();
1347: Node clonedParent = traverseNode(parent, false, false, how);
1348:
1349: while (parent != null) {
1350: while (next != null) {
1351: Node prevSibling = next.getPreviousSibling();
1352: Node clonedChild = traverseNode(next, isFullySelected,
1353: false, how);
1354: if (how != DELETE_CONTENTS) {
1355: clonedParent.insertBefore(clonedChild, clonedParent
1356: .getFirstChild());
1357: }
1358: isFullySelected = true;
1359: next = prevSibling;
1360: }
1361: if (parent == root)
1362: return clonedParent;
1363:
1364: next = parent.getPreviousSibling();
1365: parent = parent.getParentNode();
1366: Node clonedGrandParent = traverseNode(parent, false, false,
1367: how);
1368: if (how != DELETE_CONTENTS)
1369: clonedGrandParent.appendChild(clonedParent);
1370: clonedParent = clonedGrandParent;
1371:
1372: }
1373:
1374: // should never occur
1375: return null;
1376: }
1377:
1378: /**
1379: * Traverses the "left boundary" of this range and
1380: * operates on each "boundary node" according to the
1381: * <code>how</code> parameter. It is a-priori assumed
1382: * by this method that the left boundary does
1383: * not contain the range's end container.
1384: * <p>
1385: * A "left boundary" is best visualized by thinking
1386: * of a sample tree:<pre>
1387: *
1388: * A
1389: * /|\
1390: * / | \
1391: * / | \
1392: * B C D
1393: * /|\ /|\
1394: * E F G H I J
1395: * </pre>
1396: * Imagine first a range that begins between the
1397: * "E" and "F" nodes and ends between the
1398: * "I" and "J" nodes. The start container is
1399: * "B" and the end container is "D". Given this setup,
1400: * the following applies:
1401: * <p>
1402: * Partially Selected Nodes: B, D<br>
1403: * Fully Selected Nodes: F, G, C, H, I
1404: * <p>
1405: * The "left boundary" is the highest subtree node
1406: * that contains the starting container. The root of
1407: * this subtree is always partially selected.
1408: * <p>
1409: * In this example, the nodes that are traversed
1410: * as "left boundary" nodes are: F, G, and B.
1411: *
1412: * @param root The node that is the root of the "left boundary" subtree.
1413: *
1414: * @param how Specifies what type of traversal is being
1415: * requested (extract, clone, or delete).
1416: * Legal values for this argument are:
1417: *
1418: * <ol>
1419: * <li><code>EXTRACT_CONTENTS</code> - will produce
1420: * a node containing the boundaries content.
1421: * Partially selected nodes are copied, but fully
1422: * selected nodes are moved.
1423: *
1424: * <li><code>CLONE_CONTENTS</code> - will leave the
1425: * context tree of the range undisturbed, but will
1426: * produced cloned content.
1427: *
1428: * <li><code>DELETE_CONTENTS</code> - will delete from
1429: * the context tree of the range, all fully selected
1430: * nodes within the boundary.
1431: * </ol>
1432: *
1433: * @return Returns a node that is the result of visiting nodes.
1434: * If the traversal operation is
1435: * <code>DELETE_CONTENTS</code> the return value is null.
1436: */
1437: private Node traverseLeftBoundary(Node root, int how) {
1438: Node next = getSelectedNode(getStartContainer(),
1439: getStartOffset());
1440: boolean isFullySelected = (next != getStartContainer());
1441:
1442: if (next == root)
1443: return traverseNode(next, isFullySelected, true, how);
1444:
1445: Node parent = next.getParentNode();
1446: Node clonedParent = traverseNode(parent, false, true, how);
1447:
1448: while (parent != null) {
1449: while (next != null) {
1450: Node nextSibling = next.getNextSibling();
1451: Node clonedChild = traverseNode(next, isFullySelected,
1452: true, how);
1453: if (how != DELETE_CONTENTS)
1454: clonedParent.appendChild(clonedChild);
1455: isFullySelected = true;
1456: next = nextSibling;
1457: }
1458: if (parent == root)
1459: return clonedParent;
1460:
1461: next = parent.getNextSibling();
1462: parent = parent.getParentNode();
1463: Node clonedGrandParent = traverseNode(parent, false, true,
1464: how);
1465: if (how != DELETE_CONTENTS)
1466: clonedGrandParent.appendChild(clonedParent);
1467: clonedParent = clonedGrandParent;
1468:
1469: }
1470:
1471: // should never occur
1472: return null;
1473:
1474: }
1475:
1476: /**
1477: * Utility method for traversing a single node.
1478: * Does not properly handle a text node containing both the
1479: * start and end offsets. Such nodes should
1480: * have been previously detected and been routed to traverseTextNode.
1481: *
1482: * @param n The node to be traversed.
1483: *
1484: * @param isFullySelected
1485: * Set to true if the node is fully selected. Should be
1486: * false otherwise.
1487: * Note that although the DOM 2 specification says that a
1488: * text node that is boththe start and end container is not
1489: * selected, we treat it here as if it were partially
1490: * selected.
1491: *
1492: * @param isLeft Is true if we are traversing the node as part of navigating
1493: * the "left boundary" of the range. If this value is false,
1494: * it implies we are navigating the "right boundary" of the
1495: * range.
1496: *
1497: * @param how Specifies what type of traversal is being
1498: * requested (extract, clone, or delete).
1499: * Legal values for this argument are:
1500: *
1501: * <ol>
1502: * <li><code>EXTRACT_CONTENTS</code> - will simply
1503: * return the original node.
1504: *
1505: * <li><code>CLONE_CONTENTS</code> - will leave the
1506: * context tree of the range undisturbed, but will
1507: * return a cloned node.
1508: *
1509: * <li><code>DELETE_CONTENTS</code> - will delete the
1510: * node from it's parent, but will return null.
1511: * </ol>
1512: *
1513: * @return Returns a node that is the result of visiting the node.
1514: * If the traversal operation is
1515: * <code>DELETE_CONTENTS</code> the return value is null.
1516: */
1517: private Node traverseNode(Node n, boolean isFullySelected,
1518: boolean isLeft, int how) {
1519: if (isFullySelected)
1520: return traverseFullySelected(n, how);
1521: if (n.getNodeType() == Node.TEXT_NODE)
1522: return traverseTextNode(n, isLeft, how);
1523: return traversePartiallySelected(n, how);
1524: }
1525:
1526: /**
1527: * Utility method for traversing a single node when
1528: * we know a-priori that the node if fully
1529: * selected.
1530: *
1531: * @param n The node to be traversed.
1532: *
1533: * @param how Specifies what type of traversal is being
1534: * requested (extract, clone, or delete).
1535: * Legal values for this argument are:
1536: *
1537: * <ol>
1538: * <li><code>EXTRACT_CONTENTS</code> - will simply
1539: * return the original node.
1540: *
1541: * <li><code>CLONE_CONTENTS</code> - will leave the
1542: * context tree of the range undisturbed, but will
1543: * return a cloned node.
1544: *
1545: * <li><code>DELETE_CONTENTS</code> - will delete the
1546: * node from it's parent, but will return null.
1547: * </ol>
1548: *
1549: * @return Returns a node that is the result of visiting the node.
1550: * If the traversal operation is
1551: * <code>DELETE_CONTENTS</code> the return value is null.
1552: */
1553: private Node traverseFullySelected(Node n, int how) {
1554: switch (how) {
1555: case CLONE_CONTENTS:
1556: return n.cloneNode(true);
1557: case EXTRACT_CONTENTS:
1558: if (n.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
1559: // TBD: This should be a HIERARCHY_REQUEST_ERR
1560: throw new RangeExceptionImpl(
1561: RangeException.INVALID_NODE_TYPE_ERR,
1562: "DOM012 Invalid node type");
1563: }
1564: return n;
1565: case DELETE_CONTENTS:
1566: n.getParentNode().removeChild(n);
1567: return null;
1568: }
1569: return null;
1570: }
1571:
1572: /**
1573: * Utility method for traversing a single node when
1574: * we know a-priori that the node if partially
1575: * selected and is not a text node.
1576: *
1577: * @param n The node to be traversed.
1578: *
1579: * @param how Specifies what type of traversal is being
1580: * requested (extract, clone, or delete).
1581: * Legal values for this argument are:
1582: *
1583: * <ol>
1584: * <li><code>EXTRACT_CONTENTS</code> - will simply
1585: * return the original node.
1586: *
1587: * <li><code>CLONE_CONTENTS</code> - will leave the
1588: * context tree of the range undisturbed, but will
1589: * return a cloned node.
1590: *
1591: * <li><code>DELETE_CONTENTS</code> - will delete the
1592: * node from it's parent, but will return null.
1593: * </ol>
1594: *
1595: * @return Returns a node that is the result of visiting the node.
1596: * If the traversal operation is
1597: * <code>DELETE_CONTENTS</code> the return value is null.
1598: */
1599: private Node traversePartiallySelected(Node n, int how) {
1600: switch (how) {
1601: case DELETE_CONTENTS:
1602: return null;
1603: case CLONE_CONTENTS:
1604: case EXTRACT_CONTENTS:
1605: return n.cloneNode(false);
1606: }
1607: return null;
1608: }
1609:
1610: /**
1611: * Utility method for traversing a text node that we know
1612: * a-priori to be on a left or right boundary of the range.
1613: * This method does not properly handle text nodes that contain
1614: * both the start and end points of the range.
1615: *
1616: * @param n The node to be traversed.
1617: *
1618: * @param isLeft Is true if we are traversing the node as part of navigating
1619: * the "left boundary" of the range. If this value is false,
1620: * it implies we are navigating the "right boundary" of the
1621: * range.
1622: *
1623: * @param how Specifies what type of traversal is being
1624: * requested (extract, clone, or delete).
1625: * Legal values for this argument are:
1626: *
1627: * <ol>
1628: * <li><code>EXTRACT_CONTENTS</code> - will simply
1629: * return the original node.
1630: *
1631: * <li><code>CLONE_CONTENTS</code> - will leave the
1632: * context tree of the range undisturbed, but will
1633: * return a cloned node.
1634: *
1635: * <li><code>DELETE_CONTENTS</code> - will delete the
1636: * node from it's parent, but will return null.
1637: * </ol>
1638: *
1639: * @return Returns a node that is the result of visiting the node.
1640: * If the traversal operation is
1641: * <code>DELETE_CONTENTS</code> the return value is null.
1642: */
1643: private Node traverseTextNode(Node n, boolean isLeft, int how) {
1644: String txtValue = n.getNodeValue();
1645: String newNodeValue;
1646: String oldNodeValue;
1647:
1648: if (isLeft) {
1649: int offset = getStartOffset();
1650: newNodeValue = txtValue.substring(offset);
1651: oldNodeValue = txtValue.substring(0, offset);
1652: } else {
1653: int offset = getEndOffset();
1654: newNodeValue = txtValue.substring(0, offset);
1655: oldNodeValue = txtValue.substring(offset);
1656: }
1657:
1658: if (how != CLONE_CONTENTS)
1659: n.setNodeValue(oldNodeValue);
1660: if (how == DELETE_CONTENTS)
1661: return null;
1662: Node newNode = n.cloneNode(false);
1663: newNode.setNodeValue(newNodeValue);
1664: return newNode;
1665: }
1666:
1667: void checkIndex(Node refNode, int offset) throws DOMException {
1668: if (offset < 0) {
1669: throw new DOMException(DOMException.INDEX_SIZE_ERR,
1670: "DOM004 Index out of bounds");
1671: }
1672:
1673: int type = refNode.getNodeType();
1674:
1675: // If the node contains text, ensure that the
1676: // offset of the range is <= to the length of the text
1677: if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE
1678: || type == Node.COMMENT_NODE
1679: || type == Node.PROCESSING_INSTRUCTION_NODE) {
1680: if (offset > refNode.getNodeValue().length()) {
1681: throw new DOMException(DOMException.INDEX_SIZE_ERR,
1682: "DOM004 Index out of bounds");
1683: }
1684: } else {
1685: // Since the node is not text, ensure that the offset
1686: // is valid with respect to the number of child nodes
1687: if (offset > refNode.getChildNodes().getLength()) {
1688: throw new DOMException(DOMException.INDEX_SIZE_ERR,
1689: "DOM004 Index out of bounds");
1690: }
1691: }
1692: }
1693:
1694: /**
1695: * Given a node, calculate what the Range's root container
1696: * for that node would be.
1697: */
1698: private Node getRootContainer(Node node) {
1699: if (node == null)
1700: return null;
1701:
1702: while (node.getParentNode() != null)
1703: node = node.getParentNode();
1704: return node;
1705: }
1706:
1707: /**
1708: * Returns true IFF the given node can serve as a container
1709: * for a range's boundary points.
1710: */
1711: private boolean isLegalContainer(Node node) {
1712: if (node == null)
1713: return false;
1714:
1715: while (node != null) {
1716: switch (node.getNodeType()) {
1717: case Node.ENTITY_NODE:
1718: case Node.NOTATION_NODE:
1719: case Node.DOCUMENT_TYPE_NODE:
1720: return false;
1721: }
1722: node = node.getParentNode();
1723: }
1724:
1725: return true;
1726: }
1727:
1728: /**
1729: * Finds the root container for the given node and determines
1730: * if that root container is legal with respect to the
1731: * DOM 2 specification. At present, that means the root
1732: * container must be either an attribute, a document,
1733: * or a document fragment.
1734: */
1735: private boolean hasLegalRootContainer(Node node) {
1736: if (node == null)
1737: return false;
1738:
1739: Node rootContainer = getRootContainer(node);
1740: switch (rootContainer.getNodeType()) {
1741: case Node.ATTRIBUTE_NODE:
1742: case Node.DOCUMENT_NODE:
1743: case Node.DOCUMENT_FRAGMENT_NODE:
1744: return true;
1745: }
1746: return false;
1747: }
1748:
1749: /**
1750: * Returns true IFF the given node can be contained by
1751: * a range.
1752: */
1753: private boolean isLegalContainedNode(Node node) {
1754: if (node == null)
1755: return false;
1756: switch (node.getNodeType()) {
1757: case Node.DOCUMENT_NODE:
1758: case Node.DOCUMENT_FRAGMENT_NODE:
1759: case Node.ATTRIBUTE_NODE:
1760: case Node.ENTITY_NODE:
1761: case Node.NOTATION_NODE:
1762: return false;
1763: }
1764: return true;
1765: }
1766:
1767: Node nextNode(Node node, boolean visitChildren) {
1768:
1769: if (node == null)
1770: return null;
1771:
1772: Node result;
1773: if (visitChildren) {
1774: result = node.getFirstChild();
1775: if (result != null) {
1776: return result;
1777: }
1778: }
1779:
1780: // if hasSibling, return sibling
1781: result = node.getNextSibling();
1782: if (result != null) {
1783: return result;
1784: }
1785:
1786: // return parent's 1st sibling.
1787: Node parent = node.getParentNode();
1788: while (parent != null && parent != fDocument) {
1789: result = parent.getNextSibling();
1790: if (result != null) {
1791: return result;
1792: } else {
1793: parent = parent.getParentNode();
1794: }
1795:
1796: } // while (parent != null && parent != fRoot) {
1797:
1798: // end of list, return null
1799: return null;
1800: }
1801:
1802: /** is a an ancestor of b ? */
1803: boolean isAncestorOf(Node a, Node b) {
1804: for (Node node = b; node != null; node = node.getParentNode()) {
1805: if (node == a)
1806: return true;
1807: }
1808: return false;
1809: }
1810:
1811: /** what is the index of the child in the parent */
1812: int indexOf(Node child, Node parent) {
1813: if (child.getParentNode() != parent)
1814: return -1;
1815: int i = 0;
1816: for (Node node = parent.getFirstChild(); node != child; node = node
1817: .getNextSibling()) {
1818: i++;
1819: }
1820: return i;
1821: }
1822:
1823: /**
1824: * Utility method to retrieve a child node by index. This method
1825: * assumes the caller is trying to find out which node is
1826: * selected by the given index. Note that if the index is
1827: * greater than the number of children, this implies that the
1828: * first node selected is the parent node itself.
1829: *
1830: * @param container A container node
1831: *
1832: * @param offset An offset within the container for which a selected node should
1833: * be computed. If the offset is less than zero, or if the offset
1834: * is greater than the number of children, the container is returned.
1835: *
1836: * @return Returns either a child node of the container or the
1837: * container itself.
1838: */
1839: private Node getSelectedNode(Node container, int offset) {
1840: if (container.getNodeType() == Node.TEXT_NODE)
1841: return container;
1842:
1843: // This case is an important convenience for
1844: // traverseRightBoundary()
1845: if (offset < 0)
1846: return container;
1847:
1848: Node child = container.getFirstChild();
1849: while (child != null && offset > 0) {
1850: --offset;
1851: child = child.getNextSibling();
1852: }
1853: if (child != null)
1854: return child;
1855: return container;
1856: }
1857:
1858: }
|