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