KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > xquark > xpath > datamodel > 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.xquark.xpath.datamodel.xerces.dom;
57
58 import java.util.Vector JavaDoc;
59
60 import org.w3c.dom.*;
61 import org.w3c.dom.ranges.Range;
62 import org.w3c.dom.ranges.RangeException;
63
64 /** The RangeImpl class implements the org.w3c.dom.range.Range interface.
65  * <p> Please see the API documentation for the interface classes
66  * and use the interfaces in your client programs.
67  */

68 public class RangeImpl implements Range {
69     
70     //
71
// Constants
72
//
73

74
75     //
76
// Data
77
//
78

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

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

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

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

489         if( fDetach) {
490             throw new DOMException(
491                 DOMException.INVALID_STATE_ERR,
492             "DOM011 Invalid state");
493         }
494         if ( fDocument != newNode.getOwnerDocument() ) {
495             throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,"DOM004 Wrong document");
496         }
497        
498         int type = newNode.getNodeType();
499         if (type == Node.ATTRIBUTE_NODE
500             || type == Node.ENTITY_NODE
501             || type == Node.NOTATION_NODE
502             || type == Node.DOCUMENT_NODE)
503         {
504             throw new RangeExceptionImpl(
505                 RangeException.INVALID_NODE_TYPE_ERR,
506             "DOM012 Invalid node type");
507         }
508         Node cloneCurrent;
509         Node current;
510         int currentChildren = 0;
511
512         //boolean MULTIPLE_MODE = false;
513
if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
514         
515             Node parent = fStartContainer.getParentNode();
516             currentChildren = parent.getChildNodes().getLength(); //holds number of kids before insertion
517
// split text node: results is 3 nodes..
518
cloneCurrent = fStartContainer.cloneNode(false);
519             ((TextImpl)cloneCurrent).setNodeValueInternal(
520                     (cloneCurrent.getNodeValue()).substring(fStartOffset));
521             ((TextImpl)fStartContainer).setNodeValueInternal(
522                     (fStartContainer.getNodeValue()).substring(0,fStartOffset));
523             Node next = fStartContainer.getNextSibling();
524             if (next != null) {
525                     if (parent != null) {
526                         parent.insertBefore(newNode, next);
527                         parent.insertBefore(cloneCurrent, next);
528                     }
529             } else {
530                     if (parent != null) {
531                         parent.appendChild(newNode);
532                         parent.appendChild(cloneCurrent);
533                     }
534             }
535              //update ranges after the insertion
536
if ( fEndContainer == fStartContainer) {
537                   fEndContainer = cloneCurrent; //endContainer is the new Node created
538
fEndOffset -= fStartOffset;
539              }
540              else if ( fEndContainer == parent ) { //endContainer was not a text Node.
541
//endOffset + = number_of_children_added
542
fEndOffset += (parent.getChildNodes().getLength() - currentChildren);
543              }
544
545              // signal other Ranges to update their start/end containers/offsets
546
signalSplitData(fStartContainer, cloneCurrent, fStartOffset);
547                 
548              
549         } else { // ! TEXT_NODE
550
if ( fEndContainer == fStartContainer ) //need to remember number of kids
551
currentChildren= fEndContainer.getChildNodes().getLength();
552
553             current = fStartContainer.getFirstChild();
554             int i = 0;
555             for(i = 0; i < fStartOffset && current != null; i++) {
556                 current=current.getNextSibling();
557             }
558             if (current != null) {
559                 fStartContainer.insertBefore(newNode, current);
560             } else {
561                 fStartContainer.appendChild(newNode);
562             }
563             //update fEndOffset. ex:<body><p/></body>. Range(start;end): body,0; body,1
564
// insert <h1>: <body></h1><p/></body>. Range(start;end): body,0; body,2
565
if ( fEndContainer == fStartContainer ) { //update fEndOffset
566
fEndOffset += (fEndContainer.getChildNodes().getLength() - currentChildren);
567             }
568
569         }
570     }
571     
572     public void surroundContents(Node newParent)
573         throws DOMException, RangeException
574     {
575         if (newParent==null) return;
576         
577         if( fDetach) {
578             throw new DOMException(
579                 DOMException.INVALID_STATE_ERR,
580             "DOM011 Invalid state");
581         }
582         int type = newParent.getNodeType();
583         if (type == Node.ATTRIBUTE_NODE
584             || type == Node.ENTITY_NODE
585             || type == Node.NOTATION_NODE
586             || type == Node.DOCUMENT_TYPE_NODE
587             || type == Node.DOCUMENT_NODE
588             || type == Node.DOCUMENT_FRAGMENT_NODE)
589         {
590             throw new RangeExceptionImpl(
591                 RangeException.INVALID_NODE_TYPE_ERR,
592             "DOM012 Invalid node type");
593         }
594         
595         //JN: unread local variable
596
//Node root = getCommonAncestorContainer();
597

598         Node realStart = fStartContainer;
599         Node realEnd = fEndContainer;
600         if (fStartContainer.getNodeType() == Node.TEXT_NODE) {
601             realStart = fStartContainer.getParentNode();
602         }
603         if (fEndContainer.getNodeType() == Node.TEXT_NODE) {
604             realEnd = fEndContainer.getParentNode();
605         }
606             
607         if (realStart != realEnd) {
608             throw new RangeExceptionImpl(
609             RangeException.BAD_BOUNDARYPOINTS_ERR,
610             "DOM013 Bad boundary points");
611         }
612
613         DocumentFragment frag = extractContents();
614         insertNode(newParent);
615         newParent.appendChild(frag);
616         selectNode(newParent);
617     }
618         
619     public Range cloneRange(){
620         if( fDetach) {
621             throw new DOMException(
622                 DOMException.INVALID_STATE_ERR,
623             "DOM011 Invalid state");
624         }
625         
626         Range range = fDocument.createRange();
627         range.setStart(fStartContainer, fStartOffset);
628         range.setEnd(fEndContainer, fEndOffset);
629         return range;
630     }
631     
632     public String JavaDoc toString(){
633         if( fDetach) {
634             throw new DOMException(
635                 DOMException.INVALID_STATE_ERR,
636             "DOM011 Invalid state");
637         }
638         
639         Node node = fStartContainer;
640         Node stopNode = fEndContainer;
641         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
642         if (fStartContainer.getNodeType() == Node.TEXT_NODE
643          || fStartContainer.getNodeType() == Node.CDATA_SECTION_NODE
644         ) {
645             if (fStartContainer == fEndContainer) {
646                 sb.append(fStartContainer.getNodeValue().substring(fStartOffset, fEndOffset));
647                 return sb.toString();
648             }
649             sb.append(fStartContainer.getNodeValue().substring(fStartOffset));
650             node=nextNode (node,true); //fEndContainer!=fStartContainer
651

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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