source: branches/2.2/filemanager/tp/ckeditor/_source/core/dom/range.js @ 3019

Revision 3019, 49.9 KB checked in by amuller, 14 years ago (diff)

Ticket #1135 - Corrigindo CSS e adicionando filemanager

Line 
1/*
2Copyright (c) 2003-2009, 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
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        CKEDITOR.dom.range.prototype =
312        {
313                clone : function()
314                {
315                        var clone = new CKEDITOR.dom.range( this.document );
316
317                        clone.startContainer = this.startContainer;
318                        clone.startOffset = this.startOffset;
319                        clone.endContainer = this.endContainer;
320                        clone.endOffset = this.endOffset;
321                        clone.collapsed = this.collapsed;
322
323                        return clone;
324                },
325
326                collapse : function( toStart )
327                {
328                        if ( toStart )
329                        {
330                                this.endContainer       = this.startContainer;
331                                this.endOffset          = this.startOffset;
332                        }
333                        else
334                        {
335                                this.startContainer     = this.endContainer;
336                                this.startOffset        = this.endOffset;
337                        }
338
339                        this.collapsed = true;
340                },
341
342                // The selection may be lost when cloning (due to the splitText() call).
343                cloneContents : function()
344                {
345                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );
346
347                        if ( !this.collapsed )
348                                execContentsAction( this, 2, docFrag );
349
350                        return docFrag;
351                },
352
353                deleteContents : function()
354                {
355                        if ( this.collapsed )
356                                return;
357
358                        execContentsAction( this, 0 );
359                },
360
361                extractContents : function()
362                {
363                        var docFrag = new CKEDITOR.dom.documentFragment( this.document );
364
365                        if ( !this.collapsed )
366                                execContentsAction( this, 1, docFrag );
367
368                        return docFrag;
369                },
370
371                /**
372                 * Creates a bookmark object, which can be later used to restore the
373                 * range by using the moveToBookmark function.
374                 * This is an "intrusive" way to create a bookmark. It includes <span> tags
375                 * in the range boundaries. The advantage of it is that it is possible to
376                 * handle DOM mutations when moving back to the bookmark.
377                 * Attention: the inclusion of nodes in the DOM is a design choice and
378                 * should not be changed as there are other points in the code that may be
379                 * using those nodes to perform operations. See GetBookmarkNode.
380                 * @param {Boolean} [serializable] Indicates that the bookmark nodes
381                 *              must contain ids, which can be used to restore the range even
382                 *              when these nodes suffer mutations (like a clonation or innerHTML
383                 *              change).
384                 * @returns {Object} And object representing a bookmark.
385                 */
386                createBookmark : function( serializable )
387                {
388                        var startNode, endNode;
389                        var baseId;
390                        var clone;
391
392                        startNode = this.document.createElement( 'span' );
393                        startNode.setAttribute( '_fck_bookmark', 1 );
394                        startNode.setStyle( 'display', 'none' );
395
396                        // For IE, it must have something inside, otherwise it may be
397                        // removed during DOM operations.
398                        startNode.setHtml( '&nbsp;' );
399
400                        if ( serializable )
401                        {
402                                baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
403                                startNode.setAttribute( 'id', baseId + 'S' );
404                        }
405
406                        // If collapsed, the endNode will not be created.
407                        if ( !this.collapsed )
408                        {
409                                endNode = startNode.clone();
410                                endNode.setHtml( '&nbsp;' );
411
412                                if ( serializable )
413                                        endNode.setAttribute( 'id', baseId + 'E' );
414
415                                clone = this.clone();
416                                clone.collapse();
417                                clone.insertNode( endNode );
418                        }
419
420                        clone = this.clone();
421                        clone.collapse( true );
422                        clone.insertNode( startNode );
423
424                        // Update the range position.
425                        if ( endNode )
426                        {
427                                this.setStartAfter( startNode );
428                                this.setEndBefore( endNode );
429                        }
430                        else
431                                this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
432
433                        return {
434                                startNode : serializable ? baseId + 'S' : startNode,
435                                endNode : serializable ? baseId + 'E' : endNode,
436                                serializable : serializable
437                        };
438                },
439
440                /**
441                 * Creates a "non intrusive" and "mutation sensible" bookmark. This
442                 * kind of bookmark should be used only when the DOM is supposed to
443                 * remain stable after its creation.
444                 * @param {Boolean} [normalized] Indicates that the bookmark must
445                 *              normalized. When normalized, the successive text nodes are
446                 *              considered a single node. To sucessful load a normalized
447                 *              bookmark, the DOM tree must be also normalized before calling
448                 *              moveToBookmark.
449                 * @returns {Object} An object representing the bookmark.
450                 */
451                createBookmark2 : function( normalized )
452                {
453                        var startContainer      = this.startContainer,
454                                endContainer    = this.endContainer;
455
456                        var startOffset = this.startOffset,
457                                endOffset       = this.endOffset;
458
459                        var child, previous;
460
461                        // If there is no range then get out of here.
462                        // It happens on initial load in Safari #962 and if the editor it's
463                        // hidden also in Firefox
464                        if ( !startContainer || !endContainer )
465                                return { start : 0, end : 0 };
466
467                        if ( normalized )
468                        {
469                                // Find out if the start is pointing to a text node that will
470                                // be normalized.
471                                if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
472                                {
473                                        child = startContainer.getChild( startOffset );
474
475                                        // In this case, move the start information to that text
476                                        // node.
477                                        if ( child && child.type == CKEDITOR.NODE_TEXT
478                                                        && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
479                                        {
480                                                startContainer = child;
481                                                startOffset = 0;
482                                        }
483                                }
484
485                                // Normalize the start.
486                                while ( startContainer.type == CKEDITOR.NODE_TEXT
487                                                && ( previous = startContainer.getPrevious() )
488                                                && previous.type == CKEDITOR.NODE_TEXT )
489                                {
490                                        startContainer = previous;
491                                        startOffset += previous.getLength();
492                                }
493
494                                // Process the end only if not normalized.
495                                if ( !this.isCollapsed )
496                                {
497                                        // Find out if the start is pointing to a text node that
498                                        // will be normalized.
499                                        if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
500                                        {
501                                                child = endContainer.getChild( endOffset );
502
503                                                // In this case, move the start information to that
504                                                // text node.
505                                                if ( child && child.type == CKEDITOR.NODE_TEXT
506                                                                && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
507                                                {
508                                                        endContainer = child;
509                                                        endOffset = 0;
510                                                }
511                                        }
512
513                                        // Normalize the end.
514                                        while ( endContainer.type == CKEDITOR.NODE_TEXT
515                                                        && ( previous = endContainer.getPrevious() )
516                                                        && previous.type == CKEDITOR.NODE_TEXT )
517                                        {
518                                                endContainer = previous;
519                                                endOffset += previous.getLength();
520                                        }
521                                }
522                        }
523
524                        return {
525                                start           : startContainer.getAddress( normalized ),
526                                end                     : this.isCollapsed ? null : endContainer.getAddress( normalized ),
527                                startOffset     : startOffset,
528                                endOffset       : endOffset,
529                                normalized      : normalized,
530                                is2                     : true          // It's a createBookmark2 bookmark.
531                        };
532                },
533
534                moveToBookmark : function( bookmark )
535                {
536                        if ( bookmark.is2 )             // Created with createBookmark2().
537                        {
538                                // Get the start information.
539                                var startContainer      = this.document.getByAddress( bookmark.start, bookmark.normalized ),
540                                        startOffset     = bookmark.startOffset;
541
542                                // Get the end information.
543                                var endContainer        = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
544                                        endOffset       = bookmark.endOffset;
545
546                                // Set the start boundary.
547                                this.setStart( startContainer, startOffset );
548
549                                // Set the end boundary. If not available, collapse it.
550                                if ( endContainer )
551                                        this.setEnd( endContainer, endOffset );
552                                else
553                                        this.collapse( true );
554                        }
555                        else                                    // Created with createBookmark().
556                        {
557                                var serializable = bookmark.serializable,
558                                        startNode       = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
559                                        endNode         = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
560
561                                // Set the range start at the bookmark start node position.
562                                this.setStartBefore( startNode );
563
564                                // Remove it, because it may interfere in the setEndBefore call.
565                                startNode.remove();
566
567                                // Set the range end at the bookmark end node position, or simply
568                                // collapse it if it is not available.
569                                if ( endNode )
570                                {
571                                        this.setEndBefore( endNode );
572                                        endNode.remove();
573                                }
574                                else
575                                        this.collapse( true );
576                        }
577                },
578
579                getBoundaryNodes : function()
580                {
581                        var startNode = this.startContainer,
582                                endNode = this.endContainer,
583                                startOffset = this.startOffset,
584                                endOffset = this.endOffset,
585                                childCount;
586
587                        if ( startNode.type == CKEDITOR.NODE_ELEMENT )
588                        {
589                                childCount = startNode.getChildCount();
590                                if ( childCount > startOffset )
591                                        startNode = startNode.getChild( startOffset );
592                                else if ( childCount < 1 )
593                                        startNode = startNode.getPreviousSourceNode();
594                                else            // startOffset > childCount but childCount is not 0
595                                {
596                                        // Try to take the node just after the current position.
597                                        startNode = startNode.$;
598                                        while ( startNode.lastChild )
599                                                startNode = startNode.lastChild;
600                                        startNode = new CKEDITOR.dom.node( startNode );
601
602                                        // Normally we should take the next node in DFS order. But it
603                                        // is also possible that we've already reached the end of
604                                        // document.
605                                        startNode = startNode.getNextSourceNode() || startNode;
606                                }
607                        }
608                        if ( endNode.type == CKEDITOR.NODE_ELEMENT )
609                        {
610                                childCount = endNode.getChildCount();
611                                if ( childCount > endOffset )
612                                        endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
613                                else if ( childCount < 1 )
614                                        endNode = endNode.getPreviousSourceNode();
615                                else            // endOffset > childCount but childCount is not 0
616                                {
617                                        // Try to take the node just before the current position.
618                                        endNode = endNode.$;
619                                        while ( endNode.lastChild )
620                                                endNode = endNode.lastChild;
621                                        endNode = new CKEDITOR.dom.node( endNode );
622                                }
623                        }
624
625                        // Sometimes the endNode will come right before startNode for collapsed
626                        // ranges. Fix it. (#3780)
627                        if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
628                                startNode = endNode;
629
630                        return { startNode : startNode, endNode : endNode };
631                },
632
633                /**
634                 * Find the node which fully contains the range.
635                 * @param includeSelf
636                 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
637                 */
638                getCommonAncestor : function( includeSelf , ignoreTextNode )
639                {
640                        var start = this.startContainer,
641                                end = this.endContainer,
642                                ancestor;
643
644                        if ( start.equals( end ) )
645                        {
646                                if ( includeSelf
647                                                && start.type == CKEDITOR.NODE_ELEMENT
648                                                && this.startOffset == this.endOffset - 1 )
649                                        ancestor = start.getChild( this.startOffset );
650                                else
651                                        ancestor = start;
652                        }
653                        else
654                                ancestor = start.getCommonAncestor( end );
655
656                        return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
657                },
658
659                /**
660                 * Transforms the startContainer and endContainer properties from text
661                 * nodes to element nodes, whenever possible. This is actually possible
662                 * if either of the boundary containers point to a text node, and its
663                 * offset is set to zero, or after the last char in the node.
664                 */
665                optimize : function()
666                {
667                        var container = this.startContainer;
668                        var offset = this.startOffset;
669
670                        if ( container.type != CKEDITOR.NODE_ELEMENT )
671                        {
672                                if ( !offset )
673                                        this.setStartBefore( container );
674                                else if ( offset >= container.getLength() )
675                                        this.setStartAfter( container );
676                        }
677
678                        container = this.endContainer;
679                        offset = this.endOffset;
680
681                        if ( container.type != CKEDITOR.NODE_ELEMENT )
682                        {
683                                if ( !offset )
684                                        this.setEndBefore( container );
685                                else if ( offset >= container.getLength() )
686                                        this.setEndAfter( container );
687                        }
688                },
689
690                /**
691                 * Move the range out of bookmark nodes if they're been the container.
692                 */
693                optimizeBookmark: function()
694                {
695                        var startNode = this.startContainer,
696                                endNode = this.endContainer;
697
698                        if ( startNode.is && startNode.is( 'span' )
699                                && startNode.hasAttribute( '_fck_bookmark' ) )
700                                this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
701                        if ( endNode && endNode.is && endNode.is( 'span' )
702                                && endNode.hasAttribute( '_fck_bookmark' ) )
703                                this.setEndAt( endNode,  CKEDITOR.POSITION_AFTER_END );
704                },
705
706                trim : function( ignoreStart, ignoreEnd )
707                {
708                        var startContainer = this.startContainer,
709                                startOffset = this.startOffset,
710                                collapsed = this.collapsed;
711                        if ( ( !ignoreStart || collapsed )
712                                 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
713                        {
714                                // If the offset is zero, we just insert the new node before
715                                // the start.
716                                if ( !startOffset )
717                                {
718                                        startOffset = startContainer.getIndex();
719                                        startContainer = startContainer.getParent();
720                                }
721                                // If the offset is at the end, we'll insert it after the text
722                                // node.
723                                else if ( startOffset >= startContainer.getLength() )
724                                {
725                                        startOffset = startContainer.getIndex() + 1;
726                                        startContainer = startContainer.getParent();
727                                }
728                                // In other case, we split the text node and insert the new
729                                // node at the split point.
730                                else
731                                {
732                                        var nextText = startContainer.split( startOffset );
733
734                                        startOffset = startContainer.getIndex() + 1;
735                                        startContainer = startContainer.getParent();
736                                        // Check if it is necessary to update the end boundary.
737                                        if ( !collapsed && this.startContainer.equals( this.endContainer ) )
738                                                this.setEnd( nextText, this.endOffset - this.startOffset );
739                                }
740
741                                this.setStart( startContainer, startOffset );
742
743                                if ( collapsed )
744                                        this.collapse( true );
745                        }
746
747                        var endContainer = this.endContainer;
748                        var endOffset = this.endOffset;
749
750                        if ( !( ignoreEnd || collapsed )
751                                 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
752                        {
753                                // If the offset is zero, we just insert the new node before
754                                // the start.
755                                if ( !endOffset )
756                                {
757                                        endOffset = endContainer.getIndex();
758                                        endContainer = endContainer.getParent();
759                                }
760                                // If the offset is at the end, we'll insert it after the text
761                                // node.
762                                else if ( endOffset >= endContainer.getLength() )
763                                {
764                                        endOffset = endContainer.getIndex() + 1;
765                                        endContainer = endContainer.getParent();
766                                }
767                                // In other case, we split the text node and insert the new
768                                // node at the split point.
769                                else
770                                {
771                                        endContainer.split( endOffset );
772
773                                        endOffset = endContainer.getIndex() + 1;
774                                        endContainer = endContainer.getParent();
775                                }
776
777                                this.setEnd( endContainer, endOffset );
778                        }
779                },
780
781                enlarge : function( unit )
782                {
783                        switch ( unit )
784                        {
785                                case CKEDITOR.ENLARGE_ELEMENT :
786
787                                        if ( this.collapsed )
788                                                return;
789
790                                        // Get the common ancestor.
791                                        var commonAncestor = this.getCommonAncestor();
792
793                                        var body = this.document.getBody();
794
795                                        // For each boundary
796                                        //              a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
797                                        //              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.
798
799                                        var startTop, endTop;
800
801                                        var enlargeable, sibling, commonReached;
802
803                                        // Indicates that the node can be added only if whitespace
804                                        // is available before it.
805                                        var needsWhiteSpace = false;
806                                        var isWhiteSpace;
807                                        var siblingText;
808
809                                        // Process the start boundary.
810
811                                        var container = this.startContainer;
812                                        var offset = this.startOffset;
813
814                                        if ( container.type == CKEDITOR.NODE_TEXT )
815                                        {
816                                                if ( offset )
817                                                {
818                                                        // Check if there is any non-space text before the
819                                                        // offset. Otherwise, container is null.
820                                                        container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
821
822                                                        // If we found only whitespace in the node, it
823                                                        // means that we'll need more whitespace to be able
824                                                        // to expand. For example, <i> can be expanded in
825                                                        // "A <i> [B]</i>", but not in "A<i> [B]</i>".
826                                                        needsWhiteSpace = !!container;
827                                                }
828
829                                                if ( container )
830                                                {
831                                                        if ( !( sibling = container.getPrevious() ) )
832                                                                enlargeable = container.getParent();
833                                                }
834                                        }
835                                        else
836                                        {
837                                                // If we have offset, get the node preceeding it as the
838                                                // first sibling to be checked.
839                                                if ( offset )
840                                                        sibling = container.getChild( offset - 1 ) || container.getLast();
841
842                                                // If there is no sibling, mark the container to be
843                                                // enlarged.
844                                                if ( !sibling )
845                                                        enlargeable = container;
846                                        }
847
848                                        while ( enlargeable || sibling )
849                                        {
850                                                if ( enlargeable && !sibling )
851                                                {
852                                                        // If we reached the common ancestor, mark the flag
853                                                        // for it.
854                                                        if ( !commonReached && enlargeable.equals( commonAncestor ) )
855                                                                commonReached = true;
856
857                                                        if ( !body.contains( enlargeable ) )
858                                                                break;
859
860                                                        // If we don't need space or this element breaks
861                                                        // the line, then enlarge it.
862                                                        if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
863                                                        {
864                                                                needsWhiteSpace = false;
865
866                                                                // If the common ancestor has been reached,
867                                                                // we'll not enlarge it immediately, but just
868                                                                // mark it to be enlarged later if the end
869                                                                // boundary also enlarges it.
870                                                                if ( commonReached )
871                                                                        startTop = enlargeable;
872                                                                else
873                                                                        this.setStartBefore( enlargeable );
874                                                        }
875
876                                                        sibling = enlargeable.getPrevious();
877                                                }
878
879                                                // Check all sibling nodes preceeding the enlargeable
880                                                // node. The node wil lbe enlarged only if none of them
881                                                // blocks it.
882                                                while ( sibling )
883                                                {
884                                                        // This flag indicates that this node has
885                                                        // whitespaces at the end.
886                                                        isWhiteSpace = false;
887
888                                                        if ( sibling.type == CKEDITOR.NODE_TEXT )
889                                                        {
890                                                                siblingText = sibling.getText();
891
892                                                                if ( /[^\s\ufeff]/.test( siblingText ) )
893                                                                        sibling = null;
894
895                                                                isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
896                                                        }
897                                                        else
898                                                        {
899                                                                // If this is a visible element.
900                                                                // We need to check for the bookmark attribute because IE insists on
901                                                                // rendering the display:none nodes we use for bookmarks. (#3363)
902                                                                if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
903                                                                {
904                                                                        // We'll accept it only if we need
905                                                                        // whitespace, and this is an inline
906                                                                        // element with whitespace only.
907                                                                        if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
908                                                                        {
909                                                                                // It must contains spaces and inline elements only.
910
911                                                                                siblingText = sibling.getText();
912
913                                                                                if ( !(/[^\s\ufeff]/).test( siblingText ) )     // Spaces + Zero Width No-Break Space (U+FEFF)
914                                                                                        sibling = null;
915                                                                                else
916                                                                                {
917                                                                                        var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
918                                                                                        for ( var i = 0, child ; child = allChildren[ i++ ] ; )
919                                                                                        {
920                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
921                                                                                                {
922                                                                                                        sibling = null;
923                                                                                                        break;
924                                                                                                }
925                                                                                        }
926                                                                                }
927
928                                                                                if ( sibling )
929                                                                                        isWhiteSpace = !!siblingText.length;
930                                                                        }
931                                                                        else
932                                                                                sibling = null;
933                                                                }
934                                                        }
935
936                                                        // A node with whitespaces has been found.
937                                                        if ( isWhiteSpace )
938                                                        {
939                                                                // Enlarge the last enlargeable node, if we
940                                                                // were waiting for spaces.
941                                                                if ( needsWhiteSpace )
942                                                                {
943                                                                        if ( commonReached )
944                                                                                startTop = enlargeable;
945                                                                        else if ( enlargeable )
946                                                                                this.setStartBefore( enlargeable );
947                                                                }
948                                                                else
949                                                                        needsWhiteSpace = true;
950                                                        }
951
952                                                        if ( sibling )
953                                                        {
954                                                                var next = sibling.getPrevious();
955
956                                                                if ( !enlargeable && !next )
957                                                                {
958                                                                        // Set the sibling as enlargeable, so it's
959                                                                        // parent will be get later outside this while.
960                                                                        enlargeable = sibling;
961                                                                        sibling = null;
962                                                                        break;
963                                                                }
964
965                                                                sibling = next;
966                                                        }
967                                                        else
968                                                        {
969                                                                // If sibling has been set to null, then we
970                                                                // need to stop enlarging.
971                                                                enlargeable = null;
972                                                        }
973                                                }
974
975                                                if ( enlargeable )
976                                                        enlargeable = enlargeable.getParent();
977                                        }
978
979                                        // Process the end boundary. This is basically the same
980                                        // code used for the start boundary, with small changes to
981                                        // make it work in the oposite side (to the right). This
982                                        // makes it difficult to reuse the code here. So, fixes to
983                                        // the above code are likely to be replicated here.
984
985                                        container = this.endContainer;
986                                        offset = this.endOffset;
987
988                                        // Reset the common variables.
989                                        enlargeable = sibling = null;
990                                        commonReached = needsWhiteSpace = false;
991
992                                        if ( container.type == CKEDITOR.NODE_TEXT )
993                                        {
994                                                // Check if there is any non-space text after the
995                                                // offset. Otherwise, container is null.
996                                                container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
997
998                                                // If we found only whitespace in the node, it
999                                                // means that we'll need more whitespace to be able
1000                                                // to expand. For example, <i> can be expanded in
1001                                                // "A <i> [B]</i>", but not in "A<i> [B]</i>".
1002                                                needsWhiteSpace = !( container && container.getLength() );
1003
1004                                                if ( container )
1005                                                {
1006                                                        if ( !( sibling = container.getNext() ) )
1007                                                                enlargeable = container.getParent();
1008                                                }
1009                                        }
1010                                        else
1011                                        {
1012                                                // Get the node right after the boudary to be checked
1013                                                // first.
1014                                                sibling = container.getChild( offset );
1015
1016                                                if ( !sibling )
1017                                                        enlargeable = container;
1018                                        }
1019
1020                                        while ( enlargeable || sibling )
1021                                        {
1022                                                if ( enlargeable && !sibling )
1023                                                {
1024                                                        if ( !commonReached && enlargeable.equals( commonAncestor ) )
1025                                                                commonReached = true;
1026
1027                                                        if ( !body.contains( enlargeable ) )
1028                                                                break;
1029
1030                                                        if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1031                                                        {
1032                                                                needsWhiteSpace = false;
1033
1034                                                                if ( commonReached )
1035                                                                        endTop = enlargeable;
1036                                                                else if ( enlargeable )
1037                                                                        this.setEndAfter( enlargeable );
1038                                                        }
1039
1040                                                        sibling = enlargeable.getNext();
1041                                                }
1042
1043                                                while ( sibling )
1044                                                {
1045                                                        isWhiteSpace = false;
1046
1047                                                        if ( sibling.type == CKEDITOR.NODE_TEXT )
1048                                                        {
1049                                                                siblingText = sibling.getText();
1050
1051                                                                if ( /[^\s\ufeff]/.test( siblingText ) )
1052                                                                        sibling = null;
1053
1054                                                                isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1055                                                        }
1056                                                        else
1057                                                        {
1058                                                                // If this is a visible element.
1059                                                                // We need to check for the bookmark attribute because IE insists on
1060                                                                // rendering the display:none nodes we use for bookmarks. (#3363)
1061                                                                if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
1062                                                                {
1063                                                                        // We'll accept it only if we need
1064                                                                        // whitespace, and this is an inline
1065                                                                        // element with whitespace only.
1066                                                                        if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1067                                                                        {
1068                                                                                // It must contains spaces and inline elements only.
1069
1070                                                                                siblingText = sibling.getText();
1071
1072                                                                                if ( !(/[^\s\ufeff]/).test( siblingText ) )
1073                                                                                        sibling = null;
1074                                                                                else
1075                                                                                {
1076                                                                                        allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
1077                                                                                        for ( i = 0 ; child = allChildren[ i++ ] ; )
1078                                                                                        {
1079                                                                                                if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1080                                                                                                {
1081                                                                                                        sibling = null;
1082                                                                                                        break;
1083                                                                                                }
1084                                                                                        }
1085                                                                                }
1086
1087                                                                                if ( sibling )
1088                                                                                        isWhiteSpace = !!siblingText.length;
1089                                                                        }
1090                                                                        else
1091                                                                                sibling = null;
1092                                                                }
1093                                                        }
1094
1095                                                        if ( isWhiteSpace )
1096                                                        {
1097                                                                if ( needsWhiteSpace )
1098                                                                {
1099                                                                        if ( commonReached )
1100                                                                                endTop = enlargeable;
1101                                                                        else
1102                                                                                this.setEndAfter( enlargeable );
1103                                                                }
1104                                                        }
1105
1106                                                        if ( sibling )
1107                                                        {
1108                                                                next = sibling.getNext();
1109
1110                                                                if ( !enlargeable && !next )
1111                                                                {
1112                                                                        enlargeable = sibling;
1113                                                                        sibling = null;
1114                                                                        break;
1115                                                                }
1116
1117                                                                sibling = next;
1118                                                        }
1119                                                        else
1120                                                        {
1121                                                                // If sibling has been set to null, then we
1122                                                                // need to stop enlarging.
1123                                                                enlargeable = null;
1124                                                        }
1125                                                }
1126
1127                                                if ( enlargeable )
1128                                                        enlargeable = enlargeable.getParent();
1129                                        }
1130
1131                                        // If the common ancestor can be enlarged by both boundaries, then include it also.
1132                                        if ( startTop && endTop )
1133                                        {
1134                                                commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1135
1136                                                this.setStartBefore( commonAncestor );
1137                                                this.setEndAfter( commonAncestor );
1138                                        }
1139                                        break;
1140
1141                                case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1142                                case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1143
1144                                        // Enlarging the start boundary.
1145                                        var walkerRange = new CKEDITOR.dom.range( this.document );
1146
1147                                        body = this.document.getBody();
1148
1149                                        walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1150                                        walkerRange.setEnd( this.startContainer, this.startOffset );
1151
1152                                        var walker = new CKEDITOR.dom.walker( walkerRange ),
1153                                            blockBoundary,  // The node on which the enlarging should stop.
1154                                                tailBr, //
1155                                            defaultGuard = CKEDITOR.dom.walker.blockBoundary(
1156                                                                ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1157                                                // Record the encountered 'blockBoundary' for later use.
1158                                                boundaryGuard = function( node )
1159                                                {
1160                                                        var retval = defaultGuard( node );
1161                                                        if ( !retval )
1162                                                                blockBoundary = node;
1163                                                        return retval;
1164                                                },
1165                                                // Record the encounted 'tailBr' for later use.
1166                                                tailBrGuard = function( node )
1167                                                {
1168                                                        var retval = boundaryGuard( node );
1169                                                        if ( !retval && node.is && node.is( 'br' ) )
1170                                                                tailBr = node;
1171                                                        return retval;
1172                                                };
1173
1174                                        walker.guard = boundaryGuard;
1175
1176                                        enlargeable = walker.lastBackward();
1177
1178                                        // It's the body which stop the enlarging if no block boundary found.
1179                                        blockBoundary = blockBoundary || body;
1180
1181                                        // Start the range at different position by comparing
1182                                        // the document position of it with 'enlargeable' node.
1183                                        this.setStartAt(
1184                                                        blockBoundary,
1185                                                        !blockBoundary.is( 'br' ) &&
1186                                                        ( !enlargeable || blockBoundary.contains( enlargeable ) ) ?
1187                                                                CKEDITOR.POSITION_AFTER_START :
1188                                                                CKEDITOR.POSITION_AFTER_END );
1189
1190                                        // Enlarging the end boundary.
1191                                        walkerRange = this.clone();
1192                                        walkerRange.collapse();
1193                                        walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1194                                        walker = new CKEDITOR.dom.walker( walkerRange );
1195
1196                                        // tailBrGuard only used for on range end.
1197                                        walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1198                                                tailBrGuard : boundaryGuard;
1199                                        blockBoundary = null;
1200                                        // End the range right before the block boundary node.
1201
1202                                        enlargeable = walker.lastForward();
1203
1204                                        // It's the body which stop the enlarging if no block boundary found.
1205                                        blockBoundary = blockBoundary || body;
1206
1207                                        // Start the range at different position by comparing
1208                                        // the document position of it with 'enlargeable' node.
1209                                        this.setEndAt(
1210                                                        blockBoundary,
1211                                                        !blockBoundary.is( 'br' ) &&
1212                                                        ( !enlargeable || blockBoundary.contains( enlargeable ) ) ?
1213                                                                CKEDITOR.POSITION_BEFORE_END :
1214                                                                CKEDITOR.POSITION_BEFORE_START );
1215                                        // We must include the <br> at the end of range if there's
1216                                        // one and we're expanding list item contents
1217                                        if ( tailBr )
1218                                                this.setEndAfter( tailBr );
1219                        }
1220                },
1221
1222                /**
1223                 * Inserts a node at the start of the range. The range will be expanded
1224                 * the contain the node.
1225                 */
1226                insertNode : function( node )
1227                {
1228                        this.optimizeBookmark();
1229                        this.trim( false, true );
1230
1231                        var startContainer = this.startContainer;
1232                        var startOffset = this.startOffset;
1233
1234                        var nextNode = startContainer.getChild( startOffset );
1235
1236                        if ( nextNode )
1237                                node.insertBefore( nextNode );
1238                        else
1239                                startContainer.append( node );
1240
1241                        // Check if we need to update the end boundary.
1242                        if ( node.getParent().equals( this.endContainer ) )
1243                                this.endOffset++;
1244
1245                        // Expand the range to embrace the new node.
1246                        this.setStartBefore( node );
1247                },
1248
1249                moveToPosition : function( node, position )
1250                {
1251                        this.setStartAt( node, position );
1252                        this.collapse( true );
1253                },
1254
1255                selectNodeContents : function( node )
1256                {
1257                        this.setStart( node, 0 );
1258                        this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1259                },
1260
1261                /**
1262                 * Sets the start position of a Range.
1263                 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1264                 * @param {Number} startOffset An integer greater than or equal to zero
1265                 *              representing the offset for the start of the range from the start
1266                 *              of startNode.
1267                 */
1268                setStart : function( startNode, startOffset )
1269                {
1270                        // W3C requires a check for the new position. If it is after the end
1271                        // boundary, the range should be collapsed to the new start. It seams
1272                        // we will not need this check for our use of this class so we can
1273                        // ignore it for now.
1274
1275                        this.startContainer     = startNode;
1276                        this.startOffset        = startOffset;
1277
1278                        if ( !this.endContainer )
1279                        {
1280                                this.endContainer       = startNode;
1281                                this.endOffset          = startOffset;
1282                        }
1283
1284                        updateCollapsed( this );
1285                },
1286
1287                /**
1288                 * Sets the end position of a Range.
1289                 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1290                 * @param {Number} endOffset An integer greater than or equal to zero
1291                 *              representing the offset for the end of the range from the start
1292                 *              of endNode.
1293                 */
1294                setEnd : function( endNode, endOffset )
1295                {
1296                        // W3C requires a check for the new position. If it is before the start
1297                        // boundary, the range should be collapsed to the new end. It seams we
1298                        // will not need this check for our use of this class so we can ignore
1299                        // it for now.
1300
1301                        this.endContainer       = endNode;
1302                        this.endOffset          = endOffset;
1303
1304                        if ( !this.startContainer )
1305                        {
1306                                this.startContainer     = endNode;
1307                                this.startOffset        = endOffset;
1308                        }
1309
1310                        updateCollapsed( this );
1311                },
1312
1313                setStartAfter : function( node )
1314                {
1315                        this.setStart( node.getParent(), node.getIndex() + 1 );
1316                },
1317
1318                setStartBefore : function( node )
1319                {
1320                        this.setStart( node.getParent(), node.getIndex() );
1321                },
1322
1323                setEndAfter : function( node )
1324                {
1325                        this.setEnd( node.getParent(), node.getIndex() + 1 );
1326                },
1327
1328                setEndBefore : function( node )
1329                {
1330                        this.setEnd( node.getParent(), node.getIndex() );
1331                },
1332
1333                setStartAt : function( node, position )
1334                {
1335                        switch( position )
1336                        {
1337                                case CKEDITOR.POSITION_AFTER_START :
1338                                        this.setStart( node, 0 );
1339                                        break;
1340
1341                                case CKEDITOR.POSITION_BEFORE_END :
1342                                        if ( node.type == CKEDITOR.NODE_TEXT )
1343                                                this.setStart( node, node.getLength() );
1344                                        else
1345                                                this.setStart( node, node.getChildCount() );
1346                                        break;
1347
1348                                case CKEDITOR.POSITION_BEFORE_START :
1349                                        this.setStartBefore( node );
1350                                        break;
1351
1352                                case CKEDITOR.POSITION_AFTER_END :
1353                                        this.setStartAfter( node );
1354                        }
1355
1356                        updateCollapsed( this );
1357                },
1358
1359                setEndAt : function( node, position )
1360                {
1361                        switch( position )
1362                        {
1363                                case CKEDITOR.POSITION_AFTER_START :
1364                                        this.setEnd( node, 0 );
1365                                        break;
1366
1367                                case CKEDITOR.POSITION_BEFORE_END :
1368                                        if ( node.type == CKEDITOR.NODE_TEXT )
1369                                                this.setEnd( node, node.getLength() );
1370                                        else
1371                                                this.setEnd( node, node.getChildCount() );
1372                                        break;
1373
1374                                case CKEDITOR.POSITION_BEFORE_START :
1375                                        this.setEndBefore( node );
1376                                        break;
1377
1378                                case CKEDITOR.POSITION_AFTER_END :
1379                                        this.setEndAfter( node );
1380                        }
1381
1382                        updateCollapsed( this );
1383                },
1384
1385                fixBlock : function( isStart, blockTag )
1386                {
1387                        var bookmark = this.createBookmark(),
1388                                fixedBlock = this.document.createElement( blockTag );
1389
1390                        this.collapse( isStart );
1391
1392                        this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1393
1394                        this.extractContents().appendTo( fixedBlock );
1395                        fixedBlock.trim();
1396
1397                        if ( !CKEDITOR.env.ie )
1398                                fixedBlock.appendBogus();
1399
1400                        this.insertNode( fixedBlock );
1401
1402                        this.moveToBookmark( bookmark );
1403
1404                        return fixedBlock;
1405                },
1406
1407                splitBlock : function( blockTag )
1408                {
1409                        var startPath   = new CKEDITOR.dom.elementPath( this.startContainer ),
1410                                endPath         = new CKEDITOR.dom.elementPath( this.endContainer );
1411
1412                        var startBlockLimit     = startPath.blockLimit,
1413                                endBlockLimit   = endPath.blockLimit;
1414
1415                        var startBlock  = startPath.block,
1416                                endBlock        = endPath.block;
1417
1418                        var elementPath = null;
1419                        // Do nothing if the boundaries are in different block limits.
1420                        if ( !startBlockLimit.equals( endBlockLimit ) )
1421                                return null;
1422
1423                        // Get or fix current blocks.
1424                        if ( blockTag != 'br' )
1425                        {
1426                                if ( !startBlock )
1427                                {
1428                                        startBlock = this.fixBlock( true, blockTag );
1429                                        endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1430                                }
1431
1432                                if ( !endBlock )
1433                                        endBlock = this.fixBlock( false, blockTag );
1434                        }
1435
1436                        // Get the range position.
1437                        var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1438                                isEndOfBlock = endBlock && this.checkEndOfBlock();
1439
1440                        // Delete the current contents.
1441                        // TODO: Why is 2.x doing CheckIsEmpty()?
1442                        this.deleteContents();
1443
1444                        if ( startBlock && startBlock.equals( endBlock ) )
1445                        {
1446                                if ( isEndOfBlock )
1447                                {
1448                                        elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1449                                        this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1450                                        endBlock = null;
1451                                }
1452                                else if ( isStartOfBlock )
1453                                {
1454                                        elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1455                                        this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1456                                        startBlock = null;
1457                                }
1458                                else
1459                                {
1460                                        // Extract the contents of the block from the selection point to the end
1461                                        // of its contents.
1462                                        this.setEndAt( startBlock, CKEDITOR.POSITION_BEFORE_END );
1463                                        var documentFragment = this.extractContents();
1464
1465                                        // Duplicate the block element after it.
1466                                        endBlock = startBlock.clone( false );
1467
1468                                        // Place the extracted contents into the duplicated block.
1469                                        documentFragment.appendTo( endBlock );
1470                                        endBlock.insertAfter( startBlock );
1471                                        this.moveToPosition( startBlock, CKEDITOR.POSITION_AFTER_END );
1472
1473                                        // In Gecko, the last child node must be a bogus <br>.
1474                                        // Note: bogus <br> added under <ul> or <ol> would cause
1475                                        // lists to be incorrectly rendered.
1476                                        if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1477                                                startBlock.appendBogus() ;
1478                                }
1479                        }
1480
1481                        return {
1482                                previousBlock : startBlock,
1483                                nextBlock : endBlock,
1484                                wasStartOfBlock : isStartOfBlock,
1485                                wasEndOfBlock : isEndOfBlock,
1486                                elementPath : elementPath
1487                        };
1488                },
1489
1490                /**
1491                 * Check whether current range is on the inner edge of the specified element.
1492                 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
1493                 * @param {CKEDITOR.dom.element} element The target element to check.
1494                 */
1495                checkBoundaryOfElement : function( element, checkType )
1496                {
1497                        var walkerRange = this.clone();
1498                        // Expand the range to element boundary.
1499                        walkerRange[ checkType == CKEDITOR.START ?
1500                         'setStartAt' : 'setEndAt' ]
1501                         ( element, checkType == CKEDITOR.START ?
1502                           CKEDITOR.POSITION_AFTER_START
1503                           : CKEDITOR.POSITION_BEFORE_END );
1504
1505                        var walker = new CKEDITOR.dom.walker( walkerRange ),
1506                         retval = false;
1507                        walker.evaluator = elementBoundaryEval;
1508                        return walker[ checkType == CKEDITOR.START ?
1509                                'checkBackward' : 'checkForward' ]();
1510                },
1511                // Calls to this function may produce changes to the DOM. The range may
1512                // be updated to reflect such changes.
1513                checkStartOfBlock : function()
1514                {
1515                        var startContainer = this.startContainer,
1516                                startOffset = this.startOffset;
1517
1518                        // If the starting node is a text node, and non-empty before the offset,
1519                        // then we're surely not at the start of block.
1520                        if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1521                        {
1522                                var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1523                                if ( textBefore.length )
1524                                        return false;
1525                        }
1526
1527                        // Antecipate the trim() call here, so the walker will not make
1528                        // changes to the DOM, which would not get reflected into this
1529                        // range otherwise.
1530                        this.trim();
1531
1532                        // We need to grab the block element holding the start boundary, so
1533                        // let's use an element path for it.
1534                        var path = new CKEDITOR.dom.elementPath( this.startContainer );
1535
1536                        // Creates a range starting at the block start until the range start.
1537                        var walkerRange = this.clone();
1538                        walkerRange.collapse( true );
1539                        walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1540
1541                        var walker = new CKEDITOR.dom.walker( walkerRange );
1542                        walker.evaluator = getCheckStartEndBlockEvalFunction( true );
1543
1544                        return walker.checkBackward();
1545                },
1546
1547                checkEndOfBlock : function()
1548                {
1549                        var endContainer = this.endContainer,
1550                                endOffset = this.endOffset;
1551
1552                        // If the ending node is a text node, and non-empty after the offset,
1553                        // then we're surely not at the end of block.
1554                        if ( endContainer.type == CKEDITOR.NODE_TEXT )
1555                        {
1556                                var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1557                                if ( textAfter.length )
1558                                        return false;
1559                        }
1560
1561                        // Antecipate the trim() call here, so the walker will not make
1562                        // changes to the DOM, which would not get reflected into this
1563                        // range otherwise.
1564                        this.trim();
1565
1566                        // We need to grab the block element holding the start boundary, so
1567                        // let's use an element path for it.
1568                        var path = new CKEDITOR.dom.elementPath( this.endContainer );
1569
1570                        // Creates a range starting at the block start until the range start.
1571                        var walkerRange = this.clone();
1572                        walkerRange.collapse( false );
1573                        walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1574
1575                        var walker = new CKEDITOR.dom.walker( walkerRange );
1576                        walker.evaluator = getCheckStartEndBlockEvalFunction( false );
1577
1578                        return walker.checkForward();
1579                },
1580
1581                /**
1582                 * Moves the range boundaries to the first editing point inside an
1583                 * element. For example, in an element tree like
1584                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;", the start editing point is
1585                 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;^&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;" (inside &lt;i&gt;).
1586                 * @param {CKEDITOR.dom.element} targetElement The element into which
1587                 *              look for the editing spot.
1588                 */
1589                moveToElementEditStart : function( targetElement )
1590                {
1591                        var editableElement;
1592
1593                        while ( targetElement && targetElement.type == CKEDITOR.NODE_ELEMENT )
1594                        {
1595                                if ( targetElement.isEditable() )
1596                                        editableElement = targetElement;
1597                                else if ( editableElement )
1598                                        break ;         // If we already found an editable element, stop the loop.
1599
1600                                targetElement = targetElement.getFirst();
1601                        }
1602
1603                        if ( editableElement )
1604                        {
1605                                this.moveToPosition(editableElement, CKEDITOR.POSITION_AFTER_START);
1606                                return true;
1607                        }
1608                        else
1609                                return false;
1610                },
1611
1612                /**
1613                 * Get the single node enclosed within the range if there's one.
1614                 */
1615                getEnclosedNode : function()
1616                {
1617                        var walkerRange = this.clone(),
1618                                walker = new CKEDITOR.dom.walker( walkerRange ),
1619                                isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
1620                                isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1621                                evaluator = function( node )
1622                                {
1623                                        return isNotWhitespaces( node ) && isNotBookmarks( node );
1624                                };
1625                        walkerRange.evaluator = evaluator;
1626                        var node = walker.next();
1627                        walker.reset();
1628                        return node && node.equals( walker.previous() ) ? node : null;
1629                },
1630
1631                getTouchedStartNode : function()
1632                {
1633                        var container = this.startContainer ;
1634
1635                        if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1636                                return container ;
1637
1638                        return container.getChild( this.startOffset ) || container ;
1639                },
1640
1641                getTouchedEndNode : function()
1642                {
1643                        var container = this.endContainer ;
1644
1645                        if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1646                                return container ;
1647
1648                        return container.getChild( this.endOffset - 1 ) || container ;
1649                }
1650        };
1651})();
1652
1653CKEDITOR.POSITION_AFTER_START   = 1;    // <element>^contents</element>         "^text"
1654CKEDITOR.POSITION_BEFORE_END    = 2;    // <element>contents^</element>         "text^"
1655CKEDITOR.POSITION_BEFORE_START  = 3;    // ^<element>contents</element>         ^"text"
1656CKEDITOR.POSITION_AFTER_END             = 4;    // <element>contents</element>^         "text"
1657
1658CKEDITOR.ENLARGE_ELEMENT = 1;
1659CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
1660CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
1661
1662/**
1663 * Check boundary types.
1664 * @see CKEDITOR.dom.range::checkBoundaryOfElement
1665 */
1666CKEDITOR.START = 1;
1667CKEDITOR.END = 2;
1668CKEDITOR.STARTEND = 3;
Note: See TracBrowser for help on using the repository browser.