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