KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > apache > xerces > dom > RangeImpl


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  *
5  * Copyright (c) 1999 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  *4dorse or promote products derived from this
27  * software without prior written permission. For written
28  * permission, please contact apache@apache.org.
29  *
30  * 5. Products derived from this software may not be called "Apache",
31  * nor may "Apache" appear in their name, without prior written
32  * permission of the Apache Software Foundation.
33  *
34  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
35  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
36  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
38  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
40  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
41  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
42  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
43  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
44  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45  * SUCH DAMAGE.
46  * ====================================================================
47  *
48  * This software consists of voluntary contributions made by many
49  * individuals on behalf of the Apache Software Foundation and was
50  * originally based on software copyright (c) 1999, International
51  * Business Machines, Inc., http://www.apache.org. For more
52  * information on the Apache Software Foundation, please see
53  * <http://www.apache.org/>.
54  */

55
56 package org.enhydra.apache.xerces.dom;
57
58 import java.util.Vector JavaDoc;
59
60 import org.w3c.dom.CharacterData JavaDoc;
61 import org.w3c.dom.DOMException JavaDoc;
62 import org.w3c.dom.DocumentFragment JavaDoc;
63 import org.w3c.dom.Node JavaDoc;
64 import org.w3c.dom.ranges.Range;
65 import org.w3c.dom.ranges.RangeException;
66
67
68 /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
69  * <p> Please see the API documentation for the interface classes
70  * and use the interfaces in your client programs.
71  */

72 public class RangeImpl implements Range {
73     
74     //
75
// Constants
76
//
77

78
79     //
80
// Data
81
//
82

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

99     public RangeImpl(DocumentImpl document) {
100         fDocument = document;
101         fStartContainer = document;
102         fEndContainer = document;
103         fStartOffset = 0;
104         fEndOffset = 0;
105         fDetach = false;
106     }
107     
108     public Node JavaDoc getStartContainer() {
109         return fStartContainer;
110     }
111     
112     public int getStartOffset() {
113         return fStartOffset;
114     }
115     
116     public Node JavaDoc getEndContainer() {
117         return fEndContainer;
118     }
119     public int getEndOffset() {
120         return fEndOffset;
121     }
122     
123     public boolean getCollapsed() {
124         return (fStartContainer == fEndContainer
125              && fStartOffset == fEndOffset);
126     }
127     
128     public Node JavaDoc getCommonAncestorContainer(){
129         Vector JavaDoc startV = new Vector JavaDoc();
130         Node JavaDoc node;
131         for (node=fStartContainer; node != null;
132              node=node.getParentNode())
133         {
134             startV.addElement(node);
135         }
136         Vector JavaDoc endV = new Vector JavaDoc();
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 JavaDoc 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 JavaDoc)result;
155     }
156     
157     
158     public void setStart(Node JavaDoc refNode, int offset)
159                          throws RangeException, DOMException JavaDoc
160     {
161         if( fDetach) {
162             throw new DOMException JavaDoc(
163                 DOMException.INVALID_STATE_ERR,
164             "DOM011 Invalid state");
165         }
166         if ( !isLegalContainer(refNode)) {
167             throw new RangeExceptionImpl(
168                 RangeException.INVALID_NODE_TYPE_ERR,
169             "DOM012 Invalid node type");
170         }
171         
172         checkIndex(refNode, offset);
173         
174         fStartContainer = refNode;
175         fStartOffset = offset;
176     }
177     
178     public void setEnd(Node JavaDoc refNode, int offset)
179                        throws RangeException, DOMException JavaDoc
180     {
181         if( fDetach) {
182             throw new DOMException JavaDoc(
183                 DOMException.INVALID_STATE_ERR,
184             "DOM011 Invalid state");
185         }
186         if ( !isLegalContainer(refNode)) {
187             throw new RangeExceptionImpl(
188                 RangeException.INVALID_NODE_TYPE_ERR,
189             "DOM012 Invalid node type");
190         }
191         
192         checkIndex(refNode, offset);
193         
194         fEndContainer = refNode;
195         fEndOffset = offset;
196     }
197     public void setStartBefore(Node JavaDoc refNode)
198         throws RangeException
199     {
200         if( fDetach) {
201             throw new DOMException JavaDoc(
202                 DOMException.INVALID_STATE_ERR,
203             "DOM011 Invalid state");
204         }
205         if ( !hasLegalRootContainer(refNode) ||
206              !isLegalContainedNode(refNode) )
207         {
208             throw new RangeExceptionImpl(
209                 RangeException.INVALID_NODE_TYPE_ERR,
210             "DOM012 Invalid node type");
211         }
212         fStartContainer = refNode.getParentNode();
213         int i = 0;
214         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
215             i++;
216         }
217         fStartOffset = i-1;
218     }
219     public void setStartAfter(Node JavaDoc refNode)
220         throws RangeException
221     {
222         if( fDetach) {
223             throw new DOMException JavaDoc(
224                 DOMException.INVALID_STATE_ERR,
225             "DOM011 Invalid state");
226         }
227         if ( !hasLegalRootContainer(refNode) ||
228              !isLegalContainedNode(refNode)) {
229             throw new RangeExceptionImpl(
230                 RangeException.INVALID_NODE_TYPE_ERR,
231             "DOM012 Invalid node type");
232         }
233         fStartContainer = refNode.getParentNode();
234         int i = 0;
235         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
236             i++;
237         }
238         fStartOffset = i;
239     }
240     public void setEndBefore(Node JavaDoc refNode)
241         throws RangeException
242     {
243         if( fDetach) {
244             throw new DOMException JavaDoc(
245                 DOMException.INVALID_STATE_ERR,
246             "DOM011 Invalid state");
247         }
248         if ( !hasLegalRootContainer(refNode) ||
249              !isLegalContainedNode(refNode)) {
250             throw new RangeExceptionImpl(
251                 RangeException.INVALID_NODE_TYPE_ERR,
252             "DOM012 Invalid node type");
253         }
254         fEndContainer = refNode.getParentNode();
255         int i = 0;
256         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
257             i++;
258         }
259         fEndOffset = i-1;
260     }
261                                             
262     public void setEndAfter(Node JavaDoc refNode)
263         throws RangeException
264     {
265         if( fDetach) {
266             throw new DOMException JavaDoc(
267                 DOMException.INVALID_STATE_ERR,
268             "DOM011 Invalid state");
269         }
270         if ( !hasLegalRootContainer(refNode) ||
271              !isLegalContainedNode(refNode)) {
272             throw new RangeExceptionImpl(
273                 RangeException.INVALID_NODE_TYPE_ERR,
274             "DOM012 Invalid node type");
275         }
276         fEndContainer = refNode.getParentNode();
277         int i = 0;
278         for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
279             i++;
280         }
281         fEndOffset = i;
282     }
283     public void collapse(boolean toStart) {
284         
285         if( fDetach) {
286             throw new DOMException JavaDoc(
287                 DOMException.INVALID_STATE_ERR,
288             "DOM011 Invalid state");
289         }
290         
291         if (toStart) {
292             fEndContainer = fStartContainer;
293             fEndOffset = fStartOffset;
294         } else {
295             fStartContainer = fEndContainer;
296             fStartOffset = fEndOffset;
297         }
298     }
299     
300     public void selectNode(Node JavaDoc refNode)
301         throws RangeException
302     {
303         if( fDetach) {
304             throw new DOMException JavaDoc(
305                 DOMException.INVALID_STATE_ERR,
306             "DOM011 Invalid state");
307         }
308         if ( !isLegalContainer( refNode.getParentNode() ) ||
309              !isLegalContainedNode( refNode ) ) {
310             throw new RangeExceptionImpl(
311                 RangeException.INVALID_NODE_TYPE_ERR,
312             "DOM012 Invalid node type");
313         }
314         Node JavaDoc parent = refNode.getParentNode();
315         if (parent != null ) // REVIST: what to do if it IS null?
316
{
317             fStartContainer = parent;
318             fEndContainer = parent;
319             int i = 0;
320             for (Node JavaDoc n = refNode; n!=null; n = n.getPreviousSibling()) {
321                 i++;
322             }
323             fStartOffset = i-1;
324             fEndOffset = fStartOffset+1;
325         }
326     }
327         
328     public void selectNodeContents(Node JavaDoc refNode)
329         throws RangeException
330     {
331         if( fDetach) {
332             throw new DOMException JavaDoc(
333                 DOMException.INVALID_STATE_ERR,
334             "DOM011 Invalid state");
335         }
336         if ( !isLegalContainer(refNode)) {
337             throw new RangeExceptionImpl(
338                 RangeException.INVALID_NODE_TYPE_ERR,
339             "DOM012 Invalid node type");
340         }
341         fStartContainer = refNode;
342         fEndContainer = refNode;
343         Node JavaDoc first = refNode.getFirstChild();
344         fStartOffset = 0;
345         if (first == null) {
346             fEndOffset = 0;
347         } else {
348             int i = 0;
349             for (Node JavaDoc n = first; n!=null; n = n.getNextSibling()) {
350                 i++;
351             }
352             fEndOffset = i;
353         }
354         
355     }
356
357     public short compareBoundaryPoints(short how, Range sourceRange)
358         throws DOMException JavaDoc
359     {
360         if( fDetach) {
361             throw new DOMException JavaDoc(
362                 DOMException.INVALID_STATE_ERR,
363             "DOM011 Invalid state");
364         }
365        
366         Node JavaDoc endPointA;
367         Node JavaDoc endPointB;
368         int offsetA;
369         int offsetB;
370         
371         if (how == START_TO_START) {
372             endPointA = sourceRange.getStartContainer();
373             endPointB = fStartContainer;
374             offsetA = sourceRange.getStartOffset();
375             offsetB = fStartOffset;
376         } else
377         if (how == START_TO_END) {
378             endPointA = sourceRange.getStartContainer();
379             endPointB = fEndContainer;
380             offsetA = sourceRange.getStartOffset();
381             offsetB = fEndOffset;
382         } else
383         if (how == END_TO_START) {
384             endPointA = sourceRange.getEndContainer();
385             endPointB = fStartContainer;
386             offsetA = sourceRange.getEndOffset();
387             offsetB = fStartOffset;
388         } else {
389             endPointA = sourceRange.getEndContainer();
390             endPointB = fEndContainer;
391             offsetA = sourceRange.getEndOffset();
392             offsetB = fEndOffset;
393         }
394
395         // The DOM Spec outlines four cases that need to be tested
396
// to compare two range boundary points:
397
// case 1: same container
398
// case 2: Child C of container A is ancestor of B
399
// case 3: Child C of container B is ancestor of A
400
// case 4: preorder traversal of context tree.
401

402         // case 1: same container
403
if (endPointA == endPointB) {
404             if (offsetA < offsetB) return 1;
405             if (offsetA == offsetB) return 0;
406             return -1;
407         }
408         // case 2: Child C of container A is ancestor of B
409
// This can be quickly tested by walking the parent chain of B
410
for ( Node JavaDoc c = endPointB, p = c.getParentNode();
411              p != null;
412              c = p, p = p.getParentNode())
413         {
414             if (p == endPointA) {
415                 int index = indexOf(c, endPointA);
416                 if (offsetA <= index) return 1;
417                 return -1;
418             }
419         }
420
421         // case 3: Child C of container B is ancestor of A
422
// This can be quickly tested by walking the parent chain of A
423
for ( Node JavaDoc c = endPointA, p = c.getParentNode();
424              p != null;
425              c = p, p = p.getParentNode())
426         {
427             if (p == endPointB) {
428                 int index = indexOf(c, endPointB);
429                 if (index < offsetB) return 1;
430                 return -1;
431             }
432         }
433
434         // case 4: preorder traversal of context tree.
435
// Instead of literally walking the context tree in pre-order,
436
// we use relative node depth walking which is usually faster
437

438         int depthDiff = 0;
439         for ( Node JavaDoc n = endPointA; n != null; n = n.getParentNode() )
440             depthDiff++;
441         for ( Node JavaDoc n = endPointB; n != null; n = n.getParentNode() )
442             depthDiff--;
443         while (depthDiff > 0) {
444             endPointA = endPointA.getParentNode();
445             depthDiff--;
446         }
447         while (depthDiff < 0) {
448             endPointB = endPointB.getParentNode();
449             depthDiff++;
450         }
451         for (Node JavaDoc pA = endPointA.getParentNode(),
452              pB = endPointB.getParentNode();
453              pA != pB;
454              pA = pA.getParentNode(), pB = pB.getParentNode() )
455         {
456             endPointA = pA;
457             endPointB = pB;
458         }
459         for ( Node JavaDoc n = endPointA.getNextSibling();
460              n != null;
461              n = n.getNextSibling() )
462         {
463             if (n == endPointB) {
464                 return 1;
465             }
466         }
467         return -1;
468     }
469     
470     public void deleteContents()
471         throws DOMException JavaDoc
472     {
473         traverseContents(DELETE_CONTENTS);
474     }
475         
476     public DocumentFragment JavaDoc extractContents()
477         throws DOMException JavaDoc
478     {
479         return traverseContents(EXTRACT_CONTENTS);
480     }
481         
482     public DocumentFragment JavaDoc cloneContents()
483         throws DOMException JavaDoc
484     {
485         return traverseContents(CLONE_CONTENTS);
486     }
487     
488     public void insertNode(Node JavaDoc newNode)
489         throws DOMException JavaDoc, RangeException
490     {
491         if ( newNode == null ) return; //throw exception?
492

493         if( fDetach) {
494             throw new DOMException JavaDoc(
495                 DOMException.INVALID_STATE_ERR,
496             "DOM011 Invalid state");
497         }
498         if ( fDocument != newNode.getOwnerDocument() ) {
499             throw new DOMException JavaDoc(DOMException.WRONG_DOCUMENT_ERR,"DOM004 Wrong document");
500         }
501        
502         int type = newNode.getNodeType();
503         if (type == Node.ATTRIBUTE_NODE
504             || type == Node.ENTITY_NODE
505             || type == Node.NOTATION_NODE
506             || type == Node.DOCUMENT_NODE)
507         {
508             throw new RangeExceptionImpl(
509                 RangeException.INVALID_NODE_TYPE_ERR,
510             "DOM012 Invalid node type");
511         }
512         Node JavaDoc cloneCurrent;
513         Node JavaDoc current;
514         int currentChildren = 0;
515
516         //boolean MULTIPLE_MODE = false;
517
if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
518         
519             Node JavaDoc parent = fStartContainer.getParentNode();
520             currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
521
// split text node: results is 3 nodes..
522
cloneCurrent = fStartContainer.cloneNode(false);
523             ((TextImpl)cloneCurrent).setNodeValueInternal(
524                     (cloneCurrent.getNodeValue()).substring(fStartOffset));
525             ((TextImpl)fStartContainer).setNodeValueInternal(
526                     (fStartContainer.getNodeValue()).substring(0,fStartOffset));
527             Node JavaDoc next = fStartContainer.getNextSibling();
528             if (next != null) {
529                     if (parent != null) {
530                         parent.insertBefore(newNode, next);
531                         parent.insertBefore(cloneCurrent, next);
532                     }
533             } else {
534                     if (parent != null) {
535                         parent.appendChild(newNode);
536                         parent.appendChild(cloneCurrent);
537                     }
538             }
539              //update ranges after the insertion
540
if ( fEndContainer == fStartContainer) {
541                   fEndContainer = cloneCurrent; //endContainer is the new Node created
542
fEndOffset -= fStartOffset;
543              }
544              else if ( fEndContainer == parent ) { //endContainer was not a text Node.
545
//endOffset + = number_of_children_added
546
fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
547              }
548
549              // signal other Ranges to update their start/end containers/offsets
550
signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
551                 
552              
553         } else { // ! TEXT_NODE
554
if ( fEndContainer == fStartContainer ) //need to remember number of kids
555
currentChildren= fEndContainer.getChildNodes().getLength();
556
557             current = fStartContainer.getFirstChild();
558             int i = 0;
559             for(i = 0; i < fStartOffset && current != null; i++) {
560                 current=current.getNextSibling();
561             }
562             if (current != null) {
563                 fStartContainer.insertBefore(newNode, current);
564             } else {
565                 fStartContainer.appendChild(newNode);
566             }
567             //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
568
// insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
569
if ( fEndContainer == fStartContainer ) { //update fEndOffset
570
fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren);
571             }
572
573         }
574     }
575     
576     public void surroundContents(Node JavaDoc newParent)
577         throws DOMException JavaDoc, RangeException
578     {
579         if (newParent==null) return;
580         
581         if( fDetach) {
582             throw new DOMException JavaDoc(
583                 DOMException.INVALID_STATE_ERR,
584             "DOM011 Invalid state");
585         }
586         int type = newParent.getNodeType();
587         if (type == Node.ATTRIBUTE_NODE
588             || type == Node.ENTITY_NODE
589             || type == Node.NOTATION_NODE
590             || type == Node.DOCUMENT_TYPE_NODE
591             || type == Node.DOCUMENT_NODE
592             || type == Node.DOCUMENT_FRAGMENT_NODE)
593         {
594             throw new RangeExceptionImpl(
595                 RangeException.INVALID_NODE_TYPE_ERR,
596             "DOM012 Invalid node type");
597         }
598         
599         Node JavaDoc root = getCommonAncestorContainer();
600         
601         Node JavaDoc realStart = fStartContainer;
602         Node JavaDoc realEnd = fEndContainer;
603         if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
604             realStart = fStartContainer.getParentNode();
605         }
606         if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
607             realEnd = fEndContainer.getParentNode();
608         }
609             
610         if (realStart != realEnd) {
611             throw new RangeExceptionImpl(
612             RangeException.BAD_BOUNDARYPOINTS_ERR,
613             "DOM013 Bad boundary points");
614         }
615
616         DocumentFragment JavaDoc frag = extractContents();
617         insertNode(newParent);
618         newParent.appendChild(frag);
619         selectNode(newParent);
620     }
621         
622     public Range cloneRange(){
623         if( fDetach) {
624             throw new DOMException JavaDoc(
625                 DOMException.INVALID_STATE_ERR,
626             "DOM011 Invalid state");
627         }
628         
629         Range range = fDocument.createRange();
630         range.setStart(fStartContainer, fStartOffset);
631         range.setEnd(fEndContainer, fEndOffset);
632         return range;
633     }
634     
635     public String JavaDoc toString(){
636         if( fDetach) {
637             throw new DOMException JavaDoc(
638                 DOMException.INVALID_STATE_ERR,
639             "DOM011 Invalid state");
640         }
641         
642         Node JavaDoc node = fStartContainer;
643         Node JavaDoc stopNode = fEndContainer;
644         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
645         if (fStartContainer.getNodeType() == Node.TEXT_NODE
646          || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE
647         ) {
648             if (fStartContainer == fEndContainer) {
649                 sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset));
650                 return sb.toString();
651             }
652             sb.append(fStartContainer.getNodeValue().substring(fStartOffset));
653             node=nextNode (node,true); //fEndContainer!=fStartContainer
654

655         }
656         else { //fStartContainer is not a TextNode
657
node=node.getFirstChild();
658             if (fStartOffset>0) { //find a first node within a range, specified by fStartOffset
659
int counter=0;
660                while (counter<fStartOffset && node!=null) {
661                    node=node.getNextSibling();
662                    counter++;
663                }
664             }
665             if (node == null) {
666                    node = nextNode(fStartContainer,false);
667             }
668         }
669         if ( fEndContainer.getNodeType()!= Node.TEXT_NODE &&
670              fEndContainer.getNodeType()!= Node.CDATA_SECTION_NODE ){
671              int i=fEndOffset;
672              stopNode = fEndContainer.getFirstChild();
673              while( i>0 && stopNode!=null ){
674                  --i;
675                  stopNode = stopNode.getNextSibling();
676              }
677              if ( stopNode == null )
678                  stopNode = nextNode( fEndContainer, false );
679          }
680          while (node != stopNode) { //look into all kids of the Range
681
if (node == null) break;
682              if (node.getNodeType() == Node.TEXT_NODE
683              || node.getNodeType() == Node.CDATA_SECTION_NODE) {
684                  sb.append(node.getNodeValue());
685              }
686
687              node = nextNode(node, true);
688          }
689
690         if (fEndContainer.getNodeType() == Node.TEXT_NODE
691          || fEndContainer.getNodeType() == Node.CDATA_SECTION_NODE) {
692             sb.append(fEndContainer.getNodeValue().substring(0,fEndOffset));
693         }
694         return sb.toString();
695     }
696     
697     public void detach() {
698         fDetach = true;
699         fDocument.removeRange(this);
700     }
701     
702     //
703
// Mutation functions
704
//
705

706     /** Signal other Ranges to update their start/end
707      * containers/offsets. The data has already been split
708      * into the two Nodes.
709      */

710     void signalSplitData(Node JavaDoc node, Node JavaDoc newNode, int offset) {
711         fSplitNode = node;
712         // notify document
713
fDocument.splitData(node, newNode, offset);
714         fSplitNode = null;
715     }
716     
717     /** Fix up this Range if another Range has split a Text Node
718      * into 2 Nodes.
719      */

720     void receiveSplitData(Node JavaDoc node, Node JavaDoc newNode, int offset) {
721         if (node == null || newNode == null) return;
722         if (fSplitNode == node) return;
723         
724         if (node == fStartContainer
725         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
726             if (fStartOffset > offset) {
727                 fStartOffset = fStartOffset - offset;
728                 fStartContainer = newNode;
729             }
730         }
731         if (node == fEndContainer
732         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
733             if (fEndOffset > offset) {
734                 fEndOffset = fEndOffset-offset;
735                 fEndContainer = newNode;
736             }
737         }
738         
739     }
740    
741     /** This function inserts text into a Node and invokes
742      * a method to fix-up all other Ranges.
743      */

744     void deleteData(CharacterData JavaDoc node, int offset, int count) {
745         fDeleteNode = node;
746         node.deleteData( offset, count);
747         fDeleteNode = null;
748     }
749     
750     
751     /** This function is called from DOM.
752      * The text has already beeen inserted.
753      * Fix-up any offsets.
754      */

755     void receiveDeletedText(Node JavaDoc node, int offset, int count) {
756         if (node == null) return;
757         if (fDeleteNode == node) return;
758         if (node == fStartContainer
759         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
760             if (fStartOffset > offset+count) {
761                 fStartOffset = offset+(fStartOffset-(offset+count));
762             } else
763             if (fStartOffset > offset) {
764                 fStartOffset = offset;
765             }
766         }
767         if (node == fEndContainer
768         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
769             if (fEndOffset > offset+count) {
770                 fEndOffset = offset+(fEndOffset-(offset+count));
771             } else
772             if (fEndOffset > offset) {
773                 fEndOffset = offset;
774             }
775         }
776         
777     }
778    
779     /** This function inserts text into a Node and invokes
780      * a method to fix-up all other Ranges.
781      */

782     void insertData(CharacterData JavaDoc node, int index, String JavaDoc insert) {
783         fInsertNode = node;
784         node.insertData( index, insert);
785         fInsertNode = null;
786     }
787     
788     
789     /** This function is called from DOM.
790      * The text has already beeen inserted.
791      * Fix-up any offsets.
792      */

793     void receiveInsertedText(Node JavaDoc node, int index, int len) {
794         if (node == null) return;
795         if (fInsertNode == node) return;
796         if (node == fStartContainer
797         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
798             if (index < fStartOffset) {
799                 fStartOffset = fStartOffset+len;
800             }
801         }
802         if (node == fEndContainer
803         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
804             if (index < fEndOffset) {
805                 fEndOffset = fEndOffset+len;
806             }
807         }
808         
809     }
810    
811     /** This function is called from DOM.
812      * The text has already beeen replaced.
813      * Fix-up any offsets.
814      */

815     void receiveReplacedText(Node JavaDoc node) {
816         if (node == null) return;
817         if (node == fStartContainer
818         && fStartContainer.getNodeType() == Node.TEXT_NODE) {
819             fStartOffset = 0;
820         }
821         if (node == fEndContainer
822         && fEndContainer.getNodeType() == Node.TEXT_NODE) {
823             fEndOffset = 0;
824         }
825         
826     }
827     
828     /** This function is called from the DOM.
829      * This node has already been inserted into the DOM.
830      * Fix-up any offsets.
831      */

832     public void insertedNodeFromDOM(Node JavaDoc node) {
833         if (node == null) return;
834         if (fInsertNode == node) return;
835         
836         Node JavaDoc parent = node.getParentNode();
837         
838         if (parent == fStartContainer) {
839             int index = indexOf(node, fStartContainer);
840             if (index < fStartOffset) {
841                 fStartOffset++;
842             }
843         }
844         
845         if (parent == fEndContainer) {
846             int index = indexOf(node, fEndContainer);
847             if (index < fEndOffset) {
848                 fEndOffset++;
849             }
850         }
851         
852     }
853     
854     /** This function is called within Range
855      * instead of Node.removeChild,
856      * so that the range can remember that it is actively
857      * removing this child.
858      */

859      
860     Node JavaDoc fRemoveChild = null;
861     Node JavaDoc removeChild(Node JavaDoc parent, Node JavaDoc child) {
862         fRemoveChild = child;
863         Node JavaDoc n = parent.removeChild(child);
864         fRemoveChild = null;
865         return n;
866     }
867     
868     /** This function must be called by the DOM _BEFORE_
869      * a node is deleted, because at that time it is
870      * connected in the DOM tree, which we depend on.
871      */

872     void removeNode(Node JavaDoc node) {
873         if (node == null) return;
874         if (fRemoveChild == node) return;
875         
876         Node JavaDoc parent = node.getParentNode();
877         
878         if (parent == fStartContainer) {
879             int index = indexOf(node, fStartContainer);
880             if (index < fStartOffset) {
881                 fStartOffset--;
882             }
883         }
884         
885         if (parent == fEndContainer) {
886             int index = indexOf(node, fEndContainer);
887             if (index < fEndOffset) {
888                 fEndOffset--;
889             }
890         }
891         //startContainer or endContainer or both is/are the ancestor(s) of the Node to be deleted
892
if (parent != fStartContainer
893         || parent != fEndContainer) {
894             if (isAncestorOf(node, fStartContainer)) {
895                 fStartContainer = parent;
896                 fStartOffset = indexOf( node, parent);
897             }
898             if (isAncestorOf(node, fEndContainer)) {
899                 fEndContainer = parent;
900                 fEndOffset = indexOf( node, parent);
901             }
902         }
903         
904     }
905         
906     //
907
// Utility functions.
908
//
909

910     // parameters for traverseContents(int)
911
//REVIST: use boolean, since there are only 2 now...
912
static final int EXTRACT_CONTENTS = 1;
913     static final int CLONE_CONTENTS = 2;
914     static final int DELETE_CONTENTS = 3;
915     
916     /**
917      * This is the master routine invoked to visit the nodes
918      * selected by this range. For each such node, different
919      * actions are taken depending on the value of the
920      * <code>how</code> argument.
921      *
922      * @param how Specifies what type of traversal is being
923      * requested (extract, clone, or delete).
924      * Legal values for this argument are:
925      *
926      * <ol>
927      * <li><code>EXTRACT_CONTENTS</code> - will produce
928      * a document fragment containing the range's content.
929      * Partially selected nodes are copied, but fully
930      * selected nodes are moved.
931      *
932      * <li><code>CLONE_CONTENTS</code> - will leave the
933      * context tree of the range undisturbed, but sill
934      * produced cloned content in a document fragment
935      *
936      * <li><code>DELETE_CONTENTS</code> - will delete from
937      * the context tree of the range, all fully selected
938      * nodes.
939      * </ol>
940      *
941      * @return Returns a document fragment containing any
942      * copied or extracted nodes. If the <code>how</code>
943      * parameter was <code>DELETE_CONTENTS</code>, the
944      * return value is null.
945      */

946     private DocumentFragment JavaDoc traverseContents( int how )
947         throws DOMException JavaDoc
948     {
949         if (fStartContainer == null || fEndContainer == null) {
950             return null; // REVIST: Throw exception?
951
}
952         
953         //Check for a detached range.
954
if( fDetach) {
955             throw new DOMException JavaDoc(
956                 DOMException.INVALID_STATE_ERR,
957             "DOM011 Invalid state");
958         }
959
960         /*
961           Traversal is accomplished by first determining the
962           relationship between the endpoints of the range.
963           For each of four significant relationships, we will
964           delegate the traversal call to a method that
965           can make appropriate assumptions.
966          */

967
968         // case 1: same container
969
if ( fStartContainer == fEndContainer )
970             return traverseSameContainer( how );
971
972
973         // case 2: Child C of start container is ancestor of end container
974
// This can be quickly tested by walking the parent chain of
975
// end container
976
int endContainerDepth = 0;
977         for ( Node JavaDoc c = fEndContainer, p = c.getParentNode();
978              p != null;
979              c = p, p = p.getParentNode())
980         {
981             if (p == fStartContainer)
982                 return traverseCommonStartContainer( c, how );
983             ++endContainerDepth;
984         }
985
986         // case 3: Child C of container B is ancestor of A
987
// This can be quickly tested by walking the parent chain of A
988
int startContainerDepth = 0;
989         for ( Node JavaDoc c = fStartContainer, p = c.getParentNode();
990              p != null;
991              c = p, p = p.getParentNode())
992         {
993             if (p == fEndContainer)
994                 return traverseCommonEndContainer( c, how );
995             ++startContainerDepth;
996         }
997
998         // case 4: There is a common ancestor container. Find the
999
// ancestor siblings that are children of that container.
1000
int depthDiff = startContainerDepth - endContainerDepth;
1001
1002        Node JavaDoc startNode = fStartContainer;
1003        while (depthDiff > 0) {
1004            startNode = startNode.getParentNode();
1005            depthDiff--;
1006        }
1007
1008        Node JavaDoc endNode = fEndContainer;
1009        while (depthDiff < 0) {
1010            endNode = endNode.getParentNode();
1011            depthDiff++;
1012        }
1013
1014        // ascend the ancestor hierarchy until we have a common parent.
1015
for( Node JavaDoc sp = startNode.getParentNode(), ep = endNode.getParentNode();
1016             sp!=ep;
1017             sp = sp.getParentNode(), ep = ep.getParentNode() )
1018        {
1019            startNode = sp;
1020            endNode = ep;
1021        }
1022        return traverseCommonAncestors( startNode, endNode, how );
1023    }
1024    
1025    /**
1026     * Visits the nodes selected by this range when we know
1027     * a-priori that the start and end containers are the same.
1028     * This method is invoked by the generic <code>traverse</code>
1029     * method.
1030     *
1031     * @param how Specifies what type of traversal is being
1032     * requested (extract, clone, or delete).
1033     * Legal values for this argument are:
1034     *
1035     * <ol>
1036     * <li><code>EXTRACT_CONTENTS</code> - will produce
1037     * a document fragment containing the range's content.
1038     * Partially selected nodes are copied, but fully
1039     * selected nodes are moved.
1040     *
1041     * <li><code>CLONE_CONTENTS</code> - will leave the
1042     * context tree of the range undisturbed, but sill
1043     * produced cloned content in a document fragment
1044     *
1045     * <li><code>DELETE_CONTENTS</code> - will delete from
1046     * the context tree of the range, all fully selected
1047     * nodes.
1048     * </ol>
1049     *
1050     * @return Returns a document fragment containing any
1051     * copied or extracted nodes. If the <code>how</code>
1052     * parameter was <code>DELETE_CONTENTS</code>, the
1053     * return value is null.
1054     */

1055    private DocumentFragment JavaDoc traverseSameContainer( int how )
1056    {
1057        DocumentFragment JavaDoc frag = null;
1058        if ( how!=DELETE_CONTENTS)
1059            frag = fDocument.createDocumentFragment();
1060
1061        // If selection is empty, just return the fragment
1062
if ( fStartOffset==fEndOffset )
1063            return frag;
1064
1065        // Text node needs special case handling
1066
if ( fStartContainer.getNodeType()==Node.TEXT_NODE )
1067        {
1068            // get the substring
1069
String JavaDoc s = fStartContainer.getNodeValue();
1070            String JavaDoc sub = s.substring( fStartOffset, fEndOffset );
1071
1072            // set the original text node to its new value
1073
if ( how != CLONE_CONTENTS )
1074            {
1075                fStartContainer.setNodeValue(
1076                    s.substring(0, fStartOffset ) +
1077                    s.substring(fEndOffset)
1078                );
1079
1080                // Nothing is partially selected, so collapse to start point
1081
collapse( true );
1082            }
1083            if ( how==DELETE_CONTENTS)
1084                return null;
1085            frag.appendChild( fDocument.createTextNode(sub) );
1086            return frag;
1087        }
1088
1089        // Copy nodes between the start/end offsets.
1090
Node JavaDoc n = getSelectedNode( fStartContainer, fStartOffset );
1091        int cnt = fEndOffset - fStartOffset;
1092        while( cnt > 0 )
1093        {
1094            Node JavaDoc sibling = n.getNextSibling();
1095            Node JavaDoc xferNode = traverseFullySelected( n, how );
1096            if ( frag!=null )
1097                frag.appendChild( xferNode );
1098            --cnt;
1099            n = sibling;
1100        }
1101
1102        // Nothing is partially selected, so collapse to start point
1103
if ( how != CLONE_CONTENTS )
1104            collapse( true );
1105        return frag;
1106    }
1107
1108    /**
1109     * Visits the nodes selected by this range when we know
1110     * a-priori that the start and end containers are not the
1111     * same, but the start container is an ancestor of the
1112     * end container. This method is invoked by the generic
1113     * <code>traverse</code> method.
1114     *
1115     * @param endAncestor
1116     * The ancestor of the end container that is a direct child
1117     * of the start container.
1118     *
1119     * @param how Specifies what type of traversal is being
1120     * requested (extract, clone, or delete).
1121     * Legal values for this argument are:
1122     *
1123     * <ol>
1124     * <li><code>EXTRACT_CONTENTS</code> - will produce
1125     * a document fragment containing the range's content.
1126     * Partially selected nodes are copied, but fully
1127     * selected nodes are moved.
1128     *
1129     * <li><code>CLONE_CONTENTS</code> - will leave the
1130     * context tree of the range undisturbed, but sill
1131     * produced cloned content in a document fragment
1132     *
1133     * <li><code>DELETE_CONTENTS</code> - will delete from
1134     * the context tree of the range, all fully selected
1135     * nodes.
1136     * </ol>
1137     *
1138     * @return Returns a document fragment containing any
1139     * copied or extracted nodes. If the <code>how</code>
1140     * parameter was <code>DELETE_CONTENTS</code>, the
1141     * return value is null.
1142     */

1143    private DocumentFragment JavaDoc
1144        traverseCommonStartContainer( Node JavaDoc endAncestor, int how )
1145    {
1146        DocumentFragment JavaDoc frag = null;
1147        if ( how!=DELETE_CONTENTS)
1148            frag = fDocument.createDocumentFragment();
1149        Node JavaDoc n = traverseRightBoundary( endAncestor, how );
1150        if ( frag!=null )
1151            frag.appendChild( n );
1152
1153        int endIdx = indexOf( endAncestor, fStartContainer );
1154        int cnt = endIdx - fStartOffset;
1155        if ( cnt <=0 )
1156        {
1157            // Collapse to just before the endAncestor, which
1158
// is partially selected.
1159
if ( how != CLONE_CONTENTS )
1160            {
1161                setEndBefore( endAncestor );
1162                collapse( false );
1163            }
1164            return frag;
1165        }
1166
1167        n = endAncestor.getPreviousSibling();
1168        while( cnt > 0 )
1169        {
1170            Node JavaDoc sibling = n.getPreviousSibling();
1171            Node JavaDoc xferNode = traverseFullySelected( n, how );
1172            if ( frag!=null )
1173                frag.insertBefore( xferNode, frag.getFirstChild() );
1174            --cnt;
1175            n = sibling;
1176        }
1177        // Collapse to just before the endAncestor, which
1178
// is partially selected.
1179
if ( how != CLONE_CONTENTS )
1180        {
1181            setEndBefore( endAncestor );
1182            collapse( false );
1183        }
1184        return frag;
1185    }
1186    
1187    /**
1188     * Visits the nodes selected by this range when we know
1189     * a-priori that the start and end containers are not the
1190     * same, but the end container is an ancestor of the
1191     * start container. This method is invoked by the generic
1192     * <code>traverse</code> method.
1193     *
1194     * @param startAncestor
1195     * The ancestor of the start container that is a direct
1196     * child of the end container.
1197     *
1198     * @param how Specifies what type of traversal is being
1199     * requested (extract, clone, or delete).
1200     * Legal values for this argument are:
1201     *
1202     * <ol>
1203     * <li><code>EXTRACT_CONTENTS</code> - will produce
1204     * a document fragment containing the range's content.
1205     * Partially selected nodes are copied, but fully
1206     * selected nodes are moved.
1207     *
1208     * <li><code>CLONE_CONTENTS</code> - will leave the
1209     * context tree of the range undisturbed, but sill
1210     * produced cloned content in a document fragment
1211     *
1212     * <li><code>DELETE_CONTENTS</code> - will delete from
1213     * the context tree of the range, all fully selected
1214     * nodes.
1215     * </ol>
1216     *
1217     * @return Returns a document fragment containing any
1218     * copied or extracted nodes. If the <code>how</code>
1219     * parameter was <code>DELETE_CONTENTS</code>, the
1220     * return value is null.
1221     */

1222    private DocumentFragment JavaDoc
1223        traverseCommonEndContainer( Node JavaDoc startAncestor, int how )
1224    {
1225        DocumentFragment JavaDoc frag = null;
1226        if ( how!=DELETE_CONTENTS)
1227            frag = fDocument.createDocumentFragment();
1228        Node JavaDoc n = traverseLeftBoundary( startAncestor, how );
1229        if ( frag!=null )
1230            frag.appendChild( n );
1231        int startIdx = indexOf( startAncestor, fEndContainer );
1232        ++startIdx; // Because we already traversed it....
1233

1234        int cnt = fEndOffset - startIdx;
1235        n = startAncestor.getNextSibling();
1236        while( cnt > 0 )
1237        {
1238            Node JavaDoc sibling = n.getNextSibling();
1239            Node JavaDoc xferNode = traverseFullySelected( n, how );
1240            if ( frag!=null )
1241                frag.appendChild( xferNode );
1242            --cnt;
1243            n = sibling;
1244        }
1245
1246        if ( how != CLONE_CONTENTS )
1247        {
1248            setStartAfter( startAncestor );
1249            collapse( true );
1250        }
1251
1252        return frag;
1253    }
1254
1255    /**
1256     * Visits the nodes selected by this range when we know
1257     * a-priori that the start and end containers are not
1258     * the same, and we also know that neither the start
1259     * nor end container is an ancestor of the other.
1260     * This method is invoked by
1261     * the generic <code>traverse</code> method.
1262     *
1263     * @param startAncestor
1264     * Given a common ancestor of the start and end containers,
1265     * this parameter is the ancestor (or self) of the start
1266     * container that is a direct child of the common ancestor.
1267     *
1268     * @param endAncestor
1269     * Given a common ancestor of the start and end containers,
1270     * this parameter is the ancestor (or self) of the end
1271     * container that is a direct child of the common ancestor.
1272     *
1273     * @param how Specifies what type of traversal is being
1274     * requested (extract, clone, or delete).
1275     * Legal values for this argument are:
1276     *
1277     * <ol>
1278     * <li><code>EXTRACT_CONTENTS</code> - will produce
1279     * a document fragment containing the range's content.
1280     * Partially selected nodes are copied, but fully
1281     * selected nodes are moved.
1282     *
1283     * <li><code>CLONE_CONTENTS</code> - will leave the
1284     * context tree of the range undisturbed, but sill
1285     * produced cloned content in a document fragment
1286     *
1287     * <li><code>DELETE_CONTENTS</code> - will delete from
1288     * the context tree of the range, all fully selected
1289     * nodes.
1290     * </ol>
1291     *
1292     * @return Returns a document fragment containing any
1293     * copied or extracted nodes. If the <code>how</code>
1294     * parameter was <code>DELETE_CONTENTS</code>, the
1295     * return value is null.
1296     */

1297    private DocumentFragment JavaDoc
1298        traverseCommonAncestors( Node JavaDoc startAncestor, Node JavaDoc endAncestor, int how )
1299    {
1300        DocumentFragment JavaDoc frag = null;
1301        if ( how!=DELETE_CONTENTS)
1302            frag = fDocument.createDocumentFragment();
1303
1304        Node JavaDoc n = traverseLeftBoundary( startAncestor, how );
1305        if ( frag!=null )
1306            frag.appendChild( n );
1307
1308        Node JavaDoc commonParent = startAncestor.getParentNode();
1309        int startOffset = indexOf( startAncestor, commonParent );
1310        int endOffset = indexOf( endAncestor, commonParent );
1311        ++startOffset;
1312
1313        int cnt = endOffset - startOffset;
1314        Node JavaDoc sibling = startAncestor.getNextSibling();
1315
1316        while( cnt > 0 )
1317        {
1318            Node JavaDoc nextSibling = sibling.getNextSibling();
1319            n = traverseFullySelected( sibling, how );
1320            if ( frag!=null )
1321                frag.appendChild( n );
1322            sibling = nextSibling;
1323            --cnt;
1324        }
1325
1326        n = traverseRightBoundary( endAncestor, how );
1327        if ( frag!=null )
1328            frag.appendChild( n );
1329
1330        if ( how != CLONE_CONTENTS )
1331        {
1332            setStartAfter( startAncestor );
1333            collapse( true );
1334        }
1335        return frag;
1336    }
1337
1338    /**
1339     * Traverses the "right boundary" of this range and
1340     * operates on each "boundary node" according to the
1341     * <code>how</code> parameter. It is a-priori assumed
1342     * by this method that the right boundary does
1343     * not contain the range's start container.
1344     * <p>
1345     * A "right boundary" is best visualized by thinking
1346     * of a sample tree:<pre>
1347     * A
1348     * /|\
1349     * / | \
1350     * / | \
1351     * B C D
1352     * /|\ /|\
1353     * E F G H I J
1354     * </pre>
1355     * Imagine first a range that begins between the
1356     * "E" and "F" nodes and ends between the
1357     * "I" and "J" nodes. The start container is
1358     * "B" and the end container is "D". Given this setup,
1359     * the following applies:
1360     * <p>
1361     * Partially Selected Nodes: B, D<br>
1362     * Fully Selected Nodes: F, G, C, H, I
1363     * <p>
1364     * The "right boundary" is the highest subtree node
1365     * that contains the ending container. The root of
1366     * this subtree is always partially selected.
1367     * <p>
1368     * In this example, the nodes that are traversed
1369     * as "right boundary" nodes are: H, I, and D.
1370     *
1371     * @param root The node that is the root of the "right boundary" subtree.
1372     *
1373     * @param how Specifies what type of traversal is being
1374     * requested (extract, clone, or delete).
1375     * Legal values for this argument are:
1376     *
1377     * <ol>
1378     * <li><code>EXTRACT_CONTENTS</code> - will produce
1379     * a node containing the boundaries content.
1380     * Partially selected nodes are copied, but fully
1381     * selected nodes are moved.
1382     *
1383     * <li><code>CLONE_CONTENTS</code> - will leave the
1384     * context tree of the range undisturbed, but will
1385     * produced cloned content.
1386     *
1387     * <li><code>DELETE_CONTENTS</code> - will delete from
1388     * the context tree of the range, all fully selected
1389     * nodes within the boundary.
1390     * </ol>
1391     *
1392     * @return Returns a node that is the result of visiting nodes.
1393     * If the traversal operation is
1394     * <code>DELETE_CONTENTS</code> the return value is null.
1395     */

1396    private Node JavaDoc traverseRightBoundary( Node JavaDoc root, int how )
1397    {
1398        Node JavaDoc next = getSelectedNode( fEndContainer, fEndOffset-1 );
1399        boolean isFullySelected = ( next!=fEndContainer );
1400
1401        if ( next==root )
1402            return traverseNode( next, isFullySelected, false, how );
1403
1404        Node JavaDoc parent = next.getParentNode();
1405        Node JavaDoc clonedParent = traverseNode( parent, false, false, how );
1406
1407        while( parent!=null )
1408        {
1409            while( next!=null )
1410            {
1411                Node JavaDoc prevSibling = next.getPreviousSibling();
1412                Node JavaDoc clonedChild =
1413                    traverseNode( next, isFullySelected, false, how );
1414                if ( how!=DELETE_CONTENTS )
1415                {
1416                    clonedParent.insertBefore(
1417                        clonedChild,
1418                        clonedParent.getFirstChild()
1419                    );
1420                }
1421                isFullySelected = true;
1422                next = prevSibling;
1423            }
1424            if ( parent==root )
1425                return clonedParent;
1426
1427            next = parent.getPreviousSibling();
1428            parent = parent.getParentNode();
1429            Node JavaDoc clonedGrandParent = traverseNode( parent, false, false, how );
1430            if ( how!=DELETE_CONTENTS )
1431                clonedGrandParent.appendChild( clonedParent );
1432            clonedParent = clonedGrandParent;
1433
1434        }
1435
1436        // should never occur
1437
return null;
1438    }
1439
1440    /**
1441     * Traverses the "left boundary" of this range and
1442     * operates on each "boundary node" according to the
1443     * <code>how</code> parameter. It is a-priori assumed
1444     * by this method that the left boundary does
1445     * not contain the range's end container.
1446     * <p>
1447     * A "left boundary" is best visualized by thinking
1448     * of a sample tree:<pre>
1449     *
1450     * A
1451     * /|\
1452     * / | \
1453     * / | \
1454     * B C D
1455     * /|\ /|\
1456     * E F G H I J
1457     * </pre>
1458     * Imagine first a range that begins between the
1459     * "E" and "F" nodes and ends between the
1460     * "I" and "J" nodes. The start container is
1461     * "B" and the end container is "D". Given this setup,
1462     * the following applies:
1463     * <p>
1464     * Partially Selected Nodes: B, D<br>
1465     * Fully Selected Nodes: F, G, C, H, I
1466     * <p>
1467     * The "left boundary" is the highest subtree node
1468     * that contains the starting container. The root of
1469     * this subtree is always partially selected.
1470     * <p>
1471     * In this example, the nodes that are traversed
1472     * as "left boundary" nodes are: F, G, and B.
1473     *
1474     * @param root The node that is the root of the "left boundary" subtree.
1475     *
1476     * @param how Specifies what type of traversal is being
1477     * requested (extract, clone, or delete).
1478     * Legal values for this argument are:
1479     *
1480     * <ol>
1481     * <li><code>EXTRACT_CONTENTS</code> - will produce
1482     * a node containing the boundaries content.
1483     * Partially selected nodes are copied, but fully
1484     * selected nodes are moved.
1485     *
1486     * <li><code>CLONE_CONTENTS</code> - will leave the
1487     * context tree of the range undisturbed, but will
1488     * produced cloned content.
1489     *
1490     * <li><code>DELETE_CONTENTS</code> - will delete from
1491     * the context tree of the range, all fully selected
1492     * nodes within the boundary.
1493     * </ol>
1494     *
1495     * @return Returns a node that is the result of visiting nodes.
1496     * If the traversal operation is
1497     * <code>DELETE_CONTENTS</code> the return value is null.
1498     */

1499    private Node JavaDoc traverseLeftBoundary( Node JavaDoc root, int how )
1500    {
1501        Node JavaDoc next = getSelectedNode( getStartContainer(), getStartOffset() );
1502        boolean isFullySelected = ( next!=getStartContainer() );
1503
1504        if ( next==root )
1505            return traverseNode( next, isFullySelected, true, how );
1506
1507        Node JavaDoc parent = next.getParentNode();
1508        Node JavaDoc clonedParent = traverseNode( parent, false, true, how );
1509
1510        while( parent!=null )
1511        {
1512            while( next!=null )
1513            {
1514                Node JavaDoc nextSibling = next.getNextSibling();
1515                Node JavaDoc clonedChild =
1516                    traverseNode( next, isFullySelected, true, how );
1517                if ( how!=DELETE_CONTENTS )
1518                    clonedParent.appendChild(clonedChild);
1519                isFullySelected = true;
1520                next = nextSibling;
1521            }
1522            if ( parent==root )
1523                return clonedParent;
1524
1525            next = parent.getNextSibling();
1526            parent = parent.getParentNode();
1527            Node JavaDoc clonedGrandParent = traverseNode( parent, false, true, how );
1528            if ( how!=DELETE_CONTENTS )
1529                clonedGrandParent.appendChild( clonedParent );
1530            clonedParent = clonedGrandParent;
1531
1532        }
1533
1534        // should never occur
1535
return null;
1536
1537    }
1538
1539    /**
1540     * Utility method for traversing a single node.
1541     * Does not properly handle a text node containing both the
1542     * start and end offsets. Such nodes should
1543     * have been previously detected and been routed to traverseTextNode.
1544     *
1545     * @param n The node to be traversed.
1546     *
1547     * @param isFullySelected
1548     * Set to true if the node is fully selected. Should be
1549     * false otherwise.
1550     * Note that although the DOM 2 specification says that a
1551     * text node that is boththe start and end container is not
1552     * selected, we treat it here as if it were partially
1553     * selected.
1554     *
1555     * @param isLeft Is true if we are traversing the node as part of navigating
1556     * the "left boundary" of the range. If this value is false,
1557     * it implies we are navigating the "right boundary" of the
1558     * range.
1559     *
1560     * @param how Specifies what type of traversal is being
1561     * requested (extract, clone, or delete).
1562     * Legal values for this argument are:
1563     *
1564     * <ol>
1565     * <li><code>EXTRACT_CONTENTS</code> - will simply
1566     * return the original node.
1567     *
1568     * <li><code>CLONE_CONTENTS</code> - will leave the
1569     * context tree of the range undisturbed, but will
1570     * return a cloned node.
1571     *
1572     * <li><code>DELETE_CONTENTS</code> - will delete the
1573     * node from it's parent, but will return null.
1574     * </ol>
1575     *
1576     * @return Returns a node that is the result of visiting the node.
1577     * If the traversal operation is
1578     * <code>DELETE_CONTENTS</code> the return value is null.
1579     */

1580    private Node JavaDoc traverseNode( Node JavaDoc n, boolean isFullySelected, boolean isLeft, int how )
1581    {
1582        if ( isFullySelected )
1583            return traverseFullySelected( n, how );
1584        if ( n.getNodeType()==Node.TEXT_NODE )
1585            return traverseTextNode( n, isLeft, how );
1586        return traversePartiallySelected( n, how );
1587    }
1588
1589    /**
1590     * Utility method for traversing a single node when
1591     * we know a-priori that the node if fully
1592     * selected.
1593     *
1594     * @param n The node to be traversed.
1595     *
1596     * @param how Specifies what type of traversal is being
1597     * requested (extract, clone, or delete).
1598     * Legal values for this argument are:
1599     *
1600     * <ol>
1601     * <li><code>EXTRACT_CONTENTS</code> - will simply
1602     * return the original node.
1603     *
1604     * <li><code>CLONE_CONTENTS</code> - will leave the
1605     * context tree of the range undisturbed, but will
1606     * return a cloned node.
1607     *
1608     * <li><code>DELETE_CONTENTS</code> - will delete the
1609     * node from it's parent, but will return null.
1610     * </ol>
1611     *
1612     * @return Returns a node that is the result of visiting the node.
1613     * If the traversal operation is
1614     * <code>DELETE_CONTENTS</code> the return value is null.
1615     */

1616    private Node JavaDoc traverseFullySelected( Node JavaDoc n, int how )
1617    {
1618        switch( how )
1619        {
1620        case CLONE_CONTENTS:
1621            return n.cloneNode( true );
1622        case EXTRACT_CONTENTS:
1623            if ( n.getNodeType()==Node.DOCUMENT_TYPE_NODE )
1624            {
1625                // TBD: This should be a HIERARCHY_REQUEST_ERR
1626
throw new RangeExceptionImpl(
1627                    RangeException.INVALID_NODE_TYPE_ERR,
1628                "DOM012 Invalid node type");
1629            }
1630            return n;
1631        case DELETE_CONTENTS:
1632            n.getParentNode().removeChild(n);
1633            return null;
1634        }
1635        return null;
1636    }
1637
1638    /**
1639     * Utility method for traversing a single node when
1640     * we know a-priori that the node if partially
1641     * selected and is not a text node.
1642     *
1643     * @param n The node to be traversed.
1644     *
1645     * @param how Specifies what type of traversal is being
1646     * requested (extract, clone, or delete).
1647     * Legal values for this argument are:
1648     *
1649     * <ol>
1650     * <li><code>EXTRACT_CONTENTS</code> - will simply
1651     * return the original node.
1652     *
1653     * <li><code>CLONE_CONTENTS</code> - will leave the
1654     * context tree of the range undisturbed, but will
1655     * return a cloned node.
1656     *
1657     * <li><code>DELETE_CONTENTS</code> - will delete the
1658     * node from it's parent, but will return null.
1659     * </ol>
1660     *
1661     * @return Returns a node that is the result of visiting the node.
1662     * If the traversal operation is
1663     * <code>DELETE_CONTENTS</code> the return value is null.
1664     */

1665    private Node JavaDoc traversePartiallySelected( Node JavaDoc n, int how )
1666    {
1667        switch( how )
1668        {
1669        case DELETE_CONTENTS:
1670            return null;
1671        case CLONE_CONTENTS:
1672        case EXTRACT_CONTENTS:
1673            return n.cloneNode( false );
1674        }
1675        return null;
1676    }
1677
1678    /**
1679     * Utility method for traversing a text node that we know
1680     * a-priori to be on a left or right boundary of the range.
1681     * This method does not properly handle text nodes that contain
1682     * both the start and end points of the range.
1683     *
1684     * @param n The node to be traversed.
1685     *
1686     * @param isLeft Is true if we are traversing the node as part of navigating
1687     * the "left boundary" of the range. If this value is false,
1688     * it implies we are navigating the "right boundary" of the
1689     * range.
1690     *
1691     * @param how Specifies what type of traversal is being
1692     * requested (extract, clone, or delete).
1693     * Legal values for this argument are:
1694     *
1695     * <ol>
1696     * <li><code>EXTRACT_CONTENTS</code> - will simply
1697     * return the original node.
1698     *
1699     * <li><code>CLONE_CONTENTS</code> - will leave the
1700     * context tree of the range undisturbed, but will
1701     * return a cloned node.
1702     *
1703     * <li><code>DELETE_CONTENTS</code> - will delete the
1704     * node from it's parent, but will return null.
1705     * </ol>
1706     *
1707     * @return Returns a node that is the result of visiting the node.
1708     * If the traversal operation is
1709     * <code>DELETE_CONTENTS</code> the return value is null.
1710     */

1711    private Node JavaDoc traverseTextNode( Node JavaDoc n, boolean isLeft, int how )
1712    {
1713        String JavaDoc txtValue = n.getNodeValue();
1714        String JavaDoc newNodeValue;
1715        String JavaDoc oldNodeValue;
1716
1717        if ( isLeft )
1718        {
1719            int offset = getStartOffset();
1720            newNodeValue = txtValue.substring( offset );
1721            oldNodeValue = txtValue.substring( 0, offset );
1722        }
1723        else
1724        {
1725            int offset = getEndOffset();
1726            newNodeValue = txtValue.substring( 0, offset );
1727            oldNodeValue = txtValue.substring( offset );
1728        }
1729
1730        if ( how != CLONE_CONTENTS )
1731            n.setNodeValue( oldNodeValue );
1732        if ( how==DELETE_CONTENTS )
1733            return null;
1734        Node JavaDoc newNode = n.cloneNode( false );
1735        newNode.setNodeValue( newNodeValue );
1736        return newNode;
1737    }
1738
1739    void checkIndex(Node JavaDoc refNode, int offset) throws DOMException JavaDoc
1740    {
1741        if (offset < 0) {
1742            throw new DOMException JavaDoc(
1743                                   DOMException.INDEX_SIZE_ERR,
1744                                   "DOM004 Index out of bounds");
1745        }
1746
1747        int type = refNode.getNodeType();
1748        
1749        // If the node contains text, ensure that the
1750
// offset of the range is <= to the length of the text
1751
if (type == Node.TEXT_NODE
1752            || type == Node.CDATA_SECTION_NODE
1753            || type == Node.COMMENT_NODE
1754            || type == Node.PROCESSING_INSTRUCTION_NODE) {
1755            if (offset > refNode.getNodeValue().length()) {
1756                throw new DOMException JavaDoc(DOMException.INDEX_SIZE_ERR,
1757                                       "DOM004 Index out of bounds");
1758            }
1759        }
1760        else {
1761            // Since the node is not text, ensure that the offset
1762
// is valid with respect to the number of child nodes
1763
if (offset > refNode.getChildNodes().getLength()) {
1764            throw new DOMException JavaDoc(DOMException.INDEX_SIZE_ERR,
1765                                       "DOM004 Index out of bounds");
1766            }
1767        }
1768    }
1769
1770    /**
1771     * Given a node, calculate what the Range's root container
1772     * for that node would be.
1773     */

1774    private Node JavaDoc getRootContainer( Node JavaDoc node )
1775    {
1776        if ( node==null )
1777            return null;
1778
1779        while( node.getParentNode()!=null )
1780            node = node.getParentNode();
1781        return node;
1782    }
1783
1784    /**
1785     * Returns true IFF the given node can serve as a container
1786     * for a range's boundary points.
1787     */

1788    private boolean isLegalContainer( Node JavaDoc node )
1789    {
1790        if ( node==null )
1791            return false;
1792
1793        while( node!=null )
1794        {
1795            switch( node.getNodeType() )
1796            {
1797            case Node.ENTITY_NODE:
1798            case Node.NOTATION_NODE:
1799            case Node.DOCUMENT_TYPE_NODE:
1800                return false;
1801            }
1802            node = node.getParentNode();
1803        }
1804
1805        return true;
1806    }
1807
1808
1809    /**
1810     * Finds the root container for the given node and determines
1811     * if that root container is legal with respect to the
1812     * DOM 2 specification. At present, that means the root
1813     * container must be either an attribute, a document,
1814     * or a document fragment.
1815     */

1816    private boolean hasLegalRootContainer( Node JavaDoc node )
1817    {
1818        if ( node==null )
1819            return false;
1820
1821        Node JavaDoc rootContainer = getRootContainer( node );
1822        switch( rootContainer.getNodeType() )
1823        {
1824        case Node.ATTRIBUTE_NODE:
1825        case Node.DOCUMENT_NODE:
1826        case Node.DOCUMENT_FRAGMENT_NODE:
1827            return true;
1828        }
1829        return false;
1830    }
1831
1832    /**
1833     * Returns true IFF the given node can be contained by
1834     * a range.
1835     */

1836    private boolean isLegalContainedNode( Node JavaDoc node )
1837    {
1838        if ( node==null )
1839            return false;
1840        switch( node.getNodeType() )
1841        {
1842        case Node.DOCUMENT_NODE:
1843        case Node.DOCUMENT_FRAGMENT_NODE:
1844        case Node.ATTRIBUTE_NODE:
1845        case Node.ENTITY_NODE:
1846        case Node.NOTATION_NODE:
1847            return false;
1848        }
1849        return true;
1850    }
1851
1852    Node JavaDoc nextNode(Node JavaDoc node, boolean visitChildren) {
1853            
1854        if (node == null) return null;
1855
1856        Node JavaDoc result;
1857        if (visitChildren) {
1858            result = node.getFirstChild();
1859            if (result != null) {
1860                return result;
1861            }
1862        }
1863            
1864        // if hasSibling, return sibling
1865
result = node.getNextSibling();
1866        if (result != null) {
1867            return result;
1868        }
1869        
1870                
1871        // return parent's 1st sibling.
1872
Node JavaDoc parent = node.getParentNode();
1873        while (parent != null
1874               && parent != fDocument
1875                ) {
1876            result = parent.getNextSibling();
1877            if (result != null) {
1878                return result;
1879            } else {
1880                parent = parent.getParentNode();
1881            }
1882                            
1883        } // while (parent != null && parent != fRoot) {
1884

1885        // end of list, return null
1886
return null;
1887    }
1888    
1889    /** is a an ancestor of b ? */
1890    boolean isAncestorOf(Node JavaDoc a, Node JavaDoc b) {
1891        for (Node JavaDoc node=b; node != null; node=node.getParentNode()) {
1892            if (node == a) return true;
1893        }
1894        return false;
1895    }
1896
1897    /** what is the index of the child in the parent */
1898    int indexOf(Node JavaDoc child, Node JavaDoc parent) {
1899        if (child.getParentNode() != parent) return -1;
1900        int i = 0;
1901        for(Node JavaDoc node = parent.getFirstChild(); node!= child; node=node.getNextSibling()) {
1902            i++;
1903        }
1904        return i;
1905    }
1906
1907    /**
1908     * Utility method to retrieve a child node by index. This method
1909     * assumes the caller is trying to find out which node is
1910     * selected by the given index. Note that if the index is
1911     * greater than the number of children, this implies that the
1912     * first node selected is the parent node itself.
1913     *
1914     * @param container A container node
1915     *
1916     * @param offset An offset within the container for which a selected node should
1917     * be computed. If the offset is less than zero, or if the offset
1918     * is greater than the number of children, the container is returned.
1919     *
1920     * @return Returns either a child node of the container or the
1921     * container itself.
1922     */

1923    private Node JavaDoc getSelectedNode( Node JavaDoc container, int offset )
1924    {
1925        if ( container.getNodeType() == Node.TEXT_NODE )
1926            return container;
1927
1928        // This case is an important convenience for
1929
// traverseRightBoundary()
1930
if ( offset<0 )
1931            return container;
1932
1933        Node JavaDoc child = container.getFirstChild();
1934        while( child!=null && offset > 0 )
1935        {
1936            --offset;
1937            child = child.getNextSibling();
1938        }
1939        if ( child!=null )
1940            return child;
1941        return container;
1942    }
1943
1944}
1945
Popular Tags