KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > org > apache > xerces > internal > dom > RangeImpl


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  *
5  * Copyright (c) 1999-2003 The Apache Software Foundation. All rights
6  * reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Apache Software Foundation (http://www.apache.org/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Xerces" and "Apache Software Foundation" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact apache@apache.org.
31  *
32  * 5. Products derived from this software may not be called "Apache",
33  * nor may "Apache" appear in their name, without prior written
34  * permission of the Apache Software Foundation.
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  * SUCH DAMAGE.
48  * ====================================================================
49  *
50  * This software consists of voluntary contributions made by many
51  * individuals on behalf of the Apache Software Foundation and was
52  * originally based on software copyright (c) 1999, International
53  * Business Machines, Inc., http://www.apache.org. For more
54  * information on the Apache Software Foundation, please see
55  * <http://www.apache.org/>.
56  */

57
58 package com.sun.org.apache.xerces.internal.dom;
59
60 import java.util.Vector JavaDoc;
61
62 import org.w3c.dom.CharacterData JavaDoc;
63 import org.w3c.dom.DOMException JavaDoc;
64 import org.w3c.dom.DocumentFragment JavaDoc;
65 import org.w3c.dom.Node JavaDoc;
66 import org.w3c.dom.ranges.Range;
67 import org.w3c.dom.ranges.RangeException;
68
69
70 /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
71  * <p> Please see the API documentation for the interface classes
72  * and use the interfaces in your client programs.
73  *
74  * @version $Id: RangeImpl.java,v 1.28 2003/11/02 15:07:58 mrglavas Exp $
75  */

76 public class RangeImpl implements Range {
77     
78     //
79
// Constants
80
//
81

82
83     //
84
// Data
85
//
86

87     DocumentImpl fDocument;
88     Node JavaDoc fStartContainer;
89     Node JavaDoc fEndContainer;
90     int fStartOffset;
91     int fEndOffset;
92     boolean fIsCollapsed;
93     boolean fDetach = false;
94     Node JavaDoc fInsertNode = null;
95     Node JavaDoc fDeleteNode = null;
96     Node JavaDoc fSplitNode = null;
97     
98     
99     /** The constructor. Clients must use DocumentRange.createRange(),
100      * because it registers the Range with the document, so it can
101      * be fixed-up.
102      */

103     public RangeImpl(DocumentImpl document) {
104         fDocument = document;
105         fStartContainer = document;
106         fEndContainer = document;
107         fStartOffset = 0;
108         fEndOffset = 0;
109         fDetach = false;
110     }
111     
112     public Node JavaDoc getStartContainer() {
113         if ( fDetach ) {
114             throw new DOMException JavaDoc(
115                 DOMException.INVALID_STATE_ERR,
116                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
117         }
118         return fStartContainer;
119     }
120     
121     public int getStartOffset() {
122         if ( fDetach ) {
123             throw new DOMException JavaDoc(
124                 DOMException.INVALID_STATE_ERR,
125                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
126         }
127         return fStartOffset;
128     }
129     
130     public Node JavaDoc getEndContainer() {
131         if ( fDetach ) {
132             throw new DOMException JavaDoc(
133                 DOMException.INVALID_STATE_ERR,
134                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
135         }
136         return fEndContainer;
137     }
138     
139     public int getEndOffset() {
140         if ( fDetach ) {
141             throw new DOMException JavaDoc(
142                 DOMException.INVALID_STATE_ERR,
143                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
144         }
145         return fEndOffset;
146     }
147     
148     public boolean getCollapsed() {
149         if ( fDetach ) {
150             throw new DOMException JavaDoc(
151                 DOMException.INVALID_STATE_ERR,
152                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
153         }
154         return (fStartContainer == fEndContainer
155              && fStartOffset == fEndOffset);
156     }
157     
158     public Node JavaDoc getCommonAncestorContainer() {
159         if ( fDetach ) {
160             throw new DOMException JavaDoc(
161                 DOMException.INVALID_STATE_ERR,
162                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
163         }
164         Vector JavaDoc startV = new Vector JavaDoc();
165         Node JavaDoc node;
166         for (node=fStartContainer; node != null;
167              node=node.getParentNode())
168         {
169             startV.addElement(node);
170         }
171         Vector JavaDoc endV = new Vector JavaDoc();
172         for (node=fEndContainer; node != null;
173              node=node.getParentNode())
174         {
175             endV.addElement(node);
176         }
177         int s = startV.size()-1;
178         int e = endV.size()-1;
179         Object JavaDoc result = null;
180         while (s>=0 && e>=0) {
181             if (startV.elementAt(s) == endV.elementAt(e)) {
182                 result = startV.elementAt(s);
183             } else {
184                 break;
185             }
186             --s;
187             --e;
188         }
189         return (Node JavaDoc)result;
190     }
191     
192     
193     public void setStart(Node JavaDoc refNode, int offset)
194                          throws RangeException, DOMException JavaDoc
195     {
196         if( fDetach) {
197             throw new DOMException JavaDoc(
198             DOMException.INVALID_STATE_ERR,
199                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
200         }
201         if ( !isLegalContainer(refNode)) {
202             throw new RangeExceptionImpl(
203             RangeException.INVALID_NODE_TYPE_ERR,
204                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
205         }
206         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
207             throw new DOMException JavaDoc(
208                 DOMException.WRONG_DOCUMENT_ERR,
209                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
210         }
211
212         
213         checkIndex(refNode, offset);
214         
215         fStartContainer = refNode;
216         fStartOffset = offset;
217     }
218     
219     public void setEnd(Node JavaDoc refNode, int offset)
220                        throws RangeException, DOMException JavaDoc
221     {
222         if( fDetach) {
223             throw new DOMException JavaDoc(
224             DOMException.INVALID_STATE_ERR,
225                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
226         }
227         if ( !isLegalContainer(refNode)) {
228             throw new RangeExceptionImpl(
229             RangeException.INVALID_NODE_TYPE_ERR,
230                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
231         }
232         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
233             throw new DOMException JavaDoc(
234                 DOMException.WRONG_DOCUMENT_ERR,
235                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
236         }
237         
238         checkIndex(refNode, offset);
239         
240         fEndContainer = refNode;
241         fEndOffset = offset;
242     }
243
244     public void setStartBefore(Node JavaDoc refNode)
245         throws RangeException
246     {
247         if( fDetach) {
248             throw new DOMException JavaDoc(
249             DOMException.INVALID_STATE_ERR,
250                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
251         }
252         if ( !hasLegalRootContainer(refNode) ||
253             !isLegalContainedNode(refNode) )
254         {
255             throw new RangeExceptionImpl(
256             RangeException.INVALID_NODE_TYPE_ERR,
257                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
258         }
259         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
260             throw new DOMException JavaDoc(
261                 DOMException.WRONG_DOCUMENT_ERR,
262                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
263         }
264         fStartContainer = refNode.getParentNode();
265         int i = 0;
266         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
267             i++;
268         }
269         fStartOffset = i-1;
270     }
271     
272     public void setStartAfter(Node JavaDoc refNode)
273         throws RangeException
274     {
275         if( fDetach) {
276             throw new DOMException JavaDoc(
277             DOMException.INVALID_STATE_ERR,
278                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
279         }
280         if ( !hasLegalRootContainer(refNode) ||
281             !isLegalContainedNode(refNode)) {
282             throw new RangeExceptionImpl(
283             RangeException.INVALID_NODE_TYPE_ERR,
284                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
285         }
286         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
287             throw new DOMException JavaDoc(
288                 DOMException.WRONG_DOCUMENT_ERR,
289                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
290         }
291         fStartContainer = refNode.getParentNode();
292         int i = 0;
293         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
294             i++;
295         }
296         fStartOffset = i;
297     }
298     
299     public void setEndBefore(Node JavaDoc refNode)
300         throws RangeException
301     {
302         if( fDetach) {
303             throw new DOMException JavaDoc(
304             DOMException.INVALID_STATE_ERR,
305                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
306         }
307         if ( !hasLegalRootContainer(refNode) ||
308             !isLegalContainedNode(refNode)) {
309             throw new RangeExceptionImpl(
310             RangeException.INVALID_NODE_TYPE_ERR,
311                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
312         }
313         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
314             throw new DOMException JavaDoc(
315                 DOMException.WRONG_DOCUMENT_ERR,
316                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
317         }
318         fEndContainer = refNode.getParentNode();
319         int i = 0;
320         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
321             i++;
322         }
323         fEndOffset = i-1;
324     }
325                                             
326     public void setEndAfter(Node JavaDoc refNode)
327         throws RangeException
328     {
329         if( fDetach) {
330             throw new DOMException JavaDoc(
331             DOMException.INVALID_STATE_ERR,
332                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
333         }
334         if ( !hasLegalRootContainer(refNode) ||
335             !isLegalContainedNode(refNode)) {
336             throw new RangeExceptionImpl(
337             RangeException.INVALID_NODE_TYPE_ERR,
338                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
339         }
340         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
341             throw new DOMException JavaDoc(
342                 DOMException.WRONG_DOCUMENT_ERR,
343                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
344         }
345         fEndContainer = refNode.getParentNode();
346         int i = 0;
347         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
348             i++;
349         }
350         fEndOffset = i;
351     }
352     
353     public void collapse(boolean toStart) {
354         
355         if( fDetach) {
356             throw new DOMException JavaDoc(
357             DOMException.INVALID_STATE_ERR,
358                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
359         }
360         
361         if (toStart) {
362             fEndContainer = fStartContainer;
363             fEndOffset = fStartOffset;
364         } else {
365             fStartContainer = fEndContainer;
366             fStartOffset = fEndOffset;
367         }
368     }
369     
370     public void selectNode(Node JavaDoc refNode)
371         throws RangeException
372     {
373         if( fDetach) {
374             throw new DOMException JavaDoc(
375             DOMException.INVALID_STATE_ERR,
376                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
377         }
378         if ( !isLegalContainer( refNode.getParentNode() ) ||
379             !isLegalContainedNode( refNode ) ) {
380             throw new RangeExceptionImpl(
381             RangeException.INVALID_NODE_TYPE_ERR,
382                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
383         }
384         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
385             throw new DOMException JavaDoc(
386                 DOMException.WRONG_DOCUMENT_ERR,
387                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
388         }
389         Node JavaDoc parent = refNode.getParentNode();
390         if (parent != null ) // REVIST: what to do if it IS null?
391
{
392             fStartContainer = parent;
393             fEndContainer = parent;
394             int i = 0;
395             for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
396                 i++;
397             }
398             fStartOffset = i-1;
399             fEndOffset = fStartOffset+1;
400         }
401     }
402         
403     public void selectNodeContents(Node JavaDoc refNode)
404         throws RangeException
405     {
406         if( fDetach) {
407             throw new DOMException JavaDoc(
408             DOMException.INVALID_STATE_ERR,
409                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
410         }
411         if ( !isLegalContainer(refNode)) {
412             throw new RangeExceptionImpl(
413             RangeException.INVALID_NODE_TYPE_ERR,
414                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
415         }
416         if ( fDocument != refNode.getOwnerDocument() && fDocument != refNode) {
417             throw new DOMException JavaDoc(
418                 DOMException.WRONG_DOCUMENT_ERR,
419                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
420         }
421         fStartContainer = refNode;
422         fEndContainer = refNode;
423         Node JavaDoc first = refNode.getFirstChild();
424         fStartOffset = 0;
425         if (first == null) {
426             fEndOffset = 0;
427         } else {
428             int i = 0;
429             for (Node JavaDoc n = first; n!=null; n = n.getNextSibling()) {
430                 i++;
431             }
432             fEndOffset = i;
433         }
434         
435     }
436
437     public short compareBoundaryPoints(short how, Range sourceRange)
438         throws DOMException JavaDoc
439     {
440         if( fDetach) {
441             throw new DOMException JavaDoc(
442             DOMException.INVALID_STATE_ERR,
443                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
444         }
445        
446         Node JavaDoc endPointA;
447         Node JavaDoc endPointB;
448         int offsetA;
449         int offsetB;
450         
451         if (how == START_TO_START) {
452             endPointA = sourceRange.getStartContainer();
453             endPointB = fStartContainer;
454             offsetA = sourceRange.getStartOffset();
455             offsetB = fStartOffset;
456         } else
457         if (how == START_TO_END) {
458             endPointA = sourceRange.getStartContainer();
459             endPointB = fEndContainer;
460             offsetA = sourceRange.getStartOffset();
461             offsetB = fEndOffset;
462         } else
463         if (how == END_TO_START) {
464             endPointA = sourceRange.getEndContainer();
465             endPointB = fStartContainer;
466             offsetA = sourceRange.getEndOffset();
467             offsetB = fStartOffset;
468         } else {
469             endPointA = sourceRange.getEndContainer();
470             endPointB = fEndContainer;
471             offsetA = sourceRange.getEndOffset();
472             offsetB = fEndOffset;
473         }
474
475         // The DOM Spec outlines four cases that need to be tested
476
// to compare two range boundary points:
477
// case 1: same container
478
// case 2: Child C of container A is ancestor of B
479
// case 3: Child C of container B is ancestor of A
480
// case 4: preorder traversal of context tree.
481

482         // case 1: same container
483
if (endPointA == endPointB) {
484             if (offsetA < offsetB) return 1;
485             if (offsetA == offsetB) return 0;
486             return -1;
487         }
488         // case 2: Child C of container A is ancestor of B
489
// This can be quickly tested by walking the parent chain of B
490
for ( Node JavaDoc c = endPointB, p = c.getParentNode();
491              p != null;
492              c = p, p = p.getParentNode())
493         {
494             if (p == endPointA) {
495                 int index = indexOf(c, endPointA);
496                 if (offsetA <= index) return 1;
497                 return -1;
498             }
499         }
500
501         // case 3: Child C of container B is ancestor of A
502
// This can be quickly tested by walking the parent chain of A
503
for ( Node JavaDoc c = endPointA, p = c.getParentNode();
504              p != null;
505              c = p, p = p.getParentNode())
506         {
507             if (p == endPointB) {
508                 int index = indexOf(c, endPointB);
509                 if (index < offsetB) return 1;
510                 return -1;
511             }
512         }
513
514         // case 4: preorder traversal of context tree.
515
// Instead of literally walking the context tree in pre-order,
516
// we use relative node depth walking which is usually faster
517

518         int depthDiff = 0;
519         for ( Node JavaDoc n = endPointA; n != null; n = n.getParentNode() )
520             depthDiff++;
521         for ( Node JavaDoc n = endPointB; n != null; n = n.getParentNode() )
522             depthDiff--;
523         while (depthDiff > 0) {
524             endPointA = endPointA.getParentNode();
525             depthDiff--;
526         }
527         while (depthDiff < 0) {
528             endPointB = endPointB.getParentNode();
529             depthDiff++;
530         }
531         for (Node JavaDoc pA = endPointA.getParentNode(),
532              pB = endPointB.getParentNode();
533              pA != pB;
534              pA = pA.getParentNode(), pB = pB.getParentNode() )
535         {
536             endPointA = pA;
537             endPointB = pB;
538         }
539         for ( Node JavaDoc n = endPointA.getNextSibling();
540              n != null;
541              n = n.getNextSibling() )
542         {
543             if (n == endPointB) {
544                 return 1;
545             }
546         }
547         return -1;
548     }
549     
550     public void deleteContents()
551         throws DOMException JavaDoc
552     {
553         traverseContents(DELETE_CONTENTS);
554     }
555         
556     public DocumentFragment JavaDoc extractContents()
557         throws DOMException JavaDoc
558     {
559         return traverseContents(EXTRACT_CONTENTS);
560     }
561         
562     public DocumentFragment JavaDoc cloneContents()
563         throws DOMException JavaDoc
564     {
565         return traverseContents(CLONE_CONTENTS);
566     }
567     
568     public void insertNode(Node JavaDoc newNode)
569         throws DOMException JavaDoc, RangeException
570     {
571         if ( newNode == null ) return; //throw exception?
572

573         if( fDetach) {
574             throw new DOMException JavaDoc(
575             DOMException.INVALID_STATE_ERR,
576                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
577         }
578         if ( fDocument != newNode.getOwnerDocument() ) {
579             throw new DOMException JavaDoc(DOMException.WRONG_DOCUMENT_ERR,
580                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "WRONG_DOCUMENT_ERR", null));
581         }
582        
583         int type = newNode.getNodeType();
584         if (type == Node.ATTRIBUTE_NODE
585             || type == Node.ENTITY_NODE
586             || type == Node.NOTATION_NODE
587             || type == Node.DOCUMENT_NODE)
588         {
589             throw new RangeExceptionImpl(
590             RangeException.INVALID_NODE_TYPE_ERR,
591                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
592         }
593         Node JavaDoc cloneCurrent;
594         Node JavaDoc current;
595         int currentChildren = 0;
596
597         //boolean MULTIPLE_MODE = false;
598
if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
599         
600             Node JavaDoc parent = fStartContainer.getParentNode();
601             currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
602
// split text node: results is 3 nodes..
603
cloneCurrent = fStartContainer.cloneNode(false);
604             ((TextImpl)cloneCurrent).setNodeValueInternal(
605                     (cloneCurrent.getNodeValue()).substring(fStartOffset));
606             ((TextImpl)fStartContainer).setNodeValueInternal(
607                     (fStartContainer.getNodeValue()).substring(0,fStartOffset));
608             Node JavaDoc next = fStartContainer.getNextSibling();
609             if (next != null) {
610                     if (parent != null) {
611                         parent.insertBefore(newNode, next);
612                         parent.insertBefore(cloneCurrent, next);
613                     }
614             } else {
615                     if (parent != null) {
616                         parent.appendChild(newNode);
617                         parent.appendChild(cloneCurrent);
618                     }
619             }
620              //update ranges after the insertion
621
if ( fEndContainer == fStartContainer) {
622                   fEndContainer = cloneCurrent; //endContainer is the new Node created
623
fEndOffset -= fStartOffset;
624              }
625              else if ( fEndContainer == parent ) { //endContainer was not a text Node.
626
//endOffset + = number_of_children_added
627
fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
628              }
629
630              // signal other Ranges to update their start/end containers/offsets
631
signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
632                 
633              
634         } else { // ! TEXT_NODE
635
if ( fEndContainer == fStartContainer ) //need to remember number of kids
636
currentChildren= fEndContainer.getChildNodes().getLength();
637
638             current = fStartContainer.getFirstChild();
639             int i = 0;
640             for(i = 0; i < fStartOffset && current != null; i++) {
641                 current=current.getNextSibling();
642             }
643             if (current != null) {
644                 fStartContainer.insertBefore(newNode, current);
645             } else {
646                 fStartContainer.appendChild(newNode);
647             }
648             //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
649
// insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
650
if ( fEndContainer == fStartContainer ) { //update fEndOffset
651
fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren);
652             }
653
654         }
655     }
656     
657     public void surroundContents(Node JavaDoc newParent)
658         throws DOMException JavaDoc, RangeException
659     {
660         if (newParent==null) return;
661         
662         if( fDetach) {
663             throw new DOMException JavaDoc(
664             DOMException.INVALID_STATE_ERR,
665                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
666         }
667         int type = newParent.getNodeType();
668         if (type == Node.ATTRIBUTE_NODE
669             || type == Node.ENTITY_NODE
670             || type == Node.NOTATION_NODE
671             || type == Node.DOCUMENT_TYPE_NODE
672             || type == Node.DOCUMENT_NODE
673             || type == Node.DOCUMENT_FRAGMENT_NODE)
674         {
675             throw new RangeExceptionImpl(
676             RangeException.INVALID_NODE_TYPE_ERR,
677                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
678         }
679         
680         Node JavaDoc root = getCommonAncestorContainer();
681         
682         Node JavaDoc realStart = fStartContainer;
683         Node JavaDoc realEnd = fEndContainer;
684         if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
685             realStart = fStartContainer.getParentNode();
686         }
687         if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
688             realEnd = fEndContainer.getParentNode();
689         }
690             
691         if (realStart != realEnd) {
692             throw new RangeExceptionImpl(
693             RangeException.BAD_BOUNDARYPOINTS_ERR,
694                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "BAD_BOUNDARYPOINTS_ERR", null));
695         }
696
697         DocumentFragment JavaDoc frag = extractContents();
698         insertNode(newParent);
699         newParent.appendChild(frag);
700         selectNode(newParent);
701     }
702         
703     public Range cloneRange(){
704         if( fDetach) {
705             throw new DOMException JavaDoc(
706             DOMException.INVALID_STATE_ERR,
707                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
708         }
709         
710         Range range = fDocument.createRange();
711         range.setStart(fStartContainer, fStartOffset);
712         range.setEnd(fEndContainer, fEndOffset);
713         return range;
714     }
715     
716     public String JavaDoc toString(){
717         if( fDetach) {
718             throw new DOMException JavaDoc(
719             DOMException.INVALID_STATE_ERR,
720                 DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
721         }
722         
723         Node JavaDoc node = fStartContainer;
724         Node JavaDoc stopNode = fEndContainer;
725         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
726         if (fStartContainer.getNodeType() == Node.TEXT_NODE
727          || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE
728         ) {
729             if (fStartContainer == fEndContainer) {
730                 sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset));
731                 return sb.toString();
732             }
733             sb.append(fStartContainer.getNodeValue().substring(fStartOffset));
734             node=nextNode (node,true); //fEndContainer!=fStartContainer
735

736         }
737         else { //fStartContainer is not a TextNode
738
node=node.getFirstChild();
739             if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset
740
int counter=0;
741                while (counter<fStartOffset && node!=null) {
742                    node=node.getNextSibling();
743                    counter++;
744                }
745             }
746             if (node == null) {
747                    node = nextNode(fStartContainer,false);
748             }
749         }
750         if ( fEndContainer.getNodeType()!= Node.TEXT_NODE &&
751              fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){
752              int i=fEndOffset;
753              stopNode = fEndContainer.getFirstChild();
754              while( i>0 && stopNode!=null ){
755                  --i;
756                  stopNode = stopNode.getNextSibling();
757              }
758              if ( stopNode == null )
759                  stopNode = nextNode( fEndContainer, false );
760          }
761          while (node != stopNode) { //look into all kids of the Range
762
if (node == null) break;
763              if (node.getNodeType() == Node.TEXT_NODE
764              || node.getNodeType() == Node.CDATA_SECTION_NODE) {
765                  sb.append(node.getNodeValue());
766              }
767
768              node = nextNode(node, true);
769          }
770
771         if (fEndContainer.getNodeType() == Node.TEXT_NODE
772          || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
773             sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset));
774         }
775         return sb.toString();
776     }
777     
778     public void detach() {
779         fDetach = true;
780         fDocument.removeRange(this);
781     }
782     
783     //
784
// Mutation functions
785
//
786

787     /** Signal other Ranges to update their start/end
788      * containers/offsets. The data has already been split
789      * into the two Nodes.
790      */

791     void signalSplitData(Node JavaDoc node, Node JavaDoc newNode, int offset) {
792         fSplitNode = node;
793         // notify document
794
fDocument.splitData(node, newNode, offset);
795         fSplitNode = null;
796     }
797     
798     /** Fix up this Range if another Range has split a Text Node
799      * into 2 Nodes.
800      */

801     void receiveSplitData(Node JavaDoc node, Node JavaDoc newNode, int offset) {
802         if (node == null || newNode == null) return;
803         if (fSplitNode == node) return;
804         
805         if (node == fStartContainer
806         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
807             if (fStartOffset > offset) {
808                 fStartOffset = fStartOffset - offset;
809                 fStartContainer = newNode;
810             }
811         }
812         if (node == fEndContainer
813         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
814             if (fEndOffset > offset) {
815                 fEndOffset = fEndOffset-offset;
816                 fEndContainer = newNode;
817             }
818         }
819         
820     }
821    
822     /** This function inserts text into a Node and invokes
823      * a method to fix-up all other Ranges.
824      */

825     void deleteData(CharacterData JavaDoc node, int offset, int count) {
826         fDeleteNode = node;
827         node.deleteData( offset, count);
828         fDeleteNode = null;
829     }
830     
831     
832     /** This function is called from DOM.
833      * The text has already beeen inserted.
834      * Fix-up any offsets.
835      */

836     void receiveDeletedText(Node JavaDoc node, int offset, int count) {
837         if (node == null) return;
838         if (fDeleteNode == node) return;
839         if (node == fStartContainer
840         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
841             if (fStartOffset > offset+count) {
842                 fStartOffset = offset+(fStartOffset-(offset+count));
843             } else
844             if (fStartOffset > offset) {
845                 fStartOffset = offset;
846             }
847         }
848         if (node == fEndContainer
849         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
850             if (fEndOffset > offset+count) {
851                 fEndOffset = offset+(fEndOffset-(offset+count));
852             } else
853             if (fEndOffset > offset) {
854                 fEndOffset = offset;
855             }
856         }
857         
858     }
859    
860     /** This function inserts text into a Node and invokes
861      * a method to fix-up all other Ranges.
862      */

863     void insertData(CharacterData JavaDoc node, int index, String JavaDoc insert) {
864         fInsertNode = node;
865         node.insertData( index, insert);
866         fInsertNode = null;
867     }
868     
869     
870     /** This function is called from DOM.
871      * The text has already beeen inserted.
872      * Fix-up any offsets.
873      */

874     void receiveInsertedText(Node JavaDoc node, int index, int len) {
875         if (node == null) return;
876         if (fInsertNode == node) return;
877         if (node == fStartContainer
878         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
879             if (index < fStartOffset) {
880                 fStartOffset = fStartOffset+len;
881             }
882         }
883         if (node == fEndContainer
884         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
885             if (index < fEndOffset) {
886                 fEndOffset = fEndOffset+len;
887             }
888         }
889         
890     }
891    
892     /** This function is called from DOM.
893      * The text has already beeen replaced.
894      * Fix-up any offsets.
895      */

896     void receiveReplacedText(Node JavaDoc node) {
897         if (node == null) return;
898         if (node == fStartContainer
899         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
900             fStartOffset = 0;
901         }
902         if (node == fEndContainer
903         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
904             fEndOffset = 0;
905         }
906         
907     }
908     
909     /** This function is called from the DOM.
910      * This node has already been inserted into the DOM.
911      * Fix-up any offsets.
912      */

913     public void insertedNodeFromDOM(Node JavaDoc node) {
914         if (node == null) return;
915         if (fInsertNode == node) return;
916         
917         Node JavaDoc parent = node.getParentNode();
918         
919         if (parent == fStartContainer) {
920             int index = indexOf(node, fStartContainer);
921             if (index < fStartOffset) {
922                 fStartOffset++;
923             }
924         }
925         
926         if (parent == fEndContainer) {
927             int index = indexOf(node, fEndContainer);
928             if (index < fEndOffset) {
929                 fEndOffset++;
930             }
931         }
932         
933     }
934     
935     /** This function is called within Range
936      * instead of Node.removeChild,
937      * so that the range can remember that it is actively
938      * removing this child.
939      */

940      
941     Node JavaDoc fRemoveChild = null;
942     Node JavaDoc removeChild(Node JavaDoc parent, Node JavaDoc child) {
943         fRemoveChild = child;
944         Node JavaDoc n = parent.removeChild(child);
945         fRemoveChild = null;
946         return n;
947     }
948     
949     /** This function must be called by the DOM _BEFORE_
950      * a node is deleted, because at that time it is
951      * connected in the DOM tree, which we depend on.
952      */

953     void removeNode(Node JavaDoc node) {
954         if (node == null) return;
955         if (fRemoveChild == node) return;
956         
957         Node JavaDoc parent = node.getParentNode();
958         
959         if (parent == fStartContainer) {
960             int index = indexOf(node, fStartContainer);
961             if (index < fStartOffset) {
962                 fStartOffset--;
963             }
964         }
965         
966         if (parent == fEndContainer) {
967             int index = indexOf(node, fEndContainer);
968             if (index < fEndOffset) {
969                 fEndOffset--;
970             }
971         }
972         //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted
973
if (parent != fStartContainer
974         || parent != fEndContainer) {
975             if (isAncestorOf(node, fStartContainer)) {
976                 fStartContainer = parent;
977                 fStartOffset = indexOf( node, parent);
978             }
979             if (isAncestorOf(node, fEndContainer)) {
980                 fEndContainer = parent;
981                 fEndOffset = indexOf( node, parent);
982             }
983         }
984         
985     }
986         
987     //
988
// Utility functions.
989
//
990

991     // parameters for traverseContents(int)
992
//REVIST: use boolean, since there are only 2 now...
993
static final int EXTRACT_CONTENTS = 1;
994     static final int CLONE_CONTENTS = 2;
995     static final int DELETE_CONTENTS = 3;
996     
997     /**
998      * This is the master routine invoked to visit the nodes
999      * selected by this range. For each such node, different
1000     * actions are taken depending on the value of the
1001     * <code>how</code> argument.
1002     *
1003     * @param how Specifies what type of traversal is being
1004     * requested (extract, clone, or delete).
1005     * Legal values for this argument are:
1006     *
1007     * <ol>
1008     * <li><code>EXTRACT_CONTENTS</code> - will produce
1009     * a document fragment containing the range's content.
1010     * Partially selected nodes are copied, but fully
1011     * selected nodes are moved.
1012     *
1013     * <li><code>CLONE_CONTENTS</code> - will leave the
1014     * context tree of the range undisturbed, but sill
1015     * produced cloned content in a document fragment
1016     *
1017     * <li><code>DELETE_CONTENTS</code> - will delete from
1018     * the context tree of the range, all fully selected
1019     * nodes.
1020     * </ol>
1021     *
1022     * @return Returns a document fragment containing any
1023     * copied or extracted nodes. If the <code>how</code>
1024     * parameter was <code>DELETE_CONTENTS</code>, the
1025     * return value is null.
1026     */

1027    private DocumentFragment JavaDoc traverseContents( int how )
1028        throws DOMException JavaDoc
1029    {
1030        if (fStartContainer == null || fEndContainer == null) {
1031            return null; // REVIST: Throw exception?
1032
}
1033        
1034        //Check for a detached range.
1035
if( fDetach) {
1036            throw new DOMException JavaDoc(
1037                DOMException.INVALID_STATE_ERR,
1038                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_STATE_ERR", null));
1039        }
1040
1041        /*
1042          Traversal is accomplished by first determining the
1043          relationship between the endpoints of the range.
1044          For each of four significant relationships, we will
1045          delegate the traversal call to a method that
1046          can make appropriate assumptions.
1047         */

1048
1049        // case 1: same container
1050
if ( fStartContainer == fEndContainer )
1051            return traverseSameContainer( how );
1052
1053
1054        // case 2: Child C of start container is ancestor of end container
1055
// This can be quickly tested by walking the parent chain of
1056
// end container
1057
int endContainerDepth = 0;
1058        for ( Node JavaDoc c = fEndContainer, p = c.getParentNode();
1059             p != null;
1060             c = p, p = p.getParentNode())
1061        {
1062            if (p == fStartContainer)
1063                return traverseCommonStartContainer( c, how );
1064            ++endContainerDepth;
1065        }
1066
1067        // case 3: Child C of container B is ancestor of A
1068
// This can be quickly tested by walking the parent chain of A
1069
int startContainerDepth = 0;
1070        for ( Node JavaDoc c = fStartContainer, p = c.getParentNode();
1071             p != null;
1072             c = p, p = p.getParentNode())
1073        {
1074            if (p == fEndContainer)
1075                return traverseCommonEndContainer( c, how );
1076            ++startContainerDepth;
1077        }
1078
1079        // case 4: There is a common ancestor container. Find the
1080
// ancestor siblings that are children of that container.
1081
int depthDiff = startContainerDepth - endContainerDepth;
1082
1083        Node JavaDoc startNode = fStartContainer;
1084        while (depthDiff > 0) {
1085            startNode = startNode.getParentNode();
1086            depthDiff--;
1087        }
1088
1089        Node JavaDoc endNode = fEndContainer;
1090        while (depthDiff < 0) {
1091            endNode = endNode.getParentNode();
1092            depthDiff++;
1093        }
1094
1095        // ascend the ancestor hierarchy until we have a common parent.
1096
for( Node JavaDoc sp = startNode.getParentNode(), ep = endNode.getParentNode();
1097             sp!=ep;
1098             sp = sp.getParentNode(), ep = ep.getParentNode() )
1099        {
1100            startNode = sp;
1101            endNode = ep;
1102        }
1103        return traverseCommonAncestors( startNode, endNode, how );
1104    }
1105    
1106    /**
1107     * Visits the nodes selected by this range when we know
1108     * a-priori that the start and end containers are the same.
1109     * This method is invoked by the generic <code>traverse</code>
1110     * method.
1111     *
1112     * @param how Specifies what type of traversal is being
1113     * requested (extract, clone, or delete).
1114     * Legal values for this argument are:
1115     *
1116     * <ol>
1117     * <li><code>EXTRACT_CONTENTS</code> - will produce
1118     * a document fragment containing the range's content.
1119     * Partially selected nodes are copied, but fully
1120     * selected nodes are moved.
1121     *
1122     * <li><code>CLONE_CONTENTS</code> - will leave the
1123     * context tree of the range undisturbed, but sill
1124     * produced cloned content in a document fragment
1125     *
1126     * <li><code>DELETE_CONTENTS</code> - will delete from
1127     * the context tree of the range, all fully selected
1128     * nodes.
1129     * </ol>
1130     *
1131     * @return Returns a document fragment containing any
1132     * copied or extracted nodes. If the <code>how</code>
1133     * parameter was <code>DELETE_CONTENTS</code>, the
1134     * return value is null.
1135     */

1136    private DocumentFragment JavaDoc traverseSameContainer( int how )
1137    {
1138        DocumentFragment JavaDoc frag = null;
1139        if ( how!=DELETE_CONTENTS)
1140            frag = fDocument.createDocumentFragment();
1141
1142        // If selection is empty, just return the fragment
1143
if ( fStartOffset==fEndOffset )
1144            return frag;
1145
1146        // Text node needs special case handling
1147
if ( fStartContainer.getNodeType()==Node.TEXT_NODE )
1148        {
1149            // get the substring
1150
String JavaDoc s = fStartContainer.getNodeValue();
1151            String JavaDoc sub = s.substring( fStartOffset, fEndOffset );
1152
1153            // set the original text node to its new value
1154
if ( how != CLONE_CONTENTS )
1155            {
1156                ((TextImpl)fStartContainer).deleteData(fStartOffset,
1157                     fEndOffset-fStartOffset) ;
1158                // Nothing is partially selected, so collapse to start point
1159
collapse( true );
1160            }
1161            if ( how==DELETE_CONTENTS)
1162                return null;
1163            frag.appendChild( fDocument.createTextNode(sub) );
1164            return frag;
1165        }
1166
1167        // Copy nodes between the start/end offsets.
1168
Node JavaDoc n = getSelectedNode( fStartContainer, fStartOffset );
1169        int cnt = fEndOffset - fStartOffset;
1170        while( cnt > 0 )
1171        {
1172            Node JavaDoc sibling = n.getNextSibling();
1173            Node JavaDoc xferNode = traverseFullySelected( n, how );
1174            if ( frag!=null )
1175                frag.appendChild( xferNode );
1176            --cnt;
1177            n = sibling;
1178        }
1179
1180        // Nothing is partially selected, so collapse to start point
1181
if ( how != CLONE_CONTENTS )
1182            collapse( true );
1183        return frag;
1184    }
1185
1186    /**
1187     * Visits the nodes selected by this range when we know
1188     * a-priori that the start and end containers are not the
1189     * same, but the start container is an ancestor of the
1190     * end container. This method is invoked by the generic
1191     * <code>traverse</code> method.
1192     *
1193     * @param endAncestor
1194     * The ancestor of the end container that is a direct child
1195     * of the start container.
1196     *
1197     * @param how Specifies what type of traversal is being
1198     * requested (extract, clone, or delete).
1199     * Legal values for this argument are:
1200     *
1201     * <ol>
1202     * <li><code>EXTRACT_CONTENTS</code> - will produce
1203     * a document fragment containing the range's content.
1204     * Partially selected nodes are copied, but fully
1205     * selected nodes are moved.
1206     *
1207     * <li><code>CLONE_CONTENTS</code> - will leave the
1208     * context tree of the range undisturbed, but sill
1209     * produced cloned content in a document fragment
1210     *
1211     * <li><code>DELETE_CONTENTS</code> - will delete from
1212     * the context tree of the range, all fully selected
1213     * nodes.
1214     * </ol>
1215     *
1216     * @return Returns a document fragment containing any
1217     * copied or extracted nodes. If the <code>how</code>
1218     * parameter was <code>DELETE_CONTENTS</code>, the
1219     * return value is null.
1220     */

1221    private DocumentFragment JavaDoc
1222        traverseCommonStartContainer( Node JavaDoc endAncestor, int how )
1223    {
1224        DocumentFragment JavaDoc frag = null;
1225        if ( how!=DELETE_CONTENTS)
1226            frag = fDocument.createDocumentFragment();
1227        Node JavaDoc n = traverseRightBoundary( endAncestor, how );
1228        if ( frag!=null )
1229            frag.appendChild( n );
1230
1231        int endIdx = indexOf( endAncestor, fStartContainer );
1232        int cnt = endIdx - fStartOffset;
1233        if ( cnt <=0 )
1234        {
1235            // Collapse to just before the endAncestor, which
1236
// is partially selected.
1237
if ( how != CLONE_CONTENTS )
1238            {
1239                setEndBefore( endAncestor );
1240                collapse( false );
1241            }
1242            return frag;
1243        }
1244
1245        n = endAncestor.getPreviousSibling();
1246        while( cnt > 0 )
1247        {
1248            Node JavaDoc sibling = n.getPreviousSibling();
1249            Node JavaDoc xferNode = traverseFullySelected( n, how );
1250            if ( frag!=null )
1251                frag.insertBefore( xferNode, frag.getFirstChild() );
1252            --cnt;
1253            n = sibling;
1254        }
1255        // Collapse to just before the endAncestor, which
1256
// is partially selected.
1257
if ( how != CLONE_CONTENTS )
1258        {
1259            setEndBefore( endAncestor );
1260            collapse( false );
1261        }
1262        return frag;
1263    }
1264    
1265    /**
1266     * Visits the nodes selected by this range when we know
1267     * a-priori that the start and end containers are not the
1268     * same, but the end container is an ancestor of the
1269     * start container. This method is invoked by the generic
1270     * <code>traverse</code> method.
1271     *
1272     * @param startAncestor
1273     * The ancestor of the start container that is a direct
1274     * child of the end container.
1275     *
1276     * @param how Specifies what type of traversal is being
1277     * requested (extract, clone, or delete).
1278     * Legal values for this argument are:
1279     *
1280     * <ol>
1281     * <li><code>EXTRACT_CONTENTS</code> - will produce
1282     * a document fragment containing the range's content.
1283     * Partially selected nodes are copied, but fully
1284     * selected nodes are moved.
1285     *
1286     * <li><code>CLONE_CONTENTS</code> - will leave the
1287     * context tree of the range undisturbed, but sill
1288     * produced cloned content in a document fragment
1289     *
1290     * <li><code>DELETE_CONTENTS</code> - will delete from
1291     * the context tree of the range, all fully selected
1292     * nodes.
1293     * </ol>
1294     *
1295     * @return Returns a document fragment containing any
1296     * copied or extracted nodes. If the <code>how</code>
1297     * parameter was <code>DELETE_CONTENTS</code>, the
1298     * return value is null.
1299     */

1300    private DocumentFragment JavaDoc
1301        traverseCommonEndContainer( Node JavaDoc startAncestor, int how )
1302    {
1303        DocumentFragment JavaDoc frag = null;
1304        if ( how!=DELETE_CONTENTS)
1305            frag = fDocument.createDocumentFragment();
1306        Node JavaDoc n = traverseLeftBoundary( startAncestor, how );
1307        if ( frag!=null )
1308            frag.appendChild( n );
1309        int startIdx = indexOf( startAncestor, fEndContainer );
1310        ++startIdx; // Because we already traversed it....
1311

1312        int cnt = fEndOffset - startIdx;
1313        n = startAncestor.getNextSibling();
1314        while( cnt > 0 )
1315        {
1316            Node JavaDoc sibling = n.getNextSibling();
1317            Node JavaDoc xferNode = traverseFullySelected( n, how );
1318            if ( frag!=null )
1319                frag.appendChild( xferNode );
1320            --cnt;
1321            n = sibling;
1322        }
1323
1324        if ( how != CLONE_CONTENTS )
1325        {
1326            setStartAfter( startAncestor );
1327            collapse( true );
1328        }
1329
1330        return frag;
1331    }
1332
1333    /**
1334     * Visits the nodes selected by this range when we know
1335     * a-priori that the start and end containers are not
1336     * the same, and we also know that neither the start
1337     * nor end container is an ancestor of the other.
1338     * This method is invoked by
1339     * the generic <code>traverse</code> method.
1340     *
1341     * @param startAncestor
1342     * Given a common ancestor of the start and end containers,
1343     * this parameter is the ancestor (or self) of the start
1344     * container that is a direct child of the common ancestor.
1345     *
1346     * @param endAncestor
1347     * Given a common ancestor of the start and end containers,
1348     * this parameter is the ancestor (or self) of the end
1349     * container that is a direct child of the common ancestor.
1350     *
1351     * @param how Specifies what type of traversal is being
1352     * requested (extract, clone, or delete).
1353     * Legal values for this argument are:
1354     *
1355     * <ol>
1356     * <li><code>EXTRACT_CONTENTS</code> - will produce
1357     * a document fragment containing the range's content.
1358     * Partially selected nodes are copied, but fully
1359     * selected nodes are moved.
1360     *
1361     * <li><code>CLONE_CONTENTS</code> - will leave the
1362     * context tree of the range undisturbed, but sill
1363     * produced cloned content in a document fragment
1364     *
1365     * <li><code>DELETE_CONTENTS</code> - will delete from
1366     * the context tree of the range, all fully selected
1367     * nodes.
1368     * </ol>
1369     *
1370     * @return Returns a document fragment containing any
1371     * copied or extracted nodes. If the <code>how</code>
1372     * parameter was <code>DELETE_CONTENTS</code>, the
1373     * return value is null.
1374     */

1375    private DocumentFragment JavaDoc
1376        traverseCommonAncestors( Node JavaDoc startAncestor, Node JavaDoc endAncestor, int how )
1377    {
1378        DocumentFragment JavaDoc frag = null;
1379        if ( how!=DELETE_CONTENTS)
1380            frag = fDocument.createDocumentFragment();
1381
1382        Node JavaDoc n = traverseLeftBoundary( startAncestor, how );
1383        if ( frag!=null )
1384            frag.appendChild( n );
1385
1386        Node JavaDoc commonParent = startAncestor.getParentNode();
1387        int startOffset = indexOf( startAncestor, commonParent );
1388        int endOffset = indexOf( endAncestor, commonParent );
1389        ++startOffset;
1390
1391        int cnt = endOffset - startOffset;
1392        Node JavaDoc sibling = startAncestor.getNextSibling();
1393
1394        while( cnt > 0 )
1395        {
1396            Node JavaDoc nextSibling = sibling.getNextSibling();
1397            n = traverseFullySelected( sibling, how );
1398            if ( frag!=null )
1399                frag.appendChild( n );
1400            sibling = nextSibling;
1401            --cnt;
1402        }
1403
1404        n = traverseRightBoundary( endAncestor, how );
1405        if ( frag!=null )
1406            frag.appendChild( n );
1407
1408        if ( how != CLONE_CONTENTS )
1409        {
1410            setStartAfter( startAncestor );
1411            collapse( true );
1412        }
1413        return frag;
1414    }
1415
1416    /**
1417     * Traverses the "right boundary" of this range and
1418     * operates on each "boundary node" according to the
1419     * <code>how</code> parameter. It is a-priori assumed
1420     * by this method that the right boundary does
1421     * not contain the range's start container.
1422     * <p>
1423     * A "right boundary" is best visualized by thinking
1424     * of a sample tree:<pre>
1425     * A
1426     * /|\
1427     * / | \
1428     * / | \
1429     * B C D
1430     * /|\ /|\
1431     * E F G H I J
1432     * </pre>
1433     * Imagine first a range that begins between the
1434     * "E" and "F" nodes and ends between the
1435     * "I" and "J" nodes. The start container is
1436     * "B" and the end container is "D". Given this setup,
1437     * the following applies:
1438     * <p>
1439     * Partially Selected Nodes: B, D<br>
1440     * Fully Selected Nodes: F, G, C, H, I
1441     * <p>
1442     * The "right boundary" is the highest subtree node
1443     * that contains the ending container. The root of
1444     * this subtree is always partially selected.
1445     * <p>
1446     * In this example, the nodes that are traversed
1447     * as "right boundary" nodes are: H, I, and D.
1448     *
1449     * @param root The node that is the root of the "right boundary" subtree.
1450     *
1451     * @param how Specifies what type of traversal is being
1452     * requested (extract, clone, or delete).
1453     * Legal values for this argument are:
1454     *
1455     * <ol>
1456     * <li><code>EXTRACT_CONTENTS</code> - will produce
1457     * a node containing the boundaries content.
1458     * Partially selected nodes are copied, but fully
1459     * selected nodes are moved.
1460     *
1461     * <li><code>CLONE_CONTENTS</code> - will leave the
1462     * context tree of the range undisturbed, but will
1463     * produced cloned content.
1464     *
1465     * <li><code>DELETE_CONTENTS</code> - will delete from
1466     * the context tree of the range, all fully selected
1467     * nodes within the boundary.
1468     * </ol>
1469     *
1470     * @return Returns a node that is the result of visiting nodes.
1471     * If the traversal operation is
1472     * <code>DELETE_CONTENTS</code> the return value is null.
1473     */

1474    private Node JavaDoc traverseRightBoundary( Node JavaDoc root, int how )
1475    {
1476        Node JavaDoc next = getSelectedNode( fEndContainer, fEndOffset-1 );
1477        boolean isFullySelected = ( next!=fEndContainer );
1478
1479        if ( next==root )
1480            return traverseNode( next, isFullySelected, false, how );
1481
1482        Node JavaDoc parent = next.getParentNode();
1483        Node JavaDoc clonedParent = traverseNode( parent, false, false, how );
1484
1485        while( parent!=null )
1486        {
1487            while( next!=null )
1488            {
1489                Node JavaDoc prevSibling = next.getPreviousSibling();
1490                Node JavaDoc clonedChild =
1491                    traverseNode( next, isFullySelected, false, how );
1492                if ( how!=DELETE_CONTENTS )
1493                {
1494                    clonedParent.insertBefore(
1495                        clonedChild,
1496                        clonedParent.getFirstChild()
1497                    );
1498                }
1499                isFullySelected = true;
1500                next = prevSibling;
1501            }
1502            if ( parent==root )
1503                return clonedParent;
1504
1505            next = parent.getPreviousSibling();
1506            parent = parent.getParentNode();
1507            Node JavaDoc clonedGrandParent = traverseNode( parent, false, false, how );
1508            if ( how!=DELETE_CONTENTS )
1509                clonedGrandParent.appendChild( clonedParent );
1510            clonedParent = clonedGrandParent;
1511
1512        }
1513
1514        // should never occur
1515
return null;
1516    }
1517
1518    /**
1519     * Traverses the "left boundary" of this range and
1520     * operates on each "boundary node" according to the
1521     * <code>how</code> parameter. It is a-priori assumed
1522     * by this method that the left boundary does
1523     * not contain the range's end container.
1524     * <p>
1525     * A "left boundary" is best visualized by thinking
1526     * of a sample tree:<pre>
1527     *
1528     * A
1529     * /|\
1530     * / | \
1531     * / | \
1532     * B C D
1533     * /|\ /|\
1534     * E F G H I J
1535     * </pre>
1536     * Imagine first a range that begins between the
1537     * "E" and "F" nodes and ends between the
1538     * "I" and "J" nodes. The start container is
1539     * "B" and the end container is "D". Given this setup,
1540     * the following applies:
1541     * <p>
1542     * Partially Selected Nodes: B, D<br>
1543     * Fully Selected Nodes: F, G, C, H, I
1544     * <p>
1545     * The "left boundary" is the highest subtree node
1546     * that contains the starting container. The root of
1547     * this subtree is always partially selected.
1548     * <p>
1549     * In this example, the nodes that are traversed
1550     * as "left boundary" nodes are: F, G, and B.
1551     *
1552     * @param root The node that is the root of the "left boundary" subtree.
1553     *
1554     * @param how Specifies what type of traversal is being
1555     * requested (extract, clone, or delete).
1556     * Legal values for this argument are:
1557     *
1558     * <ol>
1559     * <li><code>EXTRACT_CONTENTS</code> - will produce
1560     * a node containing the boundaries content.
1561     * Partially selected nodes are copied, but fully
1562     * selected nodes are moved.
1563     *
1564     * <li><code>CLONE_CONTENTS</code> - will leave the
1565     * context tree of the range undisturbed, but will
1566     * produced cloned content.
1567     *
1568     * <li><code>DELETE_CONTENTS</code> - will delete from
1569     * the context tree of the range, all fully selected
1570     * nodes within the boundary.
1571     * </ol>
1572     *
1573     * @return Returns a node that is the result of visiting nodes.
1574     * If the traversal operation is
1575     * <code>DELETE_CONTENTS</code> the return value is null.
1576     */

1577    private Node JavaDoc traverseLeftBoundary( Node JavaDoc root, int how )
1578    {
1579        Node JavaDoc next = getSelectedNode( getStartContainer(), getStartOffset() );
1580        boolean isFullySelected = ( next!=getStartContainer() );
1581
1582        if ( next==root )
1583            return traverseNode( next, isFullySelected, true, how );
1584
1585        Node JavaDoc parent = next.getParentNode();
1586        Node JavaDoc clonedParent = traverseNode( parent, false, true, how );
1587
1588        while( parent!=null )
1589        {
1590            while( next!=null )
1591            {
1592                Node JavaDoc nextSibling = next.getNextSibling();
1593                Node JavaDoc clonedChild =
1594                    traverseNode( next, isFullySelected, true, how );
1595                if ( how!=DELETE_CONTENTS )
1596                    clonedParent.appendChild(clonedChild);
1597                isFullySelected = true;
1598                next = nextSibling;
1599            }
1600            if ( parent==root )
1601                return clonedParent;
1602
1603            next = parent.getNextSibling();
1604            parent = parent.getParentNode();
1605            Node JavaDoc clonedGrandParent = traverseNode( parent, false, true, how );
1606            if ( how!=DELETE_CONTENTS )
1607                clonedGrandParent.appendChild( clonedParent );
1608            clonedParent = clonedGrandParent;
1609
1610        }
1611
1612        // should never occur
1613
return null;
1614
1615    }
1616
1617    /**
1618     * Utility method for traversing a single node.
1619     * Does not properly handle a text node containing both the
1620     * start and end offsets. Such nodes should
1621     * have been previously detected and been routed to traverseTextNode.
1622     *
1623     * @param n The node to be traversed.
1624     *
1625     * @param isFullySelected
1626     * Set to true if the node is fully selected. Should be
1627     * false otherwise.
1628     * Note that although the DOM 2 specification says that a
1629     * text node that is boththe start and end container is not
1630     * selected, we treat it here as if it were partially
1631     * selected.
1632     *
1633     * @param isLeft Is true if we are traversing the node as part of navigating
1634     * the "left boundary" of the range. If this value is false,
1635     * it implies we are navigating the "right boundary" of the
1636     * range.
1637     *
1638     * @param how Specifies what type of traversal is being
1639     * requested (extract, clone, or delete).
1640     * Legal values for this argument are:
1641     *
1642     * <ol>
1643     * <li><code>EXTRACT_CONTENTS</code> - will simply
1644     * return the original node.
1645     *
1646     * <li><code>CLONE_CONTENTS</code> - will leave the
1647     * context tree of the range undisturbed, but will
1648     * return a cloned node.
1649     *
1650     * <li><code>DELETE_CONTENTS</code> - will delete the
1651     * node from it's parent, but will return null.
1652     * </ol>
1653     *
1654     * @return Returns a node that is the result of visiting the node.
1655     * If the traversal operation is
1656     * <code>DELETE_CONTENTS</code> the return value is null.
1657     */

1658    private Node JavaDoc traverseNode( Node JavaDoc n, boolean isFullySelected, boolean isLeft, int how )
1659    {
1660        if ( isFullySelected )
1661            return traverseFullySelected( n, how );
1662        if ( n.getNodeType()==Node.TEXT_NODE )
1663            return traverseTextNode( n, isLeft, how );
1664        return traversePartiallySelected( n, how );
1665    }
1666
1667    /**
1668     * Utility method for traversing a single node when
1669     * we know a-priori that the node if fully
1670     * selected.
1671     *
1672     * @param n The node to be traversed.
1673     *
1674     * @param how Specifies what type of traversal is being
1675     * requested (extract, clone, or delete).
1676     * Legal values for this argument are:
1677     *
1678     * <ol>
1679     * <li><code>EXTRACT_CONTENTS</code> - will simply
1680     * return the original node.
1681     *
1682     * <li><code>CLONE_CONTENTS</code> - will leave the
1683     * context tree of the range undisturbed, but will
1684     * return a cloned node.
1685     *
1686     * <li><code>DELETE_CONTENTS</code> - will delete the
1687     * node from it's parent, but will return null.
1688     * </ol>
1689     *
1690     * @return Returns a node that is the result of visiting the node.
1691     * If the traversal operation is
1692     * <code>DELETE_CONTENTS</code> the return value is null.
1693     */

1694    private Node JavaDoc traverseFullySelected( Node JavaDoc n, int how )
1695    {
1696        switch( how )
1697        {
1698        case CLONE_CONTENTS:
1699            return n.cloneNode( true );
1700        case EXTRACT_CONTENTS:
1701            if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE )
1702            {
1703                // TBD: This should be a HIERARCHY_REQUEST_ERR
1704
throw new RangeExceptionImpl(
1705            RangeException.INVALID_NODE_TYPE_ERR,
1706                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INVALID_NODE_TYPE_ERR", null));
1707            }
1708            return n;
1709        case DELETE_CONTENTS:
1710            n.getParentNode().removeChild(n);
1711            return null;
1712        }
1713        return null;
1714    }
1715
1716    /**
1717     * Utility method for traversing a single node when
1718     * we know a-priori that the node if partially
1719     * selected and is not a text node.
1720     *
1721     * @param n The node to be traversed.
1722     *
1723     * @param how Specifies what type of traversal is being
1724     * requested (extract, clone, or delete).
1725     * Legal values for this argument are:
1726     *
1727     * <ol>
1728     * <li><code>EXTRACT_CONTENTS</code> - will simply
1729     * return the original node.
1730     *
1731     * <li><code>CLONE_CONTENTS</code> - will leave the
1732     * context tree of the range undisturbed, but will
1733     * return a cloned node.
1734     *
1735     * <li><code>DELETE_CONTENTS</code> - will delete the
1736     * node from it's parent, but will return null.
1737     * </ol>
1738     *
1739     * @return Returns a node that is the result of visiting the node.
1740     * If the traversal operation is
1741     * <code>DELETE_CONTENTS</code> the return value is null.
1742     */

1743    private Node JavaDoc traversePartiallySelected( Node JavaDoc n, int how )
1744    {
1745        switch( how )
1746        {
1747        case DELETE_CONTENTS:
1748            return null;
1749        case CLONE_CONTENTS:
1750        case EXTRACT_CONTENTS:
1751            return n.cloneNode( false );
1752        }
1753        return null;
1754    }
1755
1756    /**
1757     * Utility method for traversing a text node that we know
1758     * a-priori to be on a left or right boundary of the range.
1759     * This method does not properly handle text nodes that contain
1760     * both the start and end points of the range.
1761     *
1762     * @param n The node to be traversed.
1763     *
1764     * @param isLeft Is true if we are traversing the node as part of navigating
1765     * the "left boundary" of the range. If this value is false,
1766     * it implies we are navigating the "right boundary" of the
1767     * range.
1768     *
1769     * @param how Specifies what type of traversal is being
1770     * requested (extract, clone, or delete).
1771     * Legal values for this argument are:
1772     *
1773     * <ol>
1774     * <li><code>EXTRACT_CONTENTS</code> - will simply
1775     * return the original node.
1776     *
1777     * <li><code>CLONE_CONTENTS</code> - will leave the
1778     * context tree of the range undisturbed, but will
1779     * return a cloned node.
1780     *
1781     * <li><code>DELETE_CONTENTS</code> - will delete the
1782     * node from it's parent, but will return null.
1783     * </ol>
1784     *
1785     * @return Returns a node that is the result of visiting the node.
1786     * If the traversal operation is
1787     * <code>DELETE_CONTENTS</code> the return value is null.
1788     */

1789    private Node JavaDoc traverseTextNode( Node JavaDoc n, boolean isLeft, int how )
1790    {
1791        String JavaDoc txtValue = n.getNodeValue();
1792        String JavaDoc newNodeValue;
1793        String JavaDoc oldNodeValue;
1794
1795        if ( isLeft )
1796        {
1797            int offset = getStartOffset();
1798            newNodeValue = txtValue.substring( offset );
1799            oldNodeValue = txtValue.substring( 0, offset );
1800        }
1801        else
1802        {
1803            int offset = getEndOffset();
1804            newNodeValue = txtValue.substring( 0, offset );
1805            oldNodeValue = txtValue.substring( offset );
1806        }
1807
1808        if ( how != CLONE_CONTENTS )
1809            n.setNodeValue( oldNodeValue );
1810        if ( how==DELETE_CONTENTS )
1811            return null;
1812        Node JavaDoc newNode = n.cloneNode( false );
1813        newNode.setNodeValue( newNodeValue );
1814        return newNode;
1815    }
1816
1817    void checkIndex(Node JavaDoc refNode, int offset) throws DOMException JavaDoc
1818    {
1819        if (offset < 0) {
1820            throw new DOMException JavaDoc(
1821                DOMException.INDEX_SIZE_ERR,
1822                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1823        }
1824
1825        int type = refNode.getNodeType();
1826        
1827        // If the node contains text, ensure that the
1828
// offset of the range is <= to the length of the text
1829
if (type == Node.TEXT_NODE
1830            || type == Node.CDATA_SECTION_NODE
1831            || type == Node.COMMENT_NODE
1832            || type == Node.PROCESSING_INSTRUCTION_NODE) {
1833            if (offset > refNode.getNodeValue().length()) {
1834                throw new DOMException JavaDoc(DOMException.INDEX_SIZE_ERR,
1835                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1836            }
1837        }
1838        else {
1839            // Since the node is not text, ensure that the offset
1840
// is valid with respect to the number of child nodes
1841
if (offset > refNode.getChildNodes().getLength()) {
1842            throw new DOMException JavaDoc(DOMException.INDEX_SIZE_ERR,
1843                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN, "INDEX_SIZE_ERR", null));
1844            }
1845        }
1846    }
1847
1848    /**
1849     * Given a node, calculate what the Range's root container
1850     * for that node would be.
1851     */

1852    private Node JavaDoc getRootContainer( Node JavaDoc node )
1853    {
1854        if ( node==null )
1855            return null;
1856
1857        while( node.getParentNode()!=null )
1858            node = node.getParentNode();
1859        return node;
1860    }
1861
1862    /**
1863     * Returns true IFF the given node can serve as a container
1864     * for a range's boundary points.
1865     */

1866    private boolean isLegalContainer( Node JavaDoc node )
1867    {
1868        if ( node==null )
1869            return false;
1870
1871        while( node!=null )
1872        {
1873            switch( node.getNodeType() )
1874            {
1875            case Node.ENTITY_NODE:
1876            case Node.NOTATION_NODE:
1877            case Node.DOCUMENT_TYPE_NODE:
1878                return false;
1879            }
1880            node = node.getParentNode();
1881        }
1882
1883        return true;
1884    }
1885
1886
1887    /**
1888     * Finds the root container for the given node and determines
1889     * if that root container is legal with respect to the
1890     * DOM 2 specification. At present, that means the root
1891     * container must be either an attribute, a document,
1892     * or a document fragment.
1893     */

1894    private boolean hasLegalRootContainer( Node JavaDoc node )
1895    {
1896        if ( node==null )
1897            return false;
1898
1899        Node JavaDoc rootContainer = getRootContainer( node );
1900        switch( rootContainer.getNodeType() )
1901        {
1902        case Node.ATTRIBUTE_NODE:
1903        case Node.DOCUMENT_NODE:
1904        case Node.DOCUMENT_FRAGMENT_NODE:
1905            return true;
1906        }
1907        return false;
1908    }
1909
1910    /**
1911     * Returns true IFF the given node can be contained by
1912     * a range.
1913     */

1914    private boolean isLegalContainedNode( Node JavaDoc node )
1915    {
1916        if ( node==null )
1917            return false;
1918        switch( node.getNodeType() )
1919        {
1920        case Node.DOCUMENT_NODE:
1921        case Node.DOCUMENT_FRAGMENT_NODE:
1922        case Node.ATTRIBUTE_NODE:
1923        case Node.ENTITY_NODE:
1924        case Node.NOTATION_NODE:
1925            return false;
1926        }
1927        return true;
1928    }
1929
1930    Node JavaDoc nextNode(Node JavaDoc node, boolean visitChildren) {
1931            
1932        if (node == null) return null;
1933
1934        Node JavaDoc result;
1935        if (visitChildren) {
1936            result = node.getFirstChild();
1937            if (result != null) {
1938                return result;
1939            }
1940        }
1941            
1942        // if hasSibling, return sibling
1943
result = node.getNextSibling();
1944        if (result != null) {
1945            return result;
1946        }
1947        
1948                
1949        // return parent's 1st sibling.
1950
Node JavaDoc parent = node.getParentNode();
1951        while (parent != null
1952               && parent != fDocument
1953                ) {
1954            result = parent.getNextSibling();
1955            if (result != null) {
1956                return result;
1957            } else {
1958                parent = parent.getParentNode();
1959            }
1960                            
1961        } // while (parent != null && parent != fRoot) {
1962

1963        // end of list, return null
1964
return null;
1965    }
1966    
1967    /** is a an ancestor of b ? */
1968    boolean isAncestorOf(Node JavaDoc a, Node JavaDoc b) {
1969        for (Node JavaDoc node=b; node != null; node=node.getParentNode()) {
1970            if (node == a) return true;
1971        }
1972        return false;
1973    }
1974
1975    /** what is the index of the child in the parent */
1976    int indexOf(Node JavaDoc child, Node JavaDoc parent) {
1977        if (child.getParentNode() != parent) return -1;
1978        int i = 0;
1979        for(Node JavaDoc node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) {
1980            i++;
1981        }
1982        return i;
1983    }
1984
1985    /**
1986     * Utility method to retrieve a child node by index. This method
1987     * assumes the caller is trying to find out which node is
1988     * selected by the given index. Note that if the index is
1989     * greater than the number of children, this implies that the
1990     * first node selected is the parent node itself.
1991     *
1992     * @param container A container node
1993     *
1994     * @param offset An offset within the container for which a selected node should
1995     * be computed. If the offset is less than zero, or if the offset
1996     * is greater than the number of children, the container is returned.
1997     *
1998     * @return Returns either a child node of the container or the
1999     * container itself.
2000     */

2001    private Node JavaDoc getSelectedNode( Node JavaDoc container, int offset )
2002    {
2003        if ( container.getNodeType() == Node.TEXT_NODE )
2004            return container;
2005
2006        // This case is an important convenience for
2007
// traverseRightBoundary()
2008
if ( offset<0 )
2009            return container;
2010
2011        Node JavaDoc child = container.getFirstChild();
2012        while( child!=null && offset > 0 )
2013        {
2014            --offset;
2015            child = child.getNextSibling();
2016        }
2017        if ( child!=null )
2018            return child;
2019        return container;
2020    }
2021
2022}
2023
Popular Tags