source: trunk/phpgwapi/js/ckeditor/_source/core/dom/range.js @ 2862

Revision 2862, 55.0 KB checked in by rodsouza, 14 years ago (diff)

Ticket #663 - Atualizando e centralizando o CKEditor (v. 3.2.1)

Line 
1/*
2Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6CKEDITOR.dom.range = function( document )
7{
8        this.startContainer     = null;
9        this.startOffset        = null;
10        this.endContainer       = null;
11        this.endOffset          = null;
12        this.collapsed          = true;
13
14        this.document = document;
15};
16
17(function()
18{
19        // Updates the "collapsed" property for the given range object.
20        var updateCollapsed = function( range )
21        {
22                range.collapsed = (
23                        range.startContainer &&
24                        range.endContainer &&
25                        range.startContainer.equals( range.endContainer ) &&
26                        range.startOffset == range.endOffset );
27        };
28
29        // This is a shared function used to delete, extract and clone the range
30        // contents.
31        // V2
32        var execContentsAction = function( range, action, docFrag )
33        {
34                range.optimizeBookmark();
35
36                var startNode   = range.startContainer;
37                var endNode             = range.endContainer;
38
39                var startOffset = range.startOffset;
40                var endOffset   = range.endOffset;
41
42                var removeStartNode;
43                var removeEndNode;
44
45                // For text containers, we must simply split the node and point to the
46                // second part. The removal will be handled by the rest of the code .
47                if ( endNode.type == CKEDITOR.NODE_TEXT )
48                        endNode = endNode.split( endOffset );
49                else
50                {
51                        // If the end container has children and the offset is pointing
52                        // to a child, then we should start from it.
53                        if ( endNode.getChildCount() > 0 )
54                        {
55                                // If the offset points after the last node.
56                                if ( endOffset >= endNode.getChildCount() )
57                                {
58                                        // Let's create a temporary node and mark it for removal.
59                                        endNode = endNode.append( range.document.createText( '' ) );
60                                        removeEndNode = true;
61                                }
62                                else
63                                        endNode = endNode.getChild( endOffset );
64                        }
65                }
66
67                // For text containers, we must simply split the node. The removal will
68                // be handled by the rest of the code .
69                if ( startNode.type == CKEDITOR.NODE_TEXT )
70                {
71                        startNode.split( startOffset );
72
73                        // In cases the end node is the same as the start node, the above
74                        // splitting will also split the end, so me must move the end to
75                        // the second part of the split.
76                        if ( startNode.equals( endNode ) )
77                                endNode = startNode.getNext();
78                }
79                else
80                {
81                        // If the start container has children and the offset is pointing
82                        // to a child, then we should start from its previous sibling.
83
84                        // If the offset points to the first node, we don't have a
85                        // sibling, so let's use the first one, but mark it for removal.
86                        if ( !startOffset )
87                        {
88                                // Let's create a temporary node and mark it for removal.
89                                startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
90                                removeStartNode = true;
91                        }
92                        else if ( startOffset >= startNode.getChildCount() )
93                        {
94                                // Let's create a temporary node and mark it for removal.
95                                startNode = startNode.append( range.document.createText( '' ) );
96                                removeStartNode = true;
97                        }
98                        else
99                                startNode = startNode.getChild( startOffset ).getPrevious();
100                }
101
102                // Get the parent nodes tree for the start and end boundaries.
103                var startParents        = startNode.getParents();
104                var endParents          = endNode.getParents();
105
106                // Compare them, to find the top most siblings.
107                var i, topStart, topEnd;
108
109                for ( i = 0 ; i < startParents.length ; i++ )
110                {
111                        topStart = startParents[ i ];
112                        topEnd = endParents[ i ];
113
114                        // The compared nodes will match until we find the top most
115                        // siblings (different nodes that have the same parent).
116                        // "i" will hold the index in the parents array for the top
117                        // most element.
118                        if ( !topStart.equals( topEnd ) )
119                                break;
120                }
121
122                var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
123
124                // Remove all successive sibling nodes for every node in the
125                // startParents tree.
126                for ( var j = i ; j < startParents.length ; j++ )
127                {
128                        levelStartNode = startParents[j];
129
130                        // For Extract and Clone, we must clone this level.
131                        if ( clone && !levelStartNode.equals( startNode ) )             // action = 0 = Delete
132                                levelClone = clone.append( levelStartNode.clone() );
133
134                        currentNode = levelStartNode.getNext();
135
136                        while ( currentNode )
137                        {
138                                // Stop processing when the current node matches a node in the
139                                // endParents tree or if it is the endNode.
140                                if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
141                                        break;
142
143                                // Cache the next sibling.
144                                currentSibling = currentNode.getNext();
145
146                                // If cloning, just clone it.
147                                if ( action == 2 )      // 2 = Clone
148                                        clone.append( currentNode.clone( true ) );
149                                else
150                                {
151                                        // Both Delete and Extract will remove the node.
152                                        currentNode.remove();
153
154                                        // When Extracting, move the removed node to the docFrag.
155                                        if ( action == 1 )      // 1 = Extract
156                                                clone.append( currentNode );
157                                }
158
159                                currentNode = currentSibling;
160                        }
161
162                        if ( clone )
163                                clone = levelClone;
164                }
165
166                clone = docFrag;
167
168                // Remove all previous sibling nodes for every node in the
169                // endParents tree.
170                for ( var k = i ; k < endParents.length ; k++ )
171                {
172                        levelStartNode = endParents[ k ];
173
174                        // For Extract and Clone, we must clone this level.
175                        if ( action > 0 && !levelStartNode.equals( endNode ) )          // action = 0 = Delete
176                                levelClone = clone.append( levelStartNode.clone() );
177
178                        // The processing of siblings may have already been done by the parent.
179                        if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
180                        {
181                                currentNode = levelStartNode.getPrevious();
182
183                                while ( currentNode )
184                                {
185                                        // Stop processing when the current node matches a node in the
186                                        // startParents tree or if it is the startNode.
187                                        if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
188                                                break;
189
190                                        // Cache the next sibling.
191                                        currentSibling = currentNode.getPrevious();
192
193                                        // If cloning, just clone it.
194                                        if ( action == 2 )      // 2 = Clone
195                                                clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
196                                        else
197                                        {
198                                                // Both Delete and Extract will remove the node.
199                                                currentNode.remove();
200
201                                                // When Extracting, mode the removed node to the docFrag.
202                                                if ( action == 1 )      // 1 = Extract
203                                                        clone.$.insertBefore( currentNode.$, clone.$.firstChild );
204                                        }
205
206                                        currentNode = currentSibling;
207                                }
208                        }
209
210                        if ( clone )
211                                clone = levelClone;
212                }
213
214                if ( action == 2 )              // 2 = Clone.
215                {
216                        // No changes in the DOM should be done, so fix the split text (if any).
217
218                        var startTextNode = range.startContainer;
219                        if ( startTextNode.type == CKEDITOR.NODE_TEXT )
220                        {
221                                startTextNode.$.data += startTextNode.$.nextSibling.data;
222                                startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
223                        }
224
225                        var endTextNode = range.endContainer;
226                        if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
227                        {
228                                endTextNode.$.data += endTextNode.$.nextSibling.data;
229                                endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
230                        }
231                }
232                else
233                {
234                        // Collapse the range.
235
236                        // If a node has been partially selected, collapse the range between
237                        // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
238                        if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
239                        {
240                                var endIndex = topEnd.getIndex();
241
242                                // If the start node is to be removed, we must correct the
243                                // index to reflect the removal.
244                                if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
245                                        endIndex--;
246
247                                range.setStart( topEnd.getParent(), endIndex );
248                        }
249
250                        // Collapse it to the start.
251                        range.collapse( true );
252                }
253
254                // Cleanup any marked node.
255                if ( removeStartNode )
256                        startNode.remove();
257
258                if ( removeEndNode && endNode.$.parentNode )
259                        endNode.remove();
260        };
261
262        var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 };
263
264        // Creates the appropriate node evaluator for the dom walker used inside
265        // check(Start|End)OfBlock.
266        function getCheckStartEndBlockEvalFunction( isStart )
267        {
268                var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
269                return function( node )
270                {
271                        // First ignore bookmark nodes.
272                        if ( bookmarkEvaluator( node ) )
273                                return true;
274
275                        if ( node.type == CKEDITOR.NODE_TEXT )
276                        {
277                                // If there's any visible text, then we're not at the start.
278                                if ( CKEDITOR.tools.trim( node.getText() ).length )
279                                        return false;
280                                }
281                        else if ( node.type == CKEDITOR.NODE_ELEMENT )
282                        {
283                                // If there are non-empty inline elements (e.g. <img />), then we're not
284                                // at the start.
285                                if ( !inlineChildReqElements[ node.getName() ] )
286                                {
287                                        // If we're working at the end-of-block, forgive the first <br /> in non-IE
288                                        // browsers.
289                                        if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
290                                                hadBr = true;
291                                        else
292                                                return false;
293                                }
294                        }
295                        return true;
296                };
297        }
298
299        // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
300        // text node and non-empty elements unless it's being bookmark text.
301        function elementBoundaryEval( node )
302        {
303                // Reject any text node unless it's being bookmark
304                // OR it's spaces. (#3883)
305                return node.type != CKEDITOR.NODE_TEXT
306                            && node.getName() in CKEDITOR.dtd.$removeEmpty
307                            || !CKEDITOR.tools.trim( node.getText() )
308                            || node.getParent().hasAttribute( '_fck_bookmark' );
309        }
310
311        var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
312                bookmarkEval = new CKEDITOR.dom.walker.bookmark();
313
314        function nonWhitespaceOrBookmarkEval( node )
315        {
316                // Whitespaces and bookmark nodes are to be ignored.
317                return !whitespaceEval( node ) && !bookmarkEval( node );
318        }
319
320        CKEDITOR.dom.range.prototype =
321        {
322                clone : function()
323                {
324                        var clone = new CKEDITOR.dom.range( this.document );
325
326                        clone.startContainer = this.startContainer;
327                        clone.startOffset = this.startOffset;
328                        clone.endContainer = this.endContainer;
329                        clone.endOffset = this.endOffset;
330                        clone.collapsed = this.collapsed;
331
332                        return clone;
333                },
334
335                collapse : function( toStart )
336                {
337                        if ( toStart )
338                        {
339                                this.endContainer       = this.startContainer;
340                                this.endOffset          = this.startOffset;
341                        }
342                        else
343                        {
344                                this.startContainer     = this.endContainer;
345                                this.startOffset        = this.endOffset;
346                        }
347
348                        this.collapsed = true;
349                },
350
351                // The selection may be lost when cloning (due to the splitText() call).
352                cloneContents : function()
353                {
354                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );
355
356                        if ( !this.collapsed )
357                                execContentsAction( this, 2, docFrag );
358
359                        return docFrag;
360                },
361
362                deleteContents : function()
363                {
364                        if ( this.collapsed )
365                                return;
366
367                        execContentsAction( this, 0 );
368                },
369
370                extractContents : function()
371                {
372                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );
373
374                        if ( !this.collapsed )
375                                execContentsAction( this, 1, docFrag );
376
377                        return docFrag;
378                },
379
380                /**
381                 * Creates a bookmark object, which can be later used to restore the
382                 * range by using the moveToBookmark function.
383                 * This is an "intrusive" way to create a bookmark. It includes <span> tags
384                 * in the range boundaries. The advantage of it is that it is possible to
385                 * handle DOM mutations when moving back to the bookmark.
386                 * Attention: the inclusion of nodes in the DOM is a design choice and
387                 * should not be changed as there are other points in the code that may be
388                 * using those nodes to perform operations. See GetBookmarkNode.
389                 * @param {Boolean} [serializable] Indicates that the bookmark nodes
390                 *              must contain ids, which can be used to restore the range even
391                 *              when these nodes suffer mutations (like a clonation or innerHTML
392                 *              change).
393                 * @returns {Object} And object representing a bookmark.
394                 */
395                createBookmark : function( serializable )
396                {
397                        var startNode, endNode;
398                        var baseId;
399                        var clone;
400
401                        startNode = this.document.createElement( 'span' );
402                        startNode.setAttribute( '_fck_bookmark', 1 );
403                        startNode.setStyle( 'display', 'none' );
404
405                        // For IE, it must have something inside, otherwise it may be
406                        // removed during DOM operations.
407                        startNode.setHtml( '&nbsp;' );
408
409                        if ( serializable )
410                        {
411                                baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
412                                startNode.setAttribute( 'id', baseId + 'S' );
413                        }
414
415                        // If collapsed, the endNode will not be created.
416                        if ( !this.collapsed )
417                        {
418                                endNode = startNode.clone();
419                                endNode.setHtml( '&nbsp;' );
420
421                                if ( serializable )
422                                        endNode.setAttribute( 'id', baseId + 'E' );
423
424                                clone = this.clone();
425                                clone.collapse();
426                                clone.insertNode( endNode );
427                        }
428
429                        clone = this.clone();
430                        clone.collapse( true );
431                        clone.insertNode( startNode );
432
433                        // Update the range position.
434                        if ( endNode )
435                        {
436                                this.setStartAfter( startNode );
437                                this.setEndBefore( endNode );
438                        }
439                        else
440                                this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
441
442                        return {
443                                startNode : serializable ? baseId + 'S' : startNode,
444                                endNode : serializable ? baseId + 'E' : endNode,
445                                serializable : serializable
446                        };
447                },
448
449                /**
450                 * Creates a "non intrusive" and "mutation sensible" bookmark. This
451                 * kind of bookmark should be used only when the DOM is supposed to
452                 * remain stable after its creation.
453                 * @param {Boolean} [normalized] Indicates that the bookmark must
454                 *              normalized. When normalized, the successive text nodes are
455                 *              considered a single node. To sucessful load a normalized
456                 *              bookmark, the DOM tree must be also normalized before calling
457                 *              moveToBookmark.
458                 * @returns {Object} An object representing the bookmark.
459                 */
460                createBookmark2 : function( normalized )
461                {
462                        var startContainer      = this.startContainer,
463                                endContainer    = this.endContainer;
464
465                        var startOffset = this.startOffset,
466                                endOffset       = this.endOffset;
467
468                        var child, previous;
469
470                        // If there is no range then get out of here.
471                        // It happens on initial load in Safari #962 and if the editor it's
472                        // hidden also in Firefox
473                        if ( !startContainer || !endContainer )
474                                return { start : 0, end : 0 };
475
476                        if ( normalized )
477                        {
478                                // Find out if the start is pointing to a text node that will
479                                // be normalized.
480                                if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
481                                {
482                                        child = startContainer.getChild( startOffset );
483
484                                        // In this case, move the start information to that text
485                                        // node.
486                                        if ( child && child.type == CKEDITOR.NODE_TEXT
487                                                        && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
488                                        {
489                                                startContainer = child;
490                                                startOffset = 0;
491                                        }
492                                }
493
494                                // Normalize the start.
495                                while ( startContainer.type == CKEDITOR.NODE_TEXT
496                                                && ( previous = startContainer.getPrevious() )
497                                                && previous.type == CKEDITOR.NODE_TEXT )
498                                {
499                                        startContainer = previous;
500                                        startOffset += previous.getLength();
501                                }
502
503                                // Process the end only if not normalized.
504                                if ( !this.isCollapsed )
505                                {
506                                        // Find out if the start is pointing to a text node that
507                                        // will be normalized.
508                                        if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
509                                        {
510                                                child = endContainer.getChild( endOffset );
511
512                                                // In this case, move the start information to that
513                                                // text node.
514                                                if ( child && child.type == CKEDITOR.NODE_TEXT
515                                                                && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
516                                                {
517                                                        endContainer = child;
518                                                        endOffset = 0;
519                                                }
520                                        }
521
522                                        // Normalize the end.
523                                        while ( endContainer.type == CKEDITOR.NODE_TEXT
524                                                        && ( previous = endContainer.getPrevious() )
525                                                        && previous.type == CKEDITOR.NODE_TEXT )
526                                        {
527                                                endContainer = previous;
528                                                endOffset += previous.getLength();
529                                        }
530                                }
531                        }
532
533                        return {
534                                start           : startContainer.getAddress( normalized ),
535                                end                     : this.isCollapsed ? null : endContainer.getAddress( normalized ),
536                                startOffset     : startOffset,
537                                endOffset       : endOffset,
538                                normalized      : normalized,
539                                is2                     : true          // It's a createBookmark2 bookmark.
540                        };
541                },
542
543                moveToBookmark : function( bookmark )
544                {
545                        if ( bookmark.is2 )             // Created with createBookmark2().
546                        {
547                                // Get the start information.
548                                var startContainer      = this.document.getByAddress( bookmark.start, bookmark.normalized ),
549                                        startOffset     = bookmark.startOffset;
550
551                                // Get the end information.
552                                var endContainer        = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
553                                        endOffset       = bookmark.endOffset;
554
555                                // Set the start boundary.
556                                this.setStart( startContainer, startOffset );
557
558                                // Set the end boundary. If not available, collapse it.
559                                if ( endContainer )
560                                        this.setEnd( endContainer, endOffset );
561                                else
562                                        this.collapse( true );
563                        }
564                        else                                    // Created with createBookmark().
565                        {
566                                var serializable = bookmark.serializable,
567                                        startNode       = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
568                                        endNode         = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
569
570                                // Set the range start at the bookmark start node position.
571                                this.setStartBefore( startNode );
572
573                                // Remove it, because it may interfere in the setEndBefore call.
574                                startNode.remove();
575
576                                // Set the range end at the bookmark end node position, or simply
577                                // collapse it if it is not available.
578                                if ( endNode )
579                                {
580                                        this.setEndBefore( endNode );
581                                        endNode.remove();
582                                }
583                                else
584                                        this.collapse( true );
585                        }
586                },
587
588                getBoundaryNodes : function()
589                {
590                        var startNode = this.startContainer,
591                                endNode = this.endContainer,
592                                startOffset = this.startOffset,
593                                endOffset = this.endOffset,
594                                childCount;
595
596                        if ( startNode.type == CKEDITOR.NODE_ELEMENT )
597                        {
598                                childCount = startNode.getChildCount();
599                                if ( childCount > startOffset )
600                                        startNode = startNode.getChild( startOffset );
601                                else if ( childCount < 1 )
602                                        startNode = startNode.getPreviousSourceNode();
603                                else            // startOffset > childCount but childCount is not 0
604                                {
605                                        // Try to take the node just after the current position.
606                                        startNode = startNode.$;
607                                        while ( startNode.lastChild )
608                                                startNode = startNode.lastChild;
609                                        startNode = new CKEDITOR.dom.node( startNode );
610
611                                        // Normally we should take the next node in DFS order. But it
612                                        // is also possible that we've already reached the end of
613                                        // document.
614                                        startNode = startNode.getNextSourceNode() || startNode;
615                                }
616                        }
617                        if ( endNode.type == CKEDITOR.NODE_ELEMENT )
618                        {
619                                childCount = endNode.getChildCount();
620                                if ( childCount > endOffset )
621                                        endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
622                                else if ( childCount < 1 )
623                                        endNode = endNode.getPreviousSourceNode();
624                                else            // endOffset > childCount but childCount is not 0
625                                {
626                                        // Try to take the node just before the current position.
627                                        endNode = endNode.$;
628                                        while ( endNode.lastChild )
629                                                endNode = endNode.lastChild;
630                                        endNode = new CKEDITOR.dom.node( endNode );
631                                }
632                        }
633
634                        // Sometimes the endNode will come right before startNode for collapsed
635                        // ranges. Fix it. (#3780)
636                        if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
637                                startNode = endNode;
638
639                        return { startNode : startNode, endNode : endNode };
640                },
641
642                /**
643                 * Find the node which fully contains the range.
644                 * @param includeSelf
645                 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
646                 */
647                getCommonAncestor : function( includeSelf , ignoreTextNode )
648                {
649                        var start = this.startContainer,
650                                end = this.endContainer,
651                                ancestor;
652
653                        if ( start.equals( end ) )
654                        {
655                                if ( includeSelf
656                                                && start.type == CKEDITOR.NODE_ELEMENT
657                                                && this.startOffset == this.endOffset - 1 )
658                                        ancestor = start.getChild( this.startOffset );
659                                else
660                                        ancestor = start;
661                        }
662                        else
663                                ancestor = start.getCommonAncestor( end );
664
665                        return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
666                },
667
668                /**
669                 * Transforms the startContainer and endContainer properties from text
670                 * nodes to element nodes, whenever possible. This is actually possible
671                 * if either of the boundary containers point to a text node, and its
672                 * offset is set to zero, or after the last char in the node.
673                 */
674                optimize : function()
675                {
676                        var container = this.startContainer;
677                        var offset = this.startOffset;
678
679                        if ( container.type != CKEDITOR.NODE_ELEMENT )
680                        {
681                                if ( !offset )
682                                        this.setStartBefore( container );
683                                else if ( offset >= container.getLength() )
684                                        this.setStartAfter( container );
685                        }
686
687                        container = this.endContainer;
688                        offset = this.endOffset;
689
690                        if ( container.type != CKEDITOR.NODE_ELEMENT )
691                        {
692                                if ( !offset )
693                                        this.setEndBefore( container );
694                                else if ( offset >= container.getLength() )
695                                        this.setEndAfter( container );
696                        }
697                },
698
699                /**
700                 * Move the range out of bookmark nodes if they're been the container.
701                 */
702                optimizeBookmark: function()
703                {
704                        var startNode = this.startContainer,
705                                endNode = this.endContainer;
706
707                        if ( startNode.is && startNode.is( 'span' )
708                                && startNode.hasAttribute( '_fck_bookmark' ) )
709                                this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
710                        if ( endNode && endNode.is && endNode.is( 'span' )
711                                && endNode.hasAttribute( '_fck_bookmark' ) )
712                                this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );
713                },
714
715                trim : function( ignoreStart, ignoreEnd )
716                {
717                        var startContainer = this.startContainer,
718                                startOffset = this.startOffset,
719                                collapsed = this.collapsed;
720                        if ( ( !ignoreStart || collapsed )
721                                 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
722                        {
723                                // If the offset is zero, we just insert the new node before
724                                // the start.
725                                if ( !startOffset )
726                                {
727                                        startOffset = startContainer.getIndex();
728                                        startContainer = startContainer.getParent();
729                                }
730                                // If the offset is at the end, we'll insert it after the text
731                                // node.
732                                else if ( startOffset >= startContainer.getLength() )
733                                {
734                                        startOffset = startContainer.getIndex() + 1;
735                                        startContainer = startContainer.getParent();
736                                }
737                                // In other case, we split the text node and insert the new
738                                // node at the split point.
739                                else
740                                {
741                                        var nextText = startContainer.split( startOffset );
742
743                                        startOffset = startContainer.getIndex() + 1;
744                                        startContainer = startContainer.getParent();
745
746                                        // Check all necessity of updating the end boundary.
747                                        if ( this.startContainer.equals( this.endContainer ) )
748                                                this.setEnd( nextText, this.endOffset - this.startOffset );
749                                        else if ( startContainer.equals( this.endContainer ) )
750                                                this.endOffset += 1;
751                                }
752
753                                this.setStart( startContainer, startOffset );
754
755                                if ( collapsed )
756                                {
757                                        this.collapse( true );
758                                        return;
759                                }
760                        }
761
762                        var endContainer = this.endContainer;
763                        var endOffset = this.endOffset;
764
765                        if ( !( ignoreEnd || collapsed )
766                                 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
767                        {
768                                // If the offset is zero, we just insert the new node before
769                                // the start.
770                                if ( !endOffset )
771                                {
772                                        endOffset = endContainer.getIndex();
773                                        endContainer = endContainer.getParent();
774                                }
775                                // If the offset is at the end, we'll insert it after the text
776                                // node.
777                                else if ( endOffset >= endContainer.getLength() )
778                                {
779                                        endOffset = endContainer.getIndex() + 1;
780                                        endContainer = endContainer.getParent();
781                                }
782                                // In other case, we split the text node and insert the new
783                                // node at the split point.
784                                else
785                                {
786                                        endContainer.split( endOffset );
787
788                                        endOffset = endContainer.getIndex() + 1;
789                                        endContainer = endContainer.getParent();
790                                }
791
792                                this.setEnd( endContainer, endOffset );
793                        }
794                },
795
796                enlarge : function( unit )
797                {
798                        switch ( unit )
799                        {
800                                case CKEDITOR.ENLARGE_ELEMENT :
801
802                                        if ( this.collapsed )
803                                                return;
804
805                                        // Get the common ancestor.
806                                        var commonAncestor = this.getCommonAncestor();
807
808                                        var body = this.document.getBody();
809
810                                        // For each boundary
811                                        //              a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
812                                        //              b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later.
813
814                                        var startTop, endTop;
815
816                                        var enlargeable, sibling, commonReached;
817
818                                        // Indicates that the node can be added only if whitespace
819                                        // is available before it.
820                                        var needsWhiteSpace = false;
821                                        var isWhiteSpace;
822                                        var siblingText;
823
824                                        // Process the start boundary.
825
826                                        var container = this.startContainer;
827                                        var offset = this.startOffset;
828
829                                        if ( container.type == CKEDITOR.NODE_TEXT )
830                                        {
831                                                if ( offset )
832                                                {
833                                                        // Check if there is any non-space text before the
834                                                        // offset. Otherwise, container is null.
835                                                        container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
836
837                                                        // If we found only whitespace in the node, it
838                                                        // means that we'll need more whitespace to be able
839                                                        // to expand. For example, <i> can be expanded in
840                                                        // "A <i> [B]</i>", but not in "A<i> [B]</i>".
841                                                        needsWhiteSpace = !!container;
842                                                }
843
844                                                if ( container )
845                                                {
846                                                        if ( !( sibling = container.getPrevious() ) )
847                                                                enlargeable = container.getParent();
848                                                }
849                                        }
850                                        else
851                                        {
852                                                // If we have offset, get the node preceeding it as the
853                                                // first sibling to be checked.
854                                                if ( offset )
855                                                        sibling = container.getChild( offset - 1 ) || container.getLast();
856
857                                                // If there is no sibling, mark the container to be
858                                                // enlarged.
859                                                if ( !sibling )
860                                                        enlargeable = container;
861                                        }
862
863                                        while ( enlargeable || sibling )
864                                        {
865                                                if ( enlargeable && !sibling )
866                                                {
867                                                        // If we reached the common ancestor, mark the flag
868                                                        // for it.
869                                                        if ( !commonReached && enlargeable.equals( commonAncestor ) )
870                                                                commonReached = true;
871
872                                                        if ( !body.contains( enlargeable ) )
873                                                                break;
874
875                                                        // If we don't need space or this element breaks
876                                                        // the line, then enlarge it.
877                                                        if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
878                                                        {
879                                                                needsWhiteSpace = false;
880
881                                                                // If the common ancestor has been reached,
882                                                                // we'll not enlarge it immediately, but just
883                                                                // mark it to be enlarged later if the end
884                                                                // boundary also enlarges it.
885                                                                if ( commonReached )
886                                                                        startTop = enlargeable;
887                                                                else
888                                                                        this.setStartBefore( enlargeable );
889                                                        }
890
891                                                        sibling = enlargeable.getPrevious();
892                                                }
893
894                                                // Check all sibling nodes preceeding the enlargeable
895                                                // node. The node wil lbe enlarged only if none of them
896                                                // blocks it.
897                                                while ( sibling )
898                                                {
899                                                        // This flag indicates that this node has
900                                                        // whitespaces at the end.
901                                                        isWhiteSpace = false;
902
903                                                        if ( sibling.type == CKEDITOR.NODE_TEXT )
904                                                        {
905                                                                siblingText = sibling.getText();
906
907                                                                if ( /[^\s\ufeff]/.test( siblingText ) )
908                                                                        sibling = null;
909
910                                                                isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
911                                                        }
912                                                        else
913                                                        {
914                                                                // If this is a visible element.
915                                                                // We need to check for the bookmark attribute because IE insists on
916                                                                // rendering the display:none nodes we use for bookmarks. (#3363)
917                                                                if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
918                                                                {
919                                                                        // We'll accept it only if we need
920                                                                        // whitespace, and this is an inline
921                                                                        // element with whitespace only.
922                                                                        if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
923                                                                        {
924                                                                                // It must contains spaces and inline elements only.
925
926                                                                                siblingText = sibling.getText();
927
928                                                                                if ( (/[^\s\ufeff]/).test( siblingText ) )      // Spaces + Zero Width No-Break Space (U+FEFF)
929                                                                                        sibling = null;
930                                                                                else
931                                                                                {
932                                                                                        var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
933                                                                                        for ( var i = 0, child ; child = allChildren[ i++ ] ; )
934                                                                                        {
935                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
936                                                                                                {
937                                                                                                        sibling = null;
938                                                                                                        break;
939                                                                                                }
940                                                                                        }
941                                                                                }
942
943                                                                                if ( sibling )
944                                                                                        isWhiteSpace = !!siblingText.length;
945                                                                        }
946                                                                        else
947                                                                                sibling = null;
948                                                                }
949                                                        }
950
951                                                        // A node with whitespaces has been found.
952                                                        if ( isWhiteSpace )
953                                                        {
954                                                                // Enlarge the last enlargeable node, if we
955                                                                // were waiting for spaces.
956                                                                if ( needsWhiteSpace )
957                                                                {
958                                                                        if ( commonReached )
959                                                                                startTop = enlargeable;
960                                                                        else if ( enlargeable )
961                                                                                this.setStartBefore( enlargeable );
962                                                                }
963                                                                else
964                                                                        needsWhiteSpace = true;
965                                                        }
966
967                                                        if ( sibling )
968                                                        {
969                                                                var next = sibling.getPrevious();
970
971                                                                if ( !enlargeable && !next )
972                                                                {
973                                                                        // Set the sibling as enlargeable, so it's
974                                                                        // parent will be get later outside this while.
975                                                                        enlargeable = sibling;
976                                                                        sibling = null;
977                                                                        break;
978                                                                }
979
980                                                                sibling = next;
981                                                        }
982                                                        else
983                                                        {
984                                                                // If sibling has been set to null, then we
985                                                                // need to stop enlarging.
986                                                                enlargeable = null;
987                                                        }
988                                                }
989
990                                                if ( enlargeable )
991                                                        enlargeable = enlargeable.getParent();
992                                        }
993
994                                        // Process the end boundary. This is basically the same
995                                        // code used for the start boundary, with small changes to
996                                        // make it work in the oposite side (to the right). This
997                                        // makes it difficult to reuse the code here. So, fixes to
998                                        // the above code are likely to be replicated here.
999
1000                                        container = this.endContainer;
1001                                        offset = this.endOffset;
1002
1003                                        // Reset the common variables.
1004                                        enlargeable = sibling = null;
1005                                        commonReached = needsWhiteSpace = false;
1006
1007                                        if ( container.type == CKEDITOR.NODE_TEXT )
1008                                        {
1009                                                // Check if there is any non-space text after the
1010                                                // offset. Otherwise, container is null.
1011                                                container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
1012
1013                                                // If we found only whitespace in the node, it
1014                                                // means that we'll need more whitespace to be able
1015                                                // to expand. For example, <i> can be expanded in
1016                                                // "A <i> [B]</i>", but not in "A<i> [B]</i>".
1017                                                needsWhiteSpace = !( container && container.getLength() );
1018
1019                                                if ( container )
1020                                                {
1021                                                        if ( !( sibling = container.getNext() ) )
1022                                                                enlargeable = container.getParent();
1023                                                }
1024                                        }
1025                                        else
1026                                        {
1027                                                // Get the node right after the boudary to be checked
1028                                                // first.
1029                                                sibling = container.getChild( offset );
1030
1031                                                if ( !sibling )
1032                                                        enlargeable = container;
1033                                        }
1034
1035                                        while ( enlargeable || sibling )
1036                                        {
1037                                                if ( enlargeable && !sibling )
1038                                                {
1039                                                        if ( !commonReached && enlargeable.equals( commonAncestor ) )
1040                                                                commonReached = true;
1041
1042                                                        if ( !body.contains( enlargeable ) )
1043                                                                break;
1044
1045                                                        if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1046                                                        {
1047                                                                needsWhiteSpace = false;
1048
1049                                                                if ( commonReached )
1050                                                                        endTop = enlargeable;
1051                                                                else if ( enlargeable )
1052                                                                        this.setEndAfter( enlargeable );
1053                                                        }
1054
1055                                                        sibling = enlargeable.getNext();
1056                                                }
1057
1058                                                while ( sibling )
1059                                                {
1060                                                        isWhiteSpace = false;
1061
1062                                                        if ( sibling.type == CKEDITOR.NODE_TEXT )
1063                                                        {
1064                                                                siblingText = sibling.getText();
1065
1066                                                                if ( /[^\s\ufeff]/.test( siblingText ) )
1067                                                                        sibling = null;
1068
1069                                                                isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1070                                                        }
1071                                                        else
1072                                                        {
1073                                                                // If this is a visible element.
1074                                                                // We need to check for the bookmark attribute because IE insists on
1075                                                                // rendering the display:none nodes we use for bookmarks. (#3363)
1076                                                                if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
1077                                                                {
1078                                                                        // We'll accept it only if we need
1079                                                                        // whitespace, and this is an inline
1080                                                                        // element with whitespace only.
1081                                                                        if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1082                                                                        {
1083                                                                                // It must contains spaces and inline elements only.
1084
1085                                                                                siblingText = sibling.getText();
1086
1087                                                                                if ( (/[^\s\ufeff]/).test( siblingText ) )
1088                                                                                        sibling = null;
1089                                                                                else
1090                                                                                {
1091                                                                                        allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
1092                                                                                        for ( i = 0 ; child = allChildren[ i++ ] ; )
1093                                                                                        {
1094                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1095                                                                                                {
1096                                                                                                        sibling = null;
1097                                                                                                        break;
1098                                                                                                }
1099                                                                                        }
1100                                                                                }
1101
1102                                                                                if ( sibling )
1103                                                                                        isWhiteSpace = !!siblingText.length;
1104                                                                        }
1105                                                                        else
1106                                                                                sibling = null;
1107                                                                }
1108                                                        }
1109
1110                                                        if ( isWhiteSpace )
1111                                                        {
1112                                                                if ( needsWhiteSpace )
1113                                                                {
1114                                                                        if ( commonReached )
1115                                                                                endTop = enlargeable;
1116                                                                        else
1117                                                                                this.setEndAfter( enlargeable );
1118                                                                }
1119                                                        }
1120
1121                                                        if ( sibling )
1122                                                        {
1123                                                                next = sibling.getNext();
1124
1125                                                                if ( !enlargeable && !next )
1126                                                                {
1127                                                                        enlargeable = sibling;
1128                                                                        sibling = null;
1129                                                                        break;
1130                                                                }
1131
1132                                                                sibling = next;
1133                                                        }
1134                                                        else
1135                                                        {
1136                                                                // If sibling has been set to null, then we
1137                                                                // need to stop enlarging.
1138                                                                enlargeable = null;
1139                                                        }
1140                                                }
1141
1142                                                if ( enlargeable )
1143                                                        enlargeable = enlargeable.getParent();
1144                                        }
1145
1146                                        // If the common ancestor can be enlarged by both boundaries, then include it also.
1147                                        if ( startTop && endTop )
1148                                        {
1149                                                commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1150
1151                                                this.setStartBefore( commonAncestor );
1152                                                this.setEndAfter( commonAncestor );
1153                                        }
1154                                        break;
1155
1156                                case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1157                                case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1158
1159                                        // Enlarging the start boundary.
1160                                        var walkerRange = new CKEDITOR.dom.range( this.document );
1161
1162                                        body = this.document.getBody();
1163
1164                                        walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1165                                        walkerRange.setEnd( this.startContainer, this.startOffset );
1166
1167                                        var walker = new CKEDITOR.dom.walker( walkerRange ),
1168                                            blockBoundary,  // The node on which the enlarging should stop.
1169                                                tailBr, //
1170                                            defaultGuard = CKEDITOR.dom.walker.blockBoundary(
1171                                                                ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1172                                                // Record the encountered 'blockBoundary' for later use.
1173                                                boundaryGuard = function( node )
1174                                                {
1175                                                        var retval = defaultGuard( node );
1176                                                        if ( !retval )
1177                                                                blockBoundary = node;
1178                                                        return retval;
1179                                                },
1180                                                // Record the encounted 'tailBr' for later use.
1181                                                tailBrGuard = function( node )
1182                                                {
1183                                                        var retval = boundaryGuard( node );
1184                                                        if ( !retval && node.is && node.is( 'br' ) )
1185                                                                tailBr = node;
1186                                                        return retval;
1187                                                };
1188
1189                                        walker.guard = boundaryGuard;
1190
1191                                        enlargeable = walker.lastBackward();
1192
1193                                        // It's the body which stop the enlarging if no block boundary found.
1194                                        blockBoundary = blockBoundary || body;
1195
1196                                        // Start the range at different position by comparing
1197                                        // the document position of it with 'enlargeable' node.
1198                                        this.setStartAt(
1199                                                        blockBoundary,
1200                                                        !blockBoundary.is( 'br' ) &&
1201                                                        ( !enlargeable && this.checkStartOfBlock()
1202                                                          || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1203                                                                CKEDITOR.POSITION_AFTER_START :
1204                                                                CKEDITOR.POSITION_AFTER_END );
1205
1206                                        // Enlarging the end boundary.
1207                                        walkerRange = this.clone();
1208                                        walkerRange.collapse();
1209                                        walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1210                                        walker = new CKEDITOR.dom.walker( walkerRange );
1211
1212                                        // tailBrGuard only used for on range end.
1213                                        walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1214                                                tailBrGuard : boundaryGuard;
1215                                        blockBoundary = null;
1216                                        // End the range right before the block boundary node.
1217
1218                                        enlargeable = walker.lastForward();
1219
1220                                        // It's the body which stop the enlarging if no block boundary found.
1221                                        blockBoundary = blockBoundary || body;
1222
1223                                        // Start the range at different position by comparing
1224                                        // the document position of it with 'enlargeable' node.
1225                                        this.setEndAt(
1226                                                        blockBoundary,
1227                                                        ( !enlargeable && this.checkEndOfBlock()
1228                                                          || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1229                                                                CKEDITOR.POSITION_BEFORE_END :
1230                                                                CKEDITOR.POSITION_BEFORE_START );
1231                                        // We must include the <br> at the end of range if there's
1232                                        // one and we're expanding list item contents
1233                                        if ( tailBr )
1234                                                this.setEndAfter( tailBr );
1235                        }
1236                },
1237
1238                /**
1239                 *  Descrease the range to make sure that boundaries
1240                 *  always anchor beside text nodes or innermost element.
1241                 * @param {Number} mode  ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
1242                 */
1243                shrink : function( mode )
1244                {
1245                        // Unable to shrink a collapsed range.
1246                        if ( !this.collapsed )
1247                        {
1248                                mode = mode || CKEDITOR.SHRINK_TEXT;
1249
1250                                var walkerRange = this.clone();
1251
1252                                var startContainer = this.startContainer,
1253                                        endContainer = this.endContainer,
1254                                        startOffset = this.startOffset,
1255                                        endOffset = this.endOffset,
1256                                        collapsed = this.collapsed;
1257
1258                                // Whether the start/end boundary is moveable.
1259                                var moveStart = 1,
1260                                                moveEnd = 1;
1261
1262                                if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1263                                {
1264                                        if ( !startOffset )
1265                                                walkerRange.setStartBefore( startContainer );
1266                                        else if ( startOffset >= startContainer.getLength( ) )
1267                                                walkerRange.setStartAfter( startContainer );
1268                                        else
1269                                        {
1270                                                // Enlarge the range properly to avoid walker making
1271                                                // DOM changes caused by triming the text nodes later.
1272                                                walkerRange.setStartBefore( startContainer );
1273                                                moveStart = 0;
1274                                        }
1275                                }
1276
1277                                if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1278                                {
1279                                        if ( !endOffset )
1280                                                walkerRange.setEndBefore( endContainer );
1281                                        else if ( endOffset >= endContainer.getLength( ) )
1282                                                walkerRange.setEndAfter( endContainer );
1283                                        else
1284                                        {
1285                                                walkerRange.setEndAfter( endContainer );
1286                                                moveEnd = 0;
1287                                        }
1288                                }
1289
1290                                var walker = new CKEDITOR.dom.walker( walkerRange );
1291
1292                                walker.evaluator = function( node )
1293                                {
1294                                        return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
1295                                                CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
1296                                };
1297
1298                                var currentElement;
1299                                walker.guard = function( node, movingOut )
1300                                {
1301                                        // Stop when we're shrink in element mode while encountering a text node.
1302                                        if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
1303                                                return false;
1304
1305                                        // Stop when we've already walked "through" an element.
1306                                        if ( movingOut && node.equals( currentElement ) )
1307                                                return false;
1308
1309                                        if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
1310                                                currentElement = node;
1311
1312                                        return true;
1313                                };
1314
1315                                if ( moveStart )
1316                                {
1317                                        var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
1318                                        textStart && this.setStartBefore( textStart );
1319                                }
1320
1321                                if ( moveEnd )
1322                                {
1323                                        walker.reset();
1324                                        var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
1325                                        textEnd && this.setEndAfter( textEnd );
1326                                }
1327
1328                                return !!( moveStart || moveEnd );
1329                        }
1330                },
1331
1332                /**
1333                 * Inserts a node at the start of the range. The range will be expanded
1334                 * the contain the node.
1335                 */
1336                insertNode : function( node )
1337                {
1338                        this.optimizeBookmark();
1339                        this.trim( false, true );
1340
1341                        var startContainer = this.startContainer;
1342                        var startOffset = this.startOffset;
1343
1344                        var nextNode = startContainer.getChild( startOffset );
1345
1346                        if ( nextNode )
1347                                node.insertBefore( nextNode );
1348                        else
1349                                startContainer.append( node );
1350
1351                        // Check if we need to update the end boundary.
1352                        if ( node.getParent().equals( this.endContainer ) )
1353                                this.endOffset++;
1354
1355                        // Expand the range to embrace the new node.
1356                        this.setStartBefore( node );
1357                },
1358
1359                moveToPosition : function( node, position )
1360                {
1361                        this.setStartAt( node, position );
1362                        this.collapse( true );
1363                },
1364
1365                selectNodeContents : function( node )
1366                {
1367                        this.setStart( node, 0 );
1368                        this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1369                },
1370
1371                /**
1372                 * Sets the start position of a Range.
1373                 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1374                 * @param {Number} startOffset An integer greater than or equal to zero
1375                 *              representing the offset for the start of the range from the start
1376                 *              of startNode.
1377                 */
1378                setStart : function( startNode, startOffset )
1379                {
1380                        // W3C requires a check for the new position. If it is after the end
1381                        // boundary, the range should be collapsed to the new start. It seams
1382                        // we will not need this check for our use of this class so we can
1383                        // ignore it for now.
1384
1385                        this.startContainer     = startNode;
1386                        this.startOffset        = startOffset;
1387
1388                        if ( !this.endContainer )
1389                        {
1390                                this.endContainer       = startNode;
1391                                this.endOffset          = startOffset;
1392                        }
1393
1394                        updateCollapsed( this );
1395                },
1396
1397                /**
1398                 * Sets the end position of a Range.
1399                 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1400                 * @param {Number} endOffset An integer greater than or equal to zero
1401                 *              representing the offset for the end of the range from the start
1402                 *              of endNode.
1403                 */
1404                setEnd : function( endNode, endOffset )
1405                {
1406                        // W3C requires a check for the new position. If it is before the start
1407                        // boundary, the range should be collapsed to the new end. It seams we
1408                        // will not need this check for our use of this class so we can ignore
1409                        // it for now.
1410
1411                        this.endContainer       = endNode;
1412                        this.endOffset          = endOffset;
1413
1414                        if ( !this.startContainer )
1415                        {
1416                                this.startContainer     = endNode;
1417                                this.startOffset        = endOffset;
1418                        }
1419
1420                        updateCollapsed( this );
1421                },
1422
1423                setStartAfter : function( node )
1424                {
1425                        this.setStart( node.getParent(), node.getIndex() + 1 );
1426                },
1427
1428                setStartBefore : function( node )
1429                {
1430                        this.setStart( node.getParent(), node.getIndex() );
1431                },
1432
1433                setEndAfter : function( node )
1434                {
1435                        this.setEnd( node.getParent(), node.getIndex() + 1 );
1436                },
1437
1438                setEndBefore : function( node )
1439                {
1440                        this.setEnd( node.getParent(), node.getIndex() );
1441                },
1442
1443                setStartAt : function( node, position )
1444                {
1445                        switch( position )
1446                        {
1447                                case CKEDITOR.POSITION_AFTER_START :
1448                                        this.setStart( node, 0 );
1449                                        break;
1450
1451                                case CKEDITOR.POSITION_BEFORE_END :
1452                                        if ( node.type == CKEDITOR.NODE_TEXT )
1453                                                this.setStart( node, node.getLength() );
1454                                        else
1455                                                this.setStart( node, node.getChildCount() );
1456                                        break;
1457
1458                                case CKEDITOR.POSITION_BEFORE_START :
1459                                        this.setStartBefore( node );
1460                                        break;
1461
1462                                case CKEDITOR.POSITION_AFTER_END :
1463                                        this.setStartAfter( node );
1464                        }
1465
1466                        updateCollapsed( this );
1467                },
1468
1469                setEndAt : function( node, position )
1470                {
1471                        switch( position )
1472                        {
1473                                case CKEDITOR.POSITION_AFTER_START :
1474                                        this.setEnd( node, 0 );
1475                                        break;
1476
1477                                case CKEDITOR.POSITION_BEFORE_END :
1478                                        if ( node.type == CKEDITOR.NODE_TEXT )
1479                                                this.setEnd( node, node.getLength() );
1480                                        else
1481                                                this.setEnd( node, node.getChildCount() );
1482                                        break;
1483
1484                                case CKEDITOR.POSITION_BEFORE_START :
1485                                        this.setEndBefore( node );
1486                                        break;
1487
1488                                case CKEDITOR.POSITION_AFTER_END :
1489                                        this.setEndAfter( node );
1490                        }
1491
1492                        updateCollapsed( this );
1493                },
1494
1495                fixBlock : function( isStart, blockTag )
1496                {
1497                        var bookmark = this.createBookmark(),
1498                                fixedBlock = this.document.createElement( blockTag );
1499
1500                        this.collapse( isStart );
1501
1502                        this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1503
1504                        this.extractContents().appendTo( fixedBlock );
1505                        fixedBlock.trim();
1506
1507                        if ( !CKEDITOR.env.ie )
1508                                fixedBlock.appendBogus();
1509
1510                        this.insertNode( fixedBlock );
1511
1512                        this.moveToBookmark( bookmark );
1513
1514                        return fixedBlock;
1515                },
1516
1517                splitBlock : function( blockTag )
1518                {
1519                        var startPath   = new CKEDITOR.dom.elementPath( this.startContainer ),
1520                                endPath         = new CKEDITOR.dom.elementPath( this.endContainer );
1521
1522                        var startBlockLimit     = startPath.blockLimit,
1523                                endBlockLimit   = endPath.blockLimit;
1524
1525                        var startBlock  = startPath.block,
1526                                endBlock        = endPath.block;
1527
1528                        var elementPath = null;
1529                        // Do nothing if the boundaries are in different block limits.
1530                        if ( !startBlockLimit.equals( endBlockLimit ) )
1531                                return null;
1532
1533                        // Get or fix current blocks.
1534                        if ( blockTag != 'br' )
1535                        {
1536                                if ( !startBlock )
1537                                {
1538                                        startBlock = this.fixBlock( true, blockTag );
1539                                        endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1540                                }
1541
1542                                if ( !endBlock )
1543                                        endBlock = this.fixBlock( false, blockTag );
1544                        }
1545
1546                        // Get the range position.
1547                        var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1548                                isEndOfBlock = endBlock && this.checkEndOfBlock();
1549
1550                        // Delete the current contents.
1551                        // TODO: Why is 2.x doing CheckIsEmpty()?
1552                        this.deleteContents();
1553
1554                        if ( startBlock && startBlock.equals( endBlock ) )
1555                        {
1556                                if ( isEndOfBlock )
1557                                {
1558                                        elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1559                                        this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1560                                        endBlock = null;
1561                                }
1562                                else if ( isStartOfBlock )
1563                                {
1564                                        elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1565                                        this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1566                                        startBlock = null;
1567                                }
1568                                else
1569                                {
1570                                        endBlock = this.splitElement( startBlock );
1571
1572                                        // In Gecko, the last child node must be a bogus <br>.
1573                                        // Note: bogus <br> added under <ul> or <ol> would cause
1574                                        // lists to be incorrectly rendered.
1575                                        if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1576                                                startBlock.appendBogus() ;
1577                                }
1578                        }
1579
1580                        return {
1581                                previousBlock : startBlock,
1582                                nextBlock : endBlock,
1583                                wasStartOfBlock : isStartOfBlock,
1584                                wasEndOfBlock : isEndOfBlock,
1585                                elementPath : elementPath
1586                        };
1587                },
1588
1589                /**
1590                 * Branch the specified element from the collapsed range position and
1591                 * place the caret between the two result branches.
1592                 * Note: The range must be collapsed and been enclosed by this element.
1593                 * @param {CKEDITOR.dom.element} element
1594                 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
1595                 */
1596                splitElement : function( toSplit )
1597                {
1598                        if ( !this.collapsed )
1599                                return null;
1600
1601                        // Extract the contents of the block from the selection point to the end
1602                        // of its contents.
1603                        this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
1604                        var documentFragment = this.extractContents();
1605
1606                        // Duplicate the element after it.
1607                        var clone = toSplit.clone( false );
1608
1609                        // Place the extracted contents into the duplicated element.
1610                        documentFragment.appendTo( clone );
1611                        clone.insertAfter( toSplit );
1612                        this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
1613                        return clone;
1614                },
1615
1616                /**
1617                 * Check whether current range is on the inner edge of the specified element.
1618                 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
1619                 * @param {CKEDITOR.dom.element} element The target element to check.
1620                 */
1621                checkBoundaryOfElement : function( element, checkType )
1622                {
1623                        var walkerRange = this.clone();
1624                        // Expand the range to element boundary.
1625                        walkerRange[ checkType == CKEDITOR.START ?
1626                         'setStartAt' : 'setEndAt' ]
1627                         ( element, checkType == CKEDITOR.START ?
1628                           CKEDITOR.POSITION_AFTER_START
1629                           : CKEDITOR.POSITION_BEFORE_END );
1630
1631                        var walker = new CKEDITOR.dom.walker( walkerRange ),
1632                         retval = false;
1633                        walker.evaluator = elementBoundaryEval;
1634                        return walker[ checkType == CKEDITOR.START ?
1635                                'checkBackward' : 'checkForward' ]();
1636                },
1637                // Calls to this function may produce changes to the DOM. The range may
1638                // be updated to reflect such changes.
1639                checkStartOfBlock : function()
1640                {
1641                        var startContainer = this.startContainer,
1642                                startOffset = this.startOffset;
1643
1644                        // If the starting node is a text node, and non-empty before the offset,
1645                        // then we're surely not at the start of block.
1646                        if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1647                        {
1648                                var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1649                                if ( textBefore.length )
1650                                        return false;
1651                        }
1652
1653                        // Antecipate the trim() call here, so the walker will not make
1654                        // changes to the DOM, which would not get reflected into this
1655                        // range otherwise.
1656                        this.trim();
1657
1658                        // We need to grab the block element holding the start boundary, so
1659                        // let's use an element path for it.
1660                        var path = new CKEDITOR.dom.elementPath( this.startContainer );
1661
1662                        // Creates a range starting at the block start until the range start.
1663                        var walkerRange = this.clone();
1664                        walkerRange.collapse( true );
1665                        walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1666
1667                        var walker = new CKEDITOR.dom.walker( walkerRange );
1668                        walker.evaluator = getCheckStartEndBlockEvalFunction( true );
1669
1670                        return walker.checkBackward();
1671                },
1672
1673                checkEndOfBlock : function()
1674                {
1675                        var endContainer = this.endContainer,
1676                                endOffset = this.endOffset;
1677
1678                        // If the ending node is a text node, and non-empty after the offset,
1679                        // then we're surely not at the end of block.
1680                        if ( endContainer.type == CKEDITOR.NODE_TEXT )
1681                        {
1682                                var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1683                                if ( textAfter.length )
1684                                        return false;
1685                        }
1686
1687                        // Antecipate the trim() call here, so the walker will not make
1688                        // changes to the DOM, which would not get reflected into this
1689                        // range otherwise.
1690                        this.trim();
1691
1692                        // We need to grab the block element holding the start boundary, so
1693                        // let's use an element path for it.
1694                        var path = new CKEDITOR.dom.elementPath( this.endContainer );
1695
1696                        // Creates a range starting at the block start until the range start.
1697                        var walkerRange = this.clone();
1698                        walkerRange.collapse( false );
1699                        walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1700
1701                        var walker = new CKEDITOR.dom.walker( walkerRange );
1702                        walker.evaluator = getCheckStartEndBlockEvalFunction( false );
1703
1704                        return walker.checkForward();
1705                },
1706
1707                /**
1708                 * Moves the range boundaries to the first/end editing point inside an
1709                 * element. For example, in an element tree like
1710                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;", the start editing point is
1711                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;^&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;" (inside &lt;i&gt;).
1712                 * @param {CKEDITOR.dom.element} el The element into which look for the
1713                 *              editing spot.
1714                 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
1715                 */
1716                moveToElementEditablePosition : function( el, isMoveToEnd )
1717                {
1718                        var isEditable;
1719
1720                        // Empty elements are rejected.
1721                        if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1722                                return false;
1723
1724                        while ( el && el.type == CKEDITOR.NODE_ELEMENT )
1725                        {
1726                                isEditable = el.isEditable();
1727
1728                                // If an editable element is found, move inside it.
1729                                if ( isEditable )
1730                                        this.moveToPosition( el, isMoveToEnd ?
1731                                                                 CKEDITOR.POSITION_BEFORE_END :
1732                                                                 CKEDITOR.POSITION_AFTER_START );
1733                                // Stop immediately if we've found a non editable inline element (e.g <img>).
1734                                else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
1735                                {
1736                                        this.moveToPosition( el, isMoveToEnd ?
1737                                                                 CKEDITOR.POSITION_AFTER_END :
1738                                                                 CKEDITOR.POSITION_BEFORE_START );
1739                                        return true;
1740                                }
1741
1742                                // Non-editable non-inline elements are to be bypassed, getting the next one.
1743                                if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1744                                        el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
1745                                else
1746                                        el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
1747
1748                                // Stop immediately if we've found a text node.
1749                                if ( el && el.type == CKEDITOR.NODE_TEXT )
1750                                {
1751                                        this.moveToPosition( el, isMoveToEnd ?
1752                                                                 CKEDITOR.POSITION_AFTER_END :
1753                                                                 CKEDITOR.POSITION_BEFORE_START );
1754                                        return true;
1755                                }
1756                        }
1757
1758                        return isEditable;
1759                },
1760
1761                /**
1762                 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
1763                 */
1764                moveToElementEditStart : function( target )
1765                {
1766                        return this.moveToElementEditablePosition( target );
1767                },
1768
1769                /**
1770                 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
1771                 */
1772                moveToElementEditEnd : function( target )
1773                {
1774                        return this.moveToElementEditablePosition( target, true );
1775                },
1776
1777                /**
1778                 * Get the single node enclosed within the range if there's one.
1779                 */
1780                getEnclosedNode : function()
1781                {
1782                        var walkerRange = this.clone(),
1783                                walker = new CKEDITOR.dom.walker( walkerRange ),
1784                                isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
1785                                isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1786                                evaluator = function( node )
1787                                {
1788                                        return isNotWhitespaces( node ) && isNotBookmarks( node );
1789                                };
1790                        walkerRange.evaluator = evaluator;
1791                        var node = walker.next();
1792                        walker.reset();
1793                        return node && node.equals( walker.previous() ) ? node : null;
1794                },
1795
1796                getTouchedStartNode : function()
1797                {
1798                        var container = this.startContainer ;
1799
1800                        if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1801                                return container ;
1802
1803                        return container.getChild( this.startOffset ) || container ;
1804                },
1805
1806                getTouchedEndNode : function()
1807                {
1808                        var container = this.endContainer ;
1809
1810                        if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1811                                return container ;
1812
1813                        return container.getChild( this.endOffset - 1 ) || container ;
1814                }
1815        };
1816})();
1817
1818CKEDITOR.POSITION_AFTER_START   = 1;    // <element>^contents</element>         "^text"
1819CKEDITOR.POSITION_BEFORE_END    = 2;    // <element>contents^</element>         "text^"
1820CKEDITOR.POSITION_BEFORE_START  = 3;    // ^<element>contents</element>         ^"text"
1821CKEDITOR.POSITION_AFTER_END             = 4;    // <element>contents</element>^         "text"
1822
1823CKEDITOR.ENLARGE_ELEMENT = 1;
1824CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
1825CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
1826
1827/**
1828 * Check boundary types.
1829 * @see CKEDITOR.dom.range::checkBoundaryOfElement
1830 */
1831CKEDITOR.START = 1;
1832CKEDITOR.END = 2;
1833CKEDITOR.STARTEND = 3;
1834
1835CKEDITOR.SHRINK_ELEMENT = 1;
1836CKEDITOR.SHRINK_TEXT = 2;
Note: See TracBrowser for help on using the repository browser.