1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.dom;
  22 
  23 import java.util.ArrayList;
  24 import java.util.List;
  25 import org.w3c.dom.CharacterData;
  26 import org.w3c.dom.DOMException;
  27 import org.w3c.dom.DocumentFragment;
  28 import org.w3c.dom.Node;
  29 import org.w3c.dom.ranges.Range;
  30 import org.w3c.dom.ranges.RangeException;
  31 
  32 
  33 /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
  34  *  <p> Please see the API documentation for the interface classes
  35  *  and use the interfaces in your client programs.
  36  *
  37  * @xerces.internal
  38  *
  39  * @LastModified: Oct 2017
  40  */
  41 public class RangeImpl  implements Range {
  42 
  43     //
  44     // Constants
  45     //
  46 
  47 
  48     //
  49     // Data
  50     //
  51 
  52     DocumentImpl fDocument;
  53     Node fStartContainer;
  54     Node fEndContainer;
  55     int fStartOffset;
  56     int fEndOffset;
  57     boolean fIsCollapsed;
  58     boolean fDetach = false;
  59     Node fInsertNode = null;
  60     Node fDeleteNode = null;
  61     Node fSplitNode = null;
  62     // Was the Node inserted from the Range or the Document
  63     boolean fInsertedFromRange = false;
  64 
  65     /** The constructor. Clients must use DocumentRange.createRange(),
  66      *  because it registers the Range with the document, so it can
  67      *  be fixed-up.
  68      */
  69     public RangeImpl(DocumentImpl document) {
  70         fDocument = document;
  71         fStartContainer = document;
  72         fEndContainer = document;
  73         fStartOffset = 0;
  74         fEndOffset = 0;
  75         fDetach = false;
  76     }
  77 
  78     public Node getStartContainer() {
  79         if ( fDetach ) {
  80             throw new DOMException(
  81                 DOMException.INVALID_STATE_ERR,
  82                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  83         }
  84         return fStartContainer;
  85     }
  86 
  87     public int getStartOffset() {
  88         if ( fDetach ) {
  89             throw new DOMException(
  90                 DOMException.INVALID_STATE_ERR,
  91                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
  92         }
  93         return fStartOffset;
  94     }
  95 
  96     public Node getEndContainer() {
  97         if ( fDetach ) {
  98             throw new DOMException(
  99                 DOMException.INVALID_STATE_ERR,
 100                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 101         }
 102         return fEndContainer;
 103     }
 104 
 105     public int getEndOffset() {
 106         if ( fDetach ) {
 107             throw new DOMException(
 108                 DOMException.INVALID_STATE_ERR,
 109                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 110         }
 111         return fEndOffset;
 112     }
 113 
 114     public boolean getCollapsed() {
 115         if ( fDetach ) {
 116             throw new DOMException(
 117                 DOMException.INVALID_STATE_ERR,
 118                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 119         }
 120         return (fStartContainer == fEndContainer
 121              && fStartOffset == fEndOffset);
 122     }
 123 
 124     public Node getCommonAncestorContainer() {
 125         if ( fDetach ) {
 126             throw new DOMException(
 127                 DOMException.INVALID_STATE_ERR,
 128                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 129         }
 130         List<Node> startV = new ArrayList<>();
 131         Node node;
 132         for (node=fStartContainer; node != null;
 133              node=node.getParentNode())
 134         {
 135             startV.add(node);
 136         }
 137         List<Node> endV = new ArrayList<>();
 138         for (node=fEndContainer; node != null;
 139              node=node.getParentNode())
 140         {
 141             endV.add(node);
 142         }
 143         int s = startV.size()-1;
 144         int e = endV.size()-1;
 145         Node result = null;
 146         while (s>=0 && e>=0) {
 147             if (startV.get(s) == endV.get(e)) {
 148                 result = startV.get(s);
 149             } else {
 150                 break;
 151             }
 152             --s;
 153             --e;
 154         }
 155         return result;
 156     }
 157 
 158 
 159     public void setStart(Node refNode, int offset)
 160                          throws RangeException, DOMException
 161     {
 162         if (fDocument.errorChecking) {
 163             if ( fDetach) {
 164                 throw new DOMException(
 165                         DOMException.INVALID_STATE_ERR,
 166                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 167             }
 168             if ( !isLegalContainer(refNode)) {
 169                 throw new RangeExceptionImpl(
 170                         RangeException.INVALID_NODE_TYPE_ERR,
 171                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 172             }
 173             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 174                 throw new DOMException(
 175                         DOMException.WRONG_DOCUMENT_ERR,
 176                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 177             }
 178         }
 179 
 180         checkIndex(refNode, offset);
 181 
 182         fStartContainer = refNode;
 183         fStartOffset = offset;
 184 
 185         // If one boundary-point of a Range is set to have a root container
 186         // other
 187         // than the current one for the Range, the Range should be collapsed to
 188         // the new position.
 189         // The start position of a Range should never be after the end position.
 190         if (getCommonAncestorContainer() == null
 191                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 192             collapse(true);
 193         }
 194     }
 195 
 196     public void setEnd(Node refNode, int offset)
 197                        throws RangeException, DOMException
 198     {
 199         if (fDocument.errorChecking) {
 200             if (fDetach) {
 201                 throw new DOMException(
 202                         DOMException.INVALID_STATE_ERR,
 203                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 204             }
 205             if ( !isLegalContainer(refNode)) {
 206                 throw new RangeExceptionImpl(
 207                         RangeException.INVALID_NODE_TYPE_ERR,
 208                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 209             }
 210             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 211                 throw new DOMException(
 212                         DOMException.WRONG_DOCUMENT_ERR,
 213                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 214             }
 215         }
 216 
 217         checkIndex(refNode, offset);
 218 
 219         fEndContainer = refNode;
 220         fEndOffset = offset;
 221 
 222         // If one boundary-point of a Range is set to have a root container
 223         // other
 224         // than the current one for the Range, the Range should be collapsed to
 225         // the new position.
 226         // The start position of a Range should never be after the end position.
 227         if (getCommonAncestorContainer() == null
 228                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 229             collapse(false);
 230         }
 231     }
 232 
 233     public void setStartBefore(Node refNode)
 234         throws RangeException
 235     {
 236         if (fDocument.errorChecking) {
 237             if (fDetach) {
 238                 throw new DOMException(
 239                         DOMException.INVALID_STATE_ERR,
 240                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 241             }
 242             if ( !hasLegalRootContainer(refNode) ||
 243                     !isLegalContainedNode(refNode) )
 244             {
 245                 throw new RangeExceptionImpl(
 246                         RangeException.INVALID_NODE_TYPE_ERR,
 247                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 248             }
 249             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 250                 throw new DOMException(
 251                         DOMException.WRONG_DOCUMENT_ERR,
 252                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 253             }
 254         }
 255 
 256         fStartContainer = refNode.getParentNode();
 257         int i = 0;
 258         for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
 259             i++;
 260         }
 261         fStartOffset = i-1;
 262 
 263         // If one boundary-point of a Range is set to have a root container
 264         // other
 265         // than the current one for the Range, the Range should be collapsed to
 266         // the new position.
 267         // The start position of a Range should never be after the end position.
 268         if (getCommonAncestorContainer() == null
 269                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 270             collapse(true);
 271         }
 272     }
 273 
 274     public void setStartAfter(Node refNode)
 275         throws RangeException
 276     {
 277         if (fDocument.errorChecking) {
 278             if (fDetach) {
 279                 throw new DOMException(
 280                         DOMException.INVALID_STATE_ERR,
 281                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 282             }
 283             if ( !hasLegalRootContainer(refNode) ||
 284                     !isLegalContainedNode(refNode)) {
 285                 throw new RangeExceptionImpl(
 286                         RangeException.INVALID_NODE_TYPE_ERR,
 287                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 288             }
 289             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 290                 throw new DOMException(
 291                         DOMException.WRONG_DOCUMENT_ERR,
 292                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 293             }
 294         }
 295         fStartContainer = refNode.getParentNode();
 296         int i = 0;
 297         for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
 298             i++;
 299         }
 300         fStartOffset = i;
 301 
 302         // If one boundary-point of a Range is set to have a root container
 303         // other
 304         // than the current one for the Range, the Range should be collapsed to
 305         // the new position.
 306         // The start position of a Range should never be after the end position.
 307         if (getCommonAncestorContainer() == null
 308                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 309             collapse(true);
 310         }
 311     }
 312 
 313     public void setEndBefore(Node refNode)
 314         throws RangeException
 315     {
 316         if (fDocument.errorChecking) {
 317             if (fDetach) {
 318                 throw new DOMException(
 319                         DOMException.INVALID_STATE_ERR,
 320                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 321             }
 322             if ( !hasLegalRootContainer(refNode) ||
 323                     !isLegalContainedNode(refNode)) {
 324                 throw new RangeExceptionImpl(
 325                         RangeException.INVALID_NODE_TYPE_ERR,
 326                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 327             }
 328             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 329                 throw new DOMException(
 330                         DOMException.WRONG_DOCUMENT_ERR,
 331                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 332             }
 333         }
 334         fEndContainer = refNode.getParentNode();
 335         int i = 0;
 336         for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
 337             i++;
 338         }
 339         fEndOffset = i-1;
 340 
 341         // If one boundary-point of a Range is set to have a root container
 342         // other
 343         // than the current one for the Range, the Range should be collapsed to
 344         // the new position.
 345         // The start position of a Range should never be after the end position.
 346         if (getCommonAncestorContainer() == null
 347                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 348             collapse(false);
 349         }
 350     }
 351 
 352     public void setEndAfter(Node refNode)
 353         throws RangeException
 354     {
 355         if (fDocument.errorChecking) {
 356             if( fDetach) {
 357                 throw new DOMException(
 358                         DOMException.INVALID_STATE_ERR,
 359                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 360             }
 361             if ( !hasLegalRootContainer(refNode) ||
 362                     !isLegalContainedNode(refNode)) {
 363                 throw new RangeExceptionImpl(
 364                         RangeException.INVALID_NODE_TYPE_ERR,
 365                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 366             }
 367             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 368                 throw new DOMException(
 369                         DOMException.WRONG_DOCUMENT_ERR,
 370                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 371             }
 372         }
 373         fEndContainer = refNode.getParentNode();
 374         int i = 0;
 375         for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
 376             i++;
 377         }
 378         fEndOffset = i;
 379 
 380         // If one boundary-point of a Range is set to have a root container
 381         // other
 382         // than the current one for the Range, the Range should be collapsed to
 383         // the new position.
 384         // The start position of a Range should never be after the end position.
 385         if (getCommonAncestorContainer() == null
 386                 || (fStartContainer == fEndContainer && fEndOffset < fStartOffset)) {
 387             collapse(false);
 388         }
 389     }
 390 
 391     public void collapse(boolean toStart) {
 392 
 393         if( fDetach) {
 394                 throw new DOMException(
 395                 DOMException.INVALID_STATE_ERR,
 396                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 397         }
 398 
 399         if (toStart) {
 400             fEndContainer = fStartContainer;
 401             fEndOffset = fStartOffset;
 402         } else {
 403             fStartContainer = fEndContainer;
 404             fStartOffset = fEndOffset;
 405         }
 406     }
 407 
 408     public void selectNode(Node refNode)
 409         throws RangeException
 410     {
 411         if (fDocument.errorChecking) {
 412             if (fDetach) {
 413                 throw new DOMException(
 414                         DOMException.INVALID_STATE_ERR,
 415                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 416             }
 417             if ( !isLegalContainer( refNode.getParentNode() ) ||
 418                     !isLegalContainedNode( refNode ) ) {
 419                 throw new RangeExceptionImpl(
 420                         RangeException.INVALID_NODE_TYPE_ERR,
 421                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 422             }
 423             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 424                 throw new DOMException(
 425                         DOMException.WRONG_DOCUMENT_ERR,
 426                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 427             }
 428         }
 429         Node parent = refNode.getParentNode();
 430         if (parent != null ) // REVIST: what to do if it IS null?
 431         {
 432             fStartContainer = parent;
 433             fEndContainer = parent;
 434             int i = 0;
 435             for (Node n = refNode; n!=null; n = n.getPreviousSibling()) {
 436                 i++;
 437             }
 438             fStartOffset = i-1;
 439             fEndOffset = fStartOffset+1;
 440         }
 441     }
 442 
 443     public void selectNodeContents(Node refNode)
 444         throws RangeException
 445     {
 446         if (fDocument.errorChecking) {
 447             if( fDetach) {
 448                 throw new DOMException(
 449                         DOMException.INVALID_STATE_ERR,
 450                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 451             }
 452             if ( !isLegalContainer(refNode)) {
 453                 throw new RangeExceptionImpl(
 454                         RangeException.INVALID_NODE_TYPE_ERR,
 455                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 456             }
 457             if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
 458                 throw new DOMException(
 459                         DOMException.WRONG_DOCUMENT_ERR,
 460                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 461             }
 462         }
 463         fStartContainer = refNode;
 464         fEndContainer = refNode;
 465         Node first = refNode.getFirstChild();
 466         fStartOffset = 0;
 467         if (first == null) {
 468             fEndOffset = 0;
 469         } else {
 470             int i = 0;
 471             for (Node n = first; n!=null; n = n.getNextSibling()) {
 472                 i++;
 473             }
 474             fEndOffset = i;
 475         }
 476 
 477     }
 478 
 479     public short compareBoundaryPoints(short how, Range sourceRange)
 480         throws DOMException
 481     {
 482         if (fDocument.errorChecking) {
 483             if( fDetach) {
 484                 throw new DOMException(
 485                         DOMException.INVALID_STATE_ERR,
 486                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 487             }
 488             // WRONG_DOCUMENT_ERR: Raised if the two Ranges are not in the same Document or DocumentFragment.
 489             if ((fDocument != sourceRange.getStartContainer().getOwnerDocument()
 490                     && fDocument != sourceRange.getStartContainer()
 491                     && sourceRange.getStartContainer() != null)
 492                     || (fDocument != sourceRange.getEndContainer().getOwnerDocument()
 493                             && fDocument != sourceRange.getEndContainer()
 494                             && sourceRange.getStartContainer() != null)) {
 495                 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
 496                         DOMMessageFormatter.formatMessage( DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 497             }
 498         }
 499 
 500         Node endPointA;
 501         Node endPointB;
 502         int offsetA;
 503         int offsetB;
 504 
 505         if (how == START_TO_START) {
 506             endPointA = sourceRange.getStartContainer();
 507             endPointB = fStartContainer;
 508             offsetA = sourceRange.getStartOffset();
 509             offsetB = fStartOffset;
 510         } else
 511         if (how == START_TO_END) {
 512             endPointA = sourceRange.getStartContainer();
 513             endPointB = fEndContainer;
 514             offsetA = sourceRange.getStartOffset();
 515             offsetB = fEndOffset;
 516         } else
 517         if (how == END_TO_START) {
 518             endPointA = sourceRange.getEndContainer();
 519             endPointB = fStartContainer;
 520             offsetA = sourceRange.getEndOffset();
 521             offsetB = fStartOffset;
 522         } else {
 523             endPointA = sourceRange.getEndContainer();
 524             endPointB = fEndContainer;
 525             offsetA = sourceRange.getEndOffset();
 526             offsetB = fEndOffset;
 527         }
 528 
 529         // The DOM Spec outlines four cases that need to be tested
 530         // to compare two range boundary points:
 531         //   case 1: same container
 532         //   case 2: Child C of container A is ancestor of B
 533         //   case 3: Child C of container B is ancestor of A
 534         //   case 4: preorder traversal of context tree.
 535 
 536         // case 1: same container
 537         if (endPointA == endPointB) {
 538             if (offsetA < offsetB) return 1;
 539             if (offsetA == offsetB) return 0;
 540             return -1;
 541         }
 542         // case 2: Child C of container A is ancestor of B
 543         // This can be quickly tested by walking the parent chain of B
 544         for ( Node c = endPointB, p = c.getParentNode();
 545              p != null;
 546              c = p, p = p.getParentNode())
 547         {
 548             if (p == endPointA) {
 549                 int index = indexOf(c, endPointA);
 550                 if (offsetA <= index) return 1;
 551                 return -1;
 552             }
 553         }
 554 
 555         // case 3: Child C of container B is ancestor of A
 556         // This can be quickly tested by walking the parent chain of A
 557         for ( Node c = endPointA, p = c.getParentNode();
 558              p != null;
 559              c = p, p = p.getParentNode())
 560         {
 561             if (p == endPointB) {
 562                 int index = indexOf(c, endPointB);
 563                 if (index < offsetB) return 1;
 564                 return -1;
 565             }
 566         }
 567 
 568         // case 4: preorder traversal of context tree.
 569         // Instead of literally walking the context tree in pre-order,
 570         // we use relative node depth walking which is usually faster
 571 
 572         int depthDiff = 0;
 573         for ( Node n = endPointA; n != null; n = n.getParentNode() )
 574             depthDiff++;
 575         for ( Node n = endPointB; n != null; n = n.getParentNode() )
 576             depthDiff--;
 577         while (depthDiff > 0) {
 578             endPointA = endPointA.getParentNode();
 579             depthDiff--;
 580         }
 581         while (depthDiff < 0) {
 582             endPointB = endPointB.getParentNode();
 583             depthDiff++;
 584         }
 585         for (Node pA = endPointA.getParentNode(),
 586              pB = endPointB.getParentNode();
 587              pA != pB;
 588              pA = pA.getParentNode(), pB = pB.getParentNode() )
 589         {
 590             endPointA = pA;
 591             endPointB = pB;
 592         }
 593         for ( Node n = endPointA.getNextSibling();
 594              n != null;
 595              n = n.getNextSibling() )
 596         {
 597             if (n == endPointB) {
 598                 return 1;
 599             }
 600         }
 601         return -1;
 602     }
 603 
 604     public void deleteContents()
 605         throws DOMException
 606     {
 607         traverseContents(DELETE_CONTENTS);
 608     }
 609 
 610     public DocumentFragment extractContents()
 611         throws DOMException
 612     {
 613         return traverseContents(EXTRACT_CONTENTS);
 614     }
 615 
 616     public DocumentFragment cloneContents()
 617         throws DOMException
 618     {
 619         return traverseContents(CLONE_CONTENTS);
 620     }
 621 
 622     public void insertNode(Node newNode)
 623         throws DOMException, RangeException
 624     {
 625         if ( newNode == null ) return; //throw exception?
 626 
 627         int type = newNode.getNodeType();
 628 
 629         if (fDocument.errorChecking) {
 630             if (fDetach) {
 631                 throw new DOMException(
 632                         DOMException.INVALID_STATE_ERR,
 633                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 634             }
 635             if ( fDocument != newNode.getOwnerDocument() ) {
 636                 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
 637                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
 638             }
 639 
 640             if (type == Node.ATTRIBUTE_NODE
 641                     || type == Node.ENTITY_NODE
 642                     || type == Node.NOTATION_NODE
 643                     || type == Node.DOCUMENT_NODE)
 644             {
 645                 throw new RangeExceptionImpl(
 646                         RangeException.INVALID_NODE_TYPE_ERR,
 647                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 648             }
 649         }
 650         Node cloneCurrent;
 651         Node current;
 652         int currentChildren = 0;
 653         fInsertedFromRange = true;
 654 
 655         //boolean MULTIPLE_MODE = false;
 656         if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
 657 
 658             Node parent = fStartContainer.getParentNode();
 659             currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
 660             // split text node: results is 3 nodes..
 661             cloneCurrent = fStartContainer.cloneNode(false);
 662             ((TextImpl)cloneCurrent).setNodeValueInternal(
 663                     (cloneCurrent.getNodeValue()).substring(fStartOffset));
 664             ((TextImpl)fStartContainer).setNodeValueInternal(
 665                     (fStartContainer.getNodeValue()).substring(0,fStartOffset));
 666             Node next = fStartContainer.getNextSibling();
 667             if (next != null) {
 668                     if (parent !=  null) {
 669                         parent.insertBefore(newNode, next);
 670                         parent.insertBefore(cloneCurrent, next);
 671                     }
 672             } else {
 673                     if (parent != null) {
 674                         parent.appendChild(newNode);
 675                         parent.appendChild(cloneCurrent);
 676                     }
 677             }
 678              //update ranges after the insertion
 679              if ( fEndContainer == fStartContainer) {
 680                   fEndContainer = cloneCurrent; //endContainer is the new Node created
 681                   fEndOffset -= fStartOffset;
 682              }
 683              else if ( fEndContainer == parent ) {    //endContainer was not a text Node.
 684                   //endOffset + = number_of_children_added
 685                    fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
 686              }
 687 
 688              // signal other Ranges to update their start/end containers/offsets
 689              signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
 690 
 691 
 692         } else { // ! TEXT_NODE
 693             if ( fEndContainer == fStartContainer )      //need to remember number of kids
 694                 currentChildren= fEndContainer.getChildNodes().getLength();
 695 
 696             current = fStartContainer.getFirstChild();
 697             int i = 0;
 698             for(i = 0; i < fStartOffset && current != null; i++) {
 699                 current=current.getNextSibling();
 700             }
 701             if (current != null) {
 702                 fStartContainer.insertBefore(newNode, current);
 703             } else {
 704                 fStartContainer.appendChild(newNode);
 705             }
 706             //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
 707             // insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
 708             if ( fEndContainer == fStartContainer && fEndOffset != 0 ) {     //update fEndOffset if not 0
 709                 fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren);
 710             }
 711         }
 712         fInsertedFromRange = false;
 713     }
 714 
 715     public void surroundContents(Node newParent)
 716         throws DOMException, RangeException
 717     {
 718         if (newParent==null) return;
 719         int type = newParent.getNodeType();
 720 
 721         if (fDocument.errorChecking) {
 722             if (fDetach) {
 723                 throw new DOMException(
 724                         DOMException.INVALID_STATE_ERR,
 725                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 726             }
 727             if (type == Node.ATTRIBUTE_NODE
 728                     || type == Node.ENTITY_NODE
 729                     || type == Node.NOTATION_NODE
 730                     || type == Node.DOCUMENT_TYPE_NODE
 731                     || type == Node.DOCUMENT_NODE
 732                     || type == Node.DOCUMENT_FRAGMENT_NODE)
 733             {
 734                 throw new RangeExceptionImpl(
 735                         RangeException.INVALID_NODE_TYPE_ERR,
 736                         DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
 737             }
 738         }
 739 
 740         Node realStart = fStartContainer;
 741         Node realEnd = fEndContainer;
 742         if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
 743             realStart = fStartContainer.getParentNode();
 744         }
 745         if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
 746             realEnd = fEndContainer.getParentNode();
 747         }
 748 
 749         if (realStart != realEnd) {
 750                 throw new RangeExceptionImpl(
 751                 RangeException.BAD_BOUNDARYPOINTS_ERR,
 752                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "BAD_BOUNDARYPOINTS_ERR", null));
 753         }
 754 
 755         DocumentFragment frag = extractContents();
 756         insertNode(newParent);
 757         newParent.appendChild(frag);
 758         selectNode(newParent);
 759     }
 760 
 761     public Range cloneRange(){
 762         if( fDetach) {
 763                 throw new DOMException(
 764                 DOMException.INVALID_STATE_ERR,
 765                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 766         }
 767 
 768         Range range = fDocument.createRange();
 769         range.setStart(fStartContainer, fStartOffset);
 770         range.setEnd(fEndContainer, fEndOffset);
 771         return range;
 772     }
 773 
 774     public String toString(){
 775         if( fDetach) {
 776                 throw new DOMException(
 777                 DOMException.INVALID_STATE_ERR,
 778                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 779         }
 780 
 781         Node node = fStartContainer;
 782         Node stopNode = fEndContainer;
 783         StringBuffer sb = new StringBuffer();
 784         if (fStartContainer.getNodeType() == Node.TEXT_NODE
 785          || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE
 786         ) {
 787             if (fStartContainer == fEndContainer) {
 788                 sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset));
 789                 return sb.toString();
 790             }
 791             sb.append(fStartContainer.getNodeValue().substring(fStartOffset));
 792             node=nextNode (node,true); //fEndContainer!=fStartContainer
 793 
 794         }
 795         else {  //fStartContainer is not a TextNode
 796             node=node.getFirstChild();
 797             if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset
 798                int counter=0;
 799                while (counter<fStartOffset && node!=null) {
 800                    node=node.getNextSibling();
 801                    counter++;
 802                }
 803             }
 804             if (node == null) {
 805                    node = nextNode(fStartContainer,false);
 806             }
 807         }
 808         if ( fEndContainer.getNodeType()!= Node.TEXT_NODE &&
 809              fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){
 810              int i=fEndOffset;
 811              stopNode = fEndContainer.getFirstChild();
 812              while( i>0 && stopNode!=null ){
 813                  --i;
 814                  stopNode = stopNode.getNextSibling();
 815              }
 816              if ( stopNode == null )
 817                  stopNode = nextNode( fEndContainer, false );
 818          }
 819          while (node != stopNode) {  //look into all kids of the Range
 820              if (node == null) break;
 821              if (node.getNodeType() == Node.TEXT_NODE
 822              ||  node.getNodeType() == Node.CDATA_SECTION_NODE) {
 823                  sb.append(node.getNodeValue());
 824              }
 825 
 826              node = nextNode(node, true);
 827          }
 828 
 829         if (fEndContainer.getNodeType() == Node.TEXT_NODE
 830          || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
 831             sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset));
 832         }
 833         return sb.toString();
 834     }
 835 
 836     public void detach() {
 837         if( fDetach) {
 838             throw new DOMException(
 839             DOMException.INVALID_STATE_ERR,
 840                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
 841         }
 842         fDetach = true;
 843         fDocument.removeRange(this);
 844     }
 845 
 846     //
 847     // Mutation functions
 848     //
 849 
 850     /** Signal other Ranges to update their start/end
 851      *  containers/offsets. The data has already been split
 852      *  into the two Nodes.
 853      */
 854     void signalSplitData(Node node, Node newNode, int offset) {
 855         fSplitNode = node;
 856         // notify document
 857         fDocument.splitData(node, newNode, offset);
 858         fSplitNode = null;
 859     }
 860 
 861     /** Fix up this Range if another Range has split a Text Node
 862      *  into 2 Nodes.
 863      */
 864     void receiveSplitData(Node node, Node newNode, int offset) {
 865         if (node == null || newNode == null) return;
 866         if (fSplitNode == node) return;
 867 
 868         if (node == fStartContainer
 869         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
 870             if (fStartOffset > offset) {
 871                 fStartOffset = fStartOffset - offset;
 872                 fStartContainer = newNode;
 873             }
 874         }
 875         if (node == fEndContainer
 876         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
 877             if (fEndOffset > offset) {
 878                 fEndOffset = fEndOffset-offset;
 879                 fEndContainer = newNode;
 880             }
 881         }
 882 
 883     }
 884 
 885     /** This function inserts text into a Node and invokes
 886      *  a method to fix-up all other Ranges.
 887      */
 888     void deleteData(CharacterData node, int offset, int count) {
 889         fDeleteNode = node;
 890         node.deleteData( offset,  count);
 891         fDeleteNode = null;
 892     }
 893 
 894 
 895     /** This function is called from DOM.
 896      *  The  text has already beeen inserted.
 897      *  Fix-up any offsets.
 898      */
 899     void receiveDeletedText(Node node, int offset, int count) {
 900         if (node == null) return;
 901         if (fDeleteNode == node) return;
 902         if (node == fStartContainer
 903         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
 904             if (fStartOffset > offset+count) {
 905                 fStartOffset = offset+(fStartOffset-(offset+count));
 906             } else
 907             if (fStartOffset > offset) {
 908                 fStartOffset = offset;
 909             }
 910         }
 911         if (node == fEndContainer
 912         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
 913             if (fEndOffset > offset+count) {
 914                 fEndOffset = offset+(fEndOffset-(offset+count));
 915             } else
 916             if (fEndOffset > offset) {
 917                 fEndOffset = offset;
 918             }
 919         }
 920 
 921     }
 922 
 923     /** This function inserts text into a Node and invokes
 924      *  a method to fix-up all other Ranges.
 925      */
 926     void insertData(CharacterData node, int index, String insert) {
 927         fInsertNode = node;
 928         node.insertData( index,  insert);
 929         fInsertNode = null;
 930     }
 931 
 932 
 933     /** This function is called from DOM.
 934      *  The  text has already beeen inserted.
 935      *  Fix-up any offsets.
 936      */
 937     void receiveInsertedText(Node node, int index, int len) {
 938         if (node == null) return;
 939         if (fInsertNode == node) return;
 940         if (node == fStartContainer
 941         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
 942             if (index < fStartOffset) {
 943                 fStartOffset = fStartOffset+len;
 944             }
 945         }
 946         if (node == fEndContainer
 947         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
 948             if (index < fEndOffset) {
 949                 fEndOffset = fEndOffset+len;
 950             }
 951         }
 952 
 953     }
 954 
 955     /** This function is called from DOM.
 956      *  The  text has already beeen replaced.
 957      *  Fix-up any offsets.
 958      */
 959     void receiveReplacedText(Node node) {
 960         if (node == null) return;
 961         if (node == fStartContainer
 962         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
 963             fStartOffset = 0;
 964         }
 965         if (node == fEndContainer
 966         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
 967             fEndOffset = 0;
 968         }
 969 
 970     }
 971 
 972     /** This function is called from the DOM.
 973      *  This node has already been inserted into the DOM.
 974      *  Fix-up any offsets.
 975      */
 976     public void insertedNodeFromDOM(Node node) {
 977         if (node == null) return;
 978         if (fInsertNode == node) return;
 979         if (fInsertedFromRange) return; // Offsets are adjusted in Range.insertNode
 980 
 981         Node parent = node.getParentNode();
 982 
 983         if (parent == fStartContainer) {
 984             int index = indexOf(node, fStartContainer);
 985             if (index < fStartOffset) {
 986                 fStartOffset++;
 987             }
 988         }
 989 
 990         if (parent == fEndContainer) {
 991             int index = indexOf(node, fEndContainer);
 992             if (index < fEndOffset) {
 993                 fEndOffset++;
 994             }
 995         }
 996 
 997     }
 998 
 999     /** This function is called within Range
1000      *  instead of Node.removeChild,
1001      *  so that the range can remember that it is actively
1002      *  removing this child.
1003      */
1004 
1005     Node fRemoveChild = null;
1006     Node removeChild(Node parent, Node child) {
1007         fRemoveChild = child;
1008         Node n = parent.removeChild(child);
1009         fRemoveChild = null;
1010         return n;
1011     }
1012 
1013     /** This function must be called by the DOM _BEFORE_
1014      *  a node is deleted, because at that time it is
1015      *  connected in the DOM tree, which we depend on.
1016      */
1017     void removeNode(Node node) {
1018         if (node == null) return;
1019         if (fRemoveChild == node) return;
1020 
1021         Node parent = node.getParentNode();
1022 
1023         if (parent == fStartContainer) {
1024             int index = indexOf(node, fStartContainer);
1025             if (index < fStartOffset) {
1026                 fStartOffset--;
1027             }
1028         }
1029 
1030         if (parent == fEndContainer) {
1031             int index = indexOf(node, fEndContainer);
1032             if (index < fEndOffset) {
1033                 fEndOffset--;
1034             }
1035         }
1036         //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted
1037         if (parent != fStartContainer
1038         ||  parent != fEndContainer) {
1039             if (isAncestorOf(node, fStartContainer)) {
1040                 fStartContainer = parent;
1041                 fStartOffset = indexOf( node, parent);
1042             }
1043             if (isAncestorOf(node, fEndContainer)) {
1044                 fEndContainer = parent;
1045                 fEndOffset = indexOf( node, parent);
1046             }
1047         }
1048 
1049     }
1050 
1051     //
1052     // Utility functions.
1053     //
1054 
1055     // parameters for traverseContents(int)
1056     //REVIST: use boolean, since there are only 2 now...
1057     static final int EXTRACT_CONTENTS = 1;
1058     static final int CLONE_CONTENTS = 2;
1059     static final int DELETE_CONTENTS = 3;
1060 
1061     /**
1062      * This is the master routine invoked to visit the nodes
1063      * selected by this range.  For each such node, different
1064      * actions are taken depending on the value of the
1065      * <code>how</code> argument.
1066      *
1067      * @param how    Specifies what type of traversal is being
1068      *               requested (extract, clone, or delete).
1069      *               Legal values for this argument are:
1070      *
1071      *               <ol>
1072      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1073      *               a document fragment containing the range's content.
1074      *               Partially selected nodes are copied, but fully
1075      *               selected nodes are moved.
1076      *
1077      *               <li><code>CLONE_CONTENTS</code> - will leave the
1078      *               context tree of the range undisturbed, but sill
1079      *               produced cloned content in a document fragment
1080      *
1081      *               <li><code>DELETE_CONTENTS</code> - will delete from
1082      *               the context tree of the range, all fully selected
1083      *               nodes.
1084      *               </ol>
1085      *
1086      * @return Returns a document fragment containing any
1087      *         copied or extracted nodes.  If the <code>how</code>
1088      *         parameter was <code>DELETE_CONTENTS</code>, the
1089      *         return value is null.
1090      */
1091     private DocumentFragment traverseContents( int how )
1092         throws DOMException
1093     {
1094         if (fStartContainer == null || fEndContainer == null) {
1095             return null; // REVIST: Throw exception?
1096         }
1097 
1098         //Check for a detached range.
1099         if( fDetach) {
1100             throw new DOMException(
1101                 DOMException.INVALID_STATE_ERR,
1102                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
1103         }
1104 
1105         /*
1106           Traversal is accomplished by first determining the
1107           relationship between the endpoints of the range.
1108           For each of four significant relationships, we will
1109           delegate the traversal call to a method that
1110           can make appropriate assumptions.
1111          */
1112 
1113         // case 1: same container
1114         if ( fStartContainer == fEndContainer )
1115             return traverseSameContainer( how );
1116 
1117 
1118         // case 2: Child C of start container is ancestor of end container
1119         // This can be quickly tested by walking the parent chain of
1120         // end container
1121         int endContainerDepth = 0;
1122         for ( Node c = fEndContainer, p = c.getParentNode();
1123              p != null;
1124              c = p, p = p.getParentNode())
1125         {
1126             if (p == fStartContainer)
1127                 return traverseCommonStartContainer( c, how );
1128             ++endContainerDepth;
1129         }
1130 
1131         // case 3: Child C of container B is ancestor of A
1132         // This can be quickly tested by walking the parent chain of A
1133         int startContainerDepth = 0;
1134         for ( Node c = fStartContainer, p = c.getParentNode();
1135              p != null;
1136              c = p, p = p.getParentNode())
1137         {
1138             if (p == fEndContainer)
1139                 return traverseCommonEndContainer( c, how );
1140             ++startContainerDepth;
1141         }
1142 
1143         // case 4: There is a common ancestor container.  Find the
1144         // ancestor siblings that are children of that container.
1145         int depthDiff = startContainerDepth - endContainerDepth;
1146 
1147         Node startNode = fStartContainer;
1148         while (depthDiff > 0) {
1149             startNode = startNode.getParentNode();
1150             depthDiff--;
1151         }
1152 
1153         Node endNode = fEndContainer;
1154         while (depthDiff < 0) {
1155             endNode = endNode.getParentNode();
1156             depthDiff++;
1157         }
1158 
1159         // ascend the ancestor hierarchy until we have a common parent.
1160         for( Node sp = startNode.getParentNode(), ep = endNode.getParentNode();
1161              sp!=ep;
1162              sp = sp.getParentNode(), ep = ep.getParentNode() )
1163         {
1164             startNode = sp;
1165             endNode = ep;
1166         }
1167         return traverseCommonAncestors( startNode, endNode, how );
1168     }
1169 
1170     /**
1171      * Visits the nodes selected by this range when we know
1172      * a-priori that the start and end containers are the same.
1173      * This method is invoked by the generic <code>traverse</code>
1174      * method.
1175      *
1176      * @param how    Specifies what type of traversal is being
1177      *               requested (extract, clone, or delete).
1178      *               Legal values for this argument are:
1179      *
1180      *               <ol>
1181      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1182      *               a document fragment containing the range's content.
1183      *               Partially selected nodes are copied, but fully
1184      *               selected nodes are moved.
1185      *
1186      *               <li><code>CLONE_CONTENTS</code> - will leave the
1187      *               context tree of the range undisturbed, but sill
1188      *               produced cloned content in a document fragment
1189      *
1190      *               <li><code>DELETE_CONTENTS</code> - will delete from
1191      *               the context tree of the range, all fully selected
1192      *               nodes.
1193      *               </ol>
1194      *
1195      * @return Returns a document fragment containing any
1196      *         copied or extracted nodes.  If the <code>how</code>
1197      *         parameter was <code>DELETE_CONTENTS</code>, the
1198      *         return value is null.
1199      */
1200     private DocumentFragment traverseSameContainer( int how )
1201     {
1202         DocumentFragment frag = null;
1203         if ( how!=DELETE_CONTENTS)
1204             frag = fDocument.createDocumentFragment();
1205 
1206         // If selection is empty, just return the fragment
1207         if ( fStartOffset==fEndOffset )
1208             return frag;
1209 
1210         // Text node needs special case handling
1211         if ( fStartContainer.getNodeType()==Node.TEXT_NODE )
1212         {
1213             // get the substring
1214             String s = fStartContainer.getNodeValue();
1215             String sub = s.substring( fStartOffset, fEndOffset );
1216 
1217             // set the original text node to its new value
1218             if ( how != CLONE_CONTENTS )
1219             {
1220                 ((TextImpl)fStartContainer).deleteData(fStartOffset,
1221                      fEndOffset-fStartOffset) ;
1222                 // Nothing is partially selected, so collapse to start point
1223                 collapse( true );
1224             }
1225             if ( how==DELETE_CONTENTS)
1226                 return null;
1227             frag.appendChild( fDocument.createTextNode(sub) );
1228             return frag;
1229         }
1230 
1231         // Copy nodes between the start/end offsets.
1232         Node n = getSelectedNode( fStartContainer, fStartOffset );
1233         int cnt = fEndOffset - fStartOffset;
1234         while( cnt > 0 )
1235         {
1236             Node sibling = n.getNextSibling();
1237             Node xferNode = traverseFullySelected( n, how );
1238             if ( frag!=null )
1239                 frag.appendChild( xferNode );
1240             --cnt;
1241             n = sibling;
1242         }
1243 
1244         // Nothing is partially selected, so collapse to start point
1245         if ( how != CLONE_CONTENTS )
1246             collapse( true );
1247         return frag;
1248     }
1249 
1250     /**
1251      * Visits the nodes selected by this range when we know
1252      * a-priori that the start and end containers are not the
1253      * same, but the start container is an ancestor of the
1254      * end container. This method is invoked by the generic
1255      * <code>traverse</code> method.
1256      *
1257      * @param endAncestor
1258      *               The ancestor of the end container that is a direct child
1259      *               of the start container.
1260      *
1261      * @param how    Specifies what type of traversal is being
1262      *               requested (extract, clone, or delete).
1263      *               Legal values for this argument are:
1264      *
1265      *               <ol>
1266      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1267      *               a document fragment containing the range's content.
1268      *               Partially selected nodes are copied, but fully
1269      *               selected nodes are moved.
1270      *
1271      *               <li><code>CLONE_CONTENTS</code> - will leave the
1272      *               context tree of the range undisturbed, but sill
1273      *               produced cloned content in a document fragment
1274      *
1275      *               <li><code>DELETE_CONTENTS</code> - will delete from
1276      *               the context tree of the range, all fully selected
1277      *               nodes.
1278      *               </ol>
1279      *
1280      * @return Returns a document fragment containing any
1281      *         copied or extracted nodes.  If the <code>how</code>
1282      *         parameter was <code>DELETE_CONTENTS</code>, the
1283      *         return value is null.
1284      */
1285     private DocumentFragment
1286         traverseCommonStartContainer( Node endAncestor, int how )
1287     {
1288         DocumentFragment frag = null;
1289         if ( how!=DELETE_CONTENTS)
1290             frag = fDocument.createDocumentFragment();
1291         Node n = traverseRightBoundary( endAncestor, how );
1292         if ( frag!=null )
1293             frag.appendChild( n );
1294 
1295         int endIdx = indexOf( endAncestor, fStartContainer );
1296         int cnt = endIdx - fStartOffset;
1297         if ( cnt <=0 )
1298         {
1299             // Collapse to just before the endAncestor, which
1300             // is partially selected.
1301             if ( how != CLONE_CONTENTS )
1302             {
1303                 setEndBefore( endAncestor );
1304                 collapse( false );
1305             }
1306             return frag;
1307         }
1308 
1309         n = endAncestor.getPreviousSibling();
1310         while( cnt > 0 )
1311         {
1312             Node sibling = n.getPreviousSibling();
1313             Node xferNode = traverseFullySelected( n, how );
1314             if ( frag!=null )
1315                 frag.insertBefore( xferNode, frag.getFirstChild() );
1316             --cnt;
1317             n = sibling;
1318         }
1319         // Collapse to just before the endAncestor, which
1320         // is partially selected.
1321         if ( how != CLONE_CONTENTS )
1322         {
1323             setEndBefore( endAncestor );
1324             collapse( false );
1325         }
1326         return frag;
1327     }
1328 
1329     /**
1330      * Visits the nodes selected by this range when we know
1331      * a-priori that the start and end containers are not the
1332      * same, but the end container is an ancestor of the
1333      * start container. This method is invoked by the generic
1334      * <code>traverse</code> method.
1335      *
1336      * @param startAncestor
1337      *               The ancestor of the start container that is a direct
1338      *               child of the end container.
1339      *
1340      * @param how    Specifies what type of traversal is being
1341      *               requested (extract, clone, or delete).
1342      *               Legal values for this argument are:
1343      *
1344      *               <ol>
1345      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1346      *               a document fragment containing the range's content.
1347      *               Partially selected nodes are copied, but fully
1348      *               selected nodes are moved.
1349      *
1350      *               <li><code>CLONE_CONTENTS</code> - will leave the
1351      *               context tree of the range undisturbed, but sill
1352      *               produced cloned content in a document fragment
1353      *
1354      *               <li><code>DELETE_CONTENTS</code> - will delete from
1355      *               the context tree of the range, all fully selected
1356      *               nodes.
1357      *               </ol>
1358      *
1359      * @return Returns a document fragment containing any
1360      *         copied or extracted nodes.  If the <code>how</code>
1361      *         parameter was <code>DELETE_CONTENTS</code>, the
1362      *         return value is null.
1363      */
1364     private DocumentFragment
1365         traverseCommonEndContainer( Node startAncestor, int how )
1366     {
1367         DocumentFragment frag = null;
1368         if ( how!=DELETE_CONTENTS)
1369             frag = fDocument.createDocumentFragment();
1370         Node n = traverseLeftBoundary( startAncestor, how );
1371         if ( frag!=null )
1372             frag.appendChild( n );
1373         int startIdx = indexOf( startAncestor, fEndContainer );
1374         ++startIdx;  // Because we already traversed it....
1375 
1376         int cnt = fEndOffset - startIdx;
1377         n = startAncestor.getNextSibling();
1378         while( cnt > 0 )
1379         {
1380             Node sibling = n.getNextSibling();
1381             Node xferNode = traverseFullySelected( n, how );
1382             if ( frag!=null )
1383                 frag.appendChild( xferNode );
1384             --cnt;
1385             n = sibling;
1386         }
1387 
1388         if ( how != CLONE_CONTENTS )
1389         {
1390             setStartAfter( startAncestor );
1391             collapse( true );
1392         }
1393 
1394         return frag;
1395     }
1396 
1397     /**
1398      * Visits the nodes selected by this range when we know
1399      * a-priori that the start and end containers are not
1400      * the same, and we also know that neither the start
1401      * nor end container is an ancestor of the other.
1402      * This method is invoked by
1403      * the generic <code>traverse</code> method.
1404      *
1405      * @param startAncestor
1406      *               Given a common ancestor of the start and end containers,
1407      *               this parameter is the ancestor (or self) of the start
1408      *               container that is a direct child of the common ancestor.
1409      *
1410      * @param endAncestor
1411      *               Given a common ancestor of the start and end containers,
1412      *               this parameter is the ancestor (or self) of the end
1413      *               container that is a direct child of the common ancestor.
1414      *
1415      * @param how    Specifies what type of traversal is being
1416      *               requested (extract, clone, or delete).
1417      *               Legal values for this argument are:
1418      *
1419      *               <ol>
1420      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1421      *               a document fragment containing the range's content.
1422      *               Partially selected nodes are copied, but fully
1423      *               selected nodes are moved.
1424      *
1425      *               <li><code>CLONE_CONTENTS</code> - will leave the
1426      *               context tree of the range undisturbed, but sill
1427      *               produced cloned content in a document fragment
1428      *
1429      *               <li><code>DELETE_CONTENTS</code> - will delete from
1430      *               the context tree of the range, all fully selected
1431      *               nodes.
1432      *               </ol>
1433      *
1434      * @return Returns a document fragment containing any
1435      *         copied or extracted nodes.  If the <code>how</code>
1436      *         parameter was <code>DELETE_CONTENTS</code>, the
1437      *         return value is null.
1438      */
1439     private DocumentFragment
1440         traverseCommonAncestors( Node startAncestor, Node endAncestor, int how )
1441     {
1442         DocumentFragment frag = null;
1443         if ( how!=DELETE_CONTENTS)
1444             frag = fDocument.createDocumentFragment();
1445 
1446         Node n = traverseLeftBoundary( startAncestor, how );
1447         if ( frag!=null )
1448             frag.appendChild( n );
1449 
1450         Node commonParent = startAncestor.getParentNode();
1451         int startOffset = indexOf( startAncestor, commonParent );
1452         int endOffset = indexOf( endAncestor, commonParent );
1453         ++startOffset;
1454 
1455         int cnt = endOffset - startOffset;
1456         Node sibling = startAncestor.getNextSibling();
1457 
1458         while( cnt > 0 )
1459         {
1460             Node nextSibling = sibling.getNextSibling();
1461             n = traverseFullySelected( sibling, how );
1462             if ( frag!=null )
1463                 frag.appendChild( n );
1464             sibling = nextSibling;
1465             --cnt;
1466         }
1467 
1468         n = traverseRightBoundary( endAncestor, how );
1469         if ( frag!=null )
1470             frag.appendChild( n );
1471 
1472         if ( how != CLONE_CONTENTS )
1473         {
1474             setStartAfter( startAncestor );
1475             collapse( true );
1476         }
1477         return frag;
1478     }
1479 
1480     /**
1481      * Traverses the "right boundary" of this range and
1482      * operates on each "boundary node" according to the
1483      * <code>how</code> parameter.  It is a-priori assumed
1484      * by this method that the right boundary does
1485      * not contain the range's start container.
1486      * <p>
1487      * A "right boundary" is best visualized by thinking
1488      * of a sample tree:<pre>
1489      *                 A
1490      *                /|\
1491      *               / | \
1492      *              /  |  \
1493      *             B   C   D
1494      *            /|\     /|\
1495      *           E F G   H I J
1496      * </pre>
1497      * Imagine first a range that begins between the
1498      * "E" and "F" nodes and ends between the
1499      * "I" and "J" nodes.  The start container is
1500      * "B" and the end container is "D".  Given this setup,
1501      * the following applies:
1502      * <p>
1503      * Partially Selected Nodes: B, D<br>
1504      * Fully Selected Nodes: F, G, C, H, I
1505      * <p>
1506      * The "right boundary" is the highest subtree node
1507      * that contains the ending container.  The root of
1508      * this subtree is always partially selected.
1509      * <p>
1510      * In this example, the nodes that are traversed
1511      * as "right boundary" nodes are: H, I, and D.
1512      *
1513      * @param root   The node that is the root of the "right boundary" subtree.
1514      *
1515      * @param how    Specifies what type of traversal is being
1516      *               requested (extract, clone, or delete).
1517      *               Legal values for this argument are:
1518      *
1519      *               <ol>
1520      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1521      *               a node containing the boundaries content.
1522      *               Partially selected nodes are copied, but fully
1523      *               selected nodes are moved.
1524      *
1525      *               <li><code>CLONE_CONTENTS</code> - will leave the
1526      *               context tree of the range undisturbed, but will
1527      *               produced cloned content.
1528      *
1529      *               <li><code>DELETE_CONTENTS</code> - will delete from
1530      *               the context tree of the range, all fully selected
1531      *               nodes within the boundary.
1532      *               </ol>
1533      *
1534      * @return Returns a node that is the result of visiting nodes.
1535      *         If the traversal operation is
1536      *         <code>DELETE_CONTENTS</code> the return value is null.
1537      */
1538     private Node traverseRightBoundary( Node root, int how )
1539     {
1540         Node next = getSelectedNode( fEndContainer, fEndOffset-1 );
1541         boolean isFullySelected = ( next!=fEndContainer );
1542 
1543         if ( next==root )
1544             return traverseNode( next, isFullySelected, false, how );
1545 
1546         Node parent = next.getParentNode();
1547         Node clonedParent = traverseNode( parent, false, false, how );
1548 
1549         while( parent!=null )
1550         {
1551             while( next!=null )
1552             {
1553                 Node prevSibling = next.getPreviousSibling();
1554                 Node clonedChild =
1555                     traverseNode( next, isFullySelected, false, how );
1556                 if ( how!=DELETE_CONTENTS )
1557                 {
1558                     clonedParent.insertBefore(
1559                         clonedChild,
1560                         clonedParent.getFirstChild()
1561                     );
1562                 }
1563                 isFullySelected = true;
1564                 next = prevSibling;
1565             }
1566             if ( parent==root )
1567                 return clonedParent;
1568 
1569             next = parent.getPreviousSibling();
1570             parent = parent.getParentNode();
1571             Node clonedGrandParent = traverseNode( parent, false, false, how );
1572             if ( how!=DELETE_CONTENTS )
1573                 clonedGrandParent.appendChild( clonedParent );
1574             clonedParent = clonedGrandParent;
1575 
1576         }
1577 
1578         // should never occur
1579         return null;
1580     }
1581 
1582     /**
1583      * Traverses the "left boundary" of this range and
1584      * operates on each "boundary node" according to the
1585      * <code>how</code> parameter.  It is a-priori assumed
1586      * by this method that the left boundary does
1587      * not contain the range's end container.
1588      * <p>
1589      * A "left boundary" is best visualized by thinking
1590      * of a sample tree:<pre>
1591      *
1592      *                 A
1593      *                /|\
1594      *               / | \
1595      *              /  |  \
1596      *             B   C   D
1597      *            /|\     /|\
1598      *           E F G   H I J
1599      * </pre>
1600      * Imagine first a range that begins between the
1601      * "E" and "F" nodes and ends between the
1602      * "I" and "J" nodes.  The start container is
1603      * "B" and the end container is "D".  Given this setup,
1604      * the following applies:
1605      * <p>
1606      * Partially Selected Nodes: B, D<br>
1607      * Fully Selected Nodes: F, G, C, H, I
1608      * <p>
1609      * The "left boundary" is the highest subtree node
1610      * that contains the starting container.  The root of
1611      * this subtree is always partially selected.
1612      * <p>
1613      * In this example, the nodes that are traversed
1614      * as "left boundary" nodes are: F, G, and B.
1615      *
1616      * @param root   The node that is the root of the "left boundary" subtree.
1617      *
1618      * @param how    Specifies what type of traversal is being
1619      *               requested (extract, clone, or delete).
1620      *               Legal values for this argument are:
1621      *
1622      *               <ol>
1623      *               <li><code>EXTRACT_CONTENTS</code> - will produce
1624      *               a node containing the boundaries content.
1625      *               Partially selected nodes are copied, but fully
1626      *               selected nodes are moved.
1627      *
1628      *               <li><code>CLONE_CONTENTS</code> - will leave the
1629      *               context tree of the range undisturbed, but will
1630      *               produced cloned content.
1631      *
1632      *               <li><code>DELETE_CONTENTS</code> - will delete from
1633      *               the context tree of the range, all fully selected
1634      *               nodes within the boundary.
1635      *               </ol>
1636      *
1637      * @return Returns a node that is the result of visiting nodes.
1638      *         If the traversal operation is
1639      *         <code>DELETE_CONTENTS</code> the return value is null.
1640      */
1641     private Node traverseLeftBoundary( Node root, int how )
1642     {
1643         Node next = getSelectedNode( getStartContainer(), getStartOffset() );
1644         boolean isFullySelected = ( next!=getStartContainer() );
1645 
1646         if ( next==root )
1647             return traverseNode( next, isFullySelected, true, how );
1648 
1649         Node parent = next.getParentNode();
1650         Node clonedParent = traverseNode( parent, false, true, how );
1651 
1652         while( parent!=null )
1653         {
1654             while( next!=null )
1655             {
1656                 Node nextSibling = next.getNextSibling();
1657                 Node clonedChild =
1658                     traverseNode( next, isFullySelected, true, how );
1659                 if ( how!=DELETE_CONTENTS )
1660                     clonedParent.appendChild(clonedChild);
1661                 isFullySelected = true;
1662                 next = nextSibling;
1663             }
1664             if ( parent==root )
1665                 return clonedParent;
1666 
1667             next = parent.getNextSibling();
1668             parent = parent.getParentNode();
1669             Node clonedGrandParent = traverseNode( parent, false, true, how );
1670             if ( how!=DELETE_CONTENTS )
1671                 clonedGrandParent.appendChild( clonedParent );
1672             clonedParent = clonedGrandParent;
1673 
1674         }
1675 
1676         // should never occur
1677         return null;
1678 
1679     }
1680 
1681     /**
1682      * Utility method for traversing a single node.
1683      * Does not properly handle a text node containing both the
1684      * start and end offsets.  Such nodes should
1685      * have been previously detected and been routed to traverseTextNode.
1686      *
1687      * @param n      The node to be traversed.
1688      *
1689      * @param isFullySelected
1690      *               Set to true if the node is fully selected.  Should be
1691      *               false otherwise.
1692      *               Note that although the DOM 2 specification says that a
1693      *               text node that is boththe start and end container is not
1694      *               selected, we treat it here as if it were partially
1695      *               selected.
1696      *
1697      * @param isLeft Is true if we are traversing the node as part of navigating
1698      *               the "left boundary" of the range.  If this value is false,
1699      *               it implies we are navigating the "right boundary" of the
1700      *               range.
1701      *
1702      * @param how    Specifies what type of traversal is being
1703      *               requested (extract, clone, or delete).
1704      *               Legal values for this argument are:
1705      *
1706      *               <ol>
1707      *               <li><code>EXTRACT_CONTENTS</code> - will simply
1708      *               return the original node.
1709      *
1710      *               <li><code>CLONE_CONTENTS</code> - will leave the
1711      *               context tree of the range undisturbed, but will
1712      *               return a cloned node.
1713      *
1714      *               <li><code>DELETE_CONTENTS</code> - will delete the
1715      *               node from it's parent, but will return null.
1716      *               </ol>
1717      *
1718      * @return Returns a node that is the result of visiting the node.
1719      *         If the traversal operation is
1720      *         <code>DELETE_CONTENTS</code> the return value is null.
1721      */
1722     private Node traverseNode( Node n, boolean isFullySelected, boolean isLeft, int how )
1723     {
1724         if ( isFullySelected )
1725             return traverseFullySelected( n, how );
1726         if ( n.getNodeType()==Node.TEXT_NODE )
1727             return traverseTextNode( n, isLeft, how );
1728         return traversePartiallySelected( n, how );
1729     }
1730 
1731     /**
1732      * Utility method for traversing a single node when
1733      * we know a-priori that the node if fully
1734      * selected.
1735      *
1736      * @param n      The node to be traversed.
1737      *
1738      * @param how    Specifies what type of traversal is being
1739      *               requested (extract, clone, or delete).
1740      *               Legal values for this argument are:
1741      *
1742      *               <ol>
1743      *               <li><code>EXTRACT_CONTENTS</code> - will simply
1744      *               return the original node.
1745      *
1746      *               <li><code>CLONE_CONTENTS</code> - will leave the
1747      *               context tree of the range undisturbed, but will
1748      *               return a cloned node.
1749      *
1750      *               <li><code>DELETE_CONTENTS</code> - will delete the
1751      *               node from it's parent, but will return null.
1752      *               </ol>
1753      *
1754      * @return Returns a node that is the result of visiting the node.
1755      *         If the traversal operation is
1756      *         <code>DELETE_CONTENTS</code> the return value is null.
1757      */
1758     private Node traverseFullySelected( Node n, int how )
1759     {
1760         switch( how )
1761         {
1762         case CLONE_CONTENTS:
1763             return n.cloneNode( true );
1764         case EXTRACT_CONTENTS:
1765             if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE )
1766             {
1767                 // TBD: This should be a HIERARCHY_REQUEST_ERR
1768                 throw new DOMException(
1769                         DOMException.HIERARCHY_REQUEST_ERR,
1770                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "HIERARCHY_REQUEST_ERR", null));
1771             }
1772             return n;
1773         case DELETE_CONTENTS:
1774             n.getParentNode().removeChild(n);
1775             return null;
1776         }
1777         return null;
1778     }
1779 
1780     /**
1781      * Utility method for traversing a single node when
1782      * we know a-priori that the node if partially
1783      * selected and is not a text node.
1784      *
1785      * @param n      The node to be traversed.
1786      *
1787      * @param how    Specifies what type of traversal is being
1788      *               requested (extract, clone, or delete).
1789      *               Legal values for this argument are:
1790      *
1791      *               <ol>
1792      *               <li><code>EXTRACT_CONTENTS</code> - will simply
1793      *               return the original node.
1794      *
1795      *               <li><code>CLONE_CONTENTS</code> - will leave the
1796      *               context tree of the range undisturbed, but will
1797      *               return a cloned node.
1798      *
1799      *               <li><code>DELETE_CONTENTS</code> - will delete the
1800      *               node from it's parent, but will return null.
1801      *               </ol>
1802      *
1803      * @return Returns a node that is the result of visiting the node.
1804      *         If the traversal operation is
1805      *         <code>DELETE_CONTENTS</code> the return value is null.
1806      */
1807     private Node traversePartiallySelected( Node n, int how )
1808     {
1809         switch( how )
1810         {
1811         case DELETE_CONTENTS:
1812             return null;
1813         case CLONE_CONTENTS:
1814         case EXTRACT_CONTENTS:
1815             return n.cloneNode( false );
1816         }
1817         return null;
1818     }
1819 
1820     /**
1821      * Utility method for traversing a text node that we know
1822      * a-priori to be on a left or right boundary of the range.
1823      * This method does not properly handle text nodes that contain
1824      * both the start and end points of the range.
1825      *
1826      * @param n      The node to be traversed.
1827      *
1828      * @param isLeft Is true if we are traversing the node as part of navigating
1829      *               the "left boundary" of the range.  If this value is false,
1830      *               it implies we are navigating the "right boundary" of the
1831      *               range.
1832      *
1833      * @param how    Specifies what type of traversal is being
1834      *               requested (extract, clone, or delete).
1835      *               Legal values for this argument are:
1836      *
1837      *               <ol>
1838      *               <li><code>EXTRACT_CONTENTS</code> - will simply
1839      *               return the original node.
1840      *
1841      *               <li><code>CLONE_CONTENTS</code> - will leave the
1842      *               context tree of the range undisturbed, but will
1843      *               return a cloned node.
1844      *
1845      *               <li><code>DELETE_CONTENTS</code> - will delete the
1846      *               node from it's parent, but will return null.
1847      *               </ol>
1848      *
1849      * @return Returns a node that is the result of visiting the node.
1850      *         If the traversal operation is
1851      *         <code>DELETE_CONTENTS</code> the return value is null.
1852      */
1853     private Node traverseTextNode( Node n, boolean isLeft, int how )
1854     {
1855         String txtValue = n.getNodeValue();
1856         String newNodeValue;
1857         String oldNodeValue;
1858 
1859         if ( isLeft )
1860         {
1861             int offset = getStartOffset();
1862             newNodeValue = txtValue.substring( offset );
1863             oldNodeValue = txtValue.substring( 0, offset );
1864         }
1865         else
1866         {
1867             int offset = getEndOffset();
1868             newNodeValue = txtValue.substring( 0, offset );
1869             oldNodeValue = txtValue.substring( offset );
1870         }
1871 
1872         if ( how != CLONE_CONTENTS )
1873             n.setNodeValue( oldNodeValue );
1874         if ( how==DELETE_CONTENTS )
1875             return null;
1876         Node newNode = n.cloneNode( false );
1877         newNode.setNodeValue( newNodeValue );
1878         return newNode;
1879     }
1880 
1881     void checkIndex(Node refNode, int offset) throws DOMException
1882     {
1883         if (offset < 0) {
1884             throw new DOMException(
1885                 DOMException.INDEX_SIZE_ERR,
1886                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1887         }
1888 
1889         int type = refNode.getNodeType();
1890 
1891         // If the node contains text, ensure that the
1892         // offset of the range is <= to the length of the text
1893         if (type == Node.TEXT_NODE
1894             || type == Node.CDATA_SECTION_NODE
1895             || type == Node.COMMENT_NODE
1896             || type == Node.PROCESSING_INSTRUCTION_NODE) {
1897             if (offset > refNode.getNodeValue().length()) {
1898                 throw new DOMException(DOMException.INDEX_SIZE_ERR,
1899                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1900             }
1901         }
1902         else {
1903             // Since the node is not text, ensure that the offset
1904             // is valid with respect to the number of child nodes
1905             if (offset > refNode.getChildNodes().getLength()) {
1906                 throw new DOMException(DOMException.INDEX_SIZE_ERR,
1907                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1908             }
1909         }
1910     }
1911 
1912         /**
1913          * Given a node, calculate what the Range's root container
1914          * for that node would be.
1915          */
1916         private Node getRootContainer( Node node )
1917         {
1918                 if ( node==null )
1919                         return null;
1920 
1921                 while( node.getParentNode()!=null )
1922                         node = node.getParentNode();
1923                 return node;
1924         }
1925 
1926         /**
1927          * Returns true IFF the given node can serve as a container
1928          * for a range's boundary points.
1929          */
1930         private boolean isLegalContainer( Node node )
1931         {
1932                 if ( node==null )
1933                         return false;
1934 
1935                 while( node!=null )
1936                 {
1937                         switch( node.getNodeType() )
1938                         {
1939                         case Node.ENTITY_NODE:
1940                         case Node.NOTATION_NODE:
1941                         case Node.DOCUMENT_TYPE_NODE:
1942                                 return false;
1943                         }
1944                         node = node.getParentNode();
1945                 }
1946 
1947                 return true;
1948         }
1949 
1950 
1951         /**
1952          * Finds the root container for the given node and determines
1953          * if that root container is legal with respect to the
1954          * DOM 2 specification.  At present, that means the root
1955          * container must be either an attribute, a document,
1956          * or a document fragment.
1957          */
1958         private boolean hasLegalRootContainer( Node node )
1959         {
1960                 if ( node==null )
1961                         return false;
1962 
1963                 Node rootContainer = getRootContainer( node );
1964                 switch( rootContainer.getNodeType() )
1965                 {
1966                 case Node.ATTRIBUTE_NODE:
1967                 case Node.DOCUMENT_NODE:
1968                 case Node.DOCUMENT_FRAGMENT_NODE:
1969                         return true;
1970                 }
1971                 return false;
1972         }
1973 
1974         /**
1975          * Returns true IFF the given node can be contained by
1976          * a range.
1977          */
1978         private boolean isLegalContainedNode( Node node )
1979         {
1980                 if ( node==null )
1981                         return false;
1982                 switch( node.getNodeType() )
1983                 {
1984                 case Node.DOCUMENT_NODE:
1985                 case Node.DOCUMENT_FRAGMENT_NODE:
1986                 case Node.ATTRIBUTE_NODE:
1987                 case Node.ENTITY_NODE:
1988                 case Node.NOTATION_NODE:
1989                         return false;
1990                 }
1991                 return true;
1992         }
1993 
1994     Node nextNode(Node node, boolean visitChildren) {
1995 
1996         if (node == null) return null;
1997 
1998         Node result;
1999         if (visitChildren) {
2000             result = node.getFirstChild();
2001             if (result != null) {
2002                 return result;
2003             }
2004         }
2005 
2006         // if hasSibling, return sibling
2007         result = node.getNextSibling();
2008         if (result != null) {
2009             return result;
2010         }
2011 
2012 
2013         // return parent's 1st sibling.
2014         Node parent = node.getParentNode();
2015         while (parent != null
2016                && parent != fDocument
2017                 ) {
2018             result = parent.getNextSibling();
2019             if (result != null) {
2020                 return result;
2021             } else {
2022                 parent = parent.getParentNode();
2023             }
2024 
2025         } // while (parent != null && parent != fRoot) {
2026 
2027         // end of list, return null
2028         return null;
2029     }
2030 
2031     /** is a an ancestor of b ? */
2032     boolean isAncestorOf(Node a, Node b) {
2033         for (Node node=b; node != null; node=node.getParentNode()) {
2034             if (node == a) return true;
2035         }
2036         return false;
2037     }
2038 
2039     /** what is the index of the child in the parent */
2040     int indexOf(Node child, Node parent) {
2041         if (child.getParentNode() != parent) return -1;
2042         int i = 0;
2043         for(Node node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) {
2044             i++;
2045         }
2046         return i;
2047     }
2048 
2049     /**
2050      * Utility method to retrieve a child node by index.  This method
2051      * assumes the caller is trying to find out which node is
2052      * selected by the given index.  Note that if the index is
2053      * greater than the number of children, this implies that the
2054      * first node selected is the parent node itself.
2055      *
2056      * @param container A container node
2057      *
2058      * @param offset    An offset within the container for which a selected node should
2059      *                  be computed.  If the offset is less than zero, or if the offset
2060      *                  is greater than the number of children, the container is returned.
2061      *
2062      * @return Returns either a child node of the container or the
2063      *         container itself.
2064      */
2065     private Node getSelectedNode( Node container, int offset )
2066     {
2067         if ( container.getNodeType() == Node.TEXT_NODE )
2068             return container;
2069 
2070         // This case is an important convenience for
2071         // traverseRightBoundary()
2072         if ( offset<0 )
2073             return container;
2074 
2075         Node child = container.getFirstChild();
2076         while( child!=null && offset > 0 )
2077         {
2078             --offset;
2079             child = child.getNextSibling();
2080         }
2081         if ( child!=null )
2082             return child;
2083         return container;
2084     }
2085 
2086 }