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