2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license
\r
6 CKEDITOR.dom.range = function( document )
\r
8 this.startContainer = null;
\r
9 this.startOffset = null;
\r
10 this.endContainer = null;
\r
11 this.endOffset = null;
\r
12 this.collapsed = true;
\r
14 this.document = document;
\r
19 // Updates the "collapsed" property for the given range object.
\r
20 var updateCollapsed = function( range )
\r
23 range.startContainer &&
\r
24 range.endContainer &&
\r
25 range.startContainer.equals( range.endContainer ) &&
\r
26 range.startOffset == range.endOffset );
\r
29 // This is a shared function used to delete, extract and clone the range
\r
32 var execContentsAction = function( range, action, docFrag )
\r
34 range.optimizeBookmark();
\r
36 var startNode = range.startContainer;
\r
37 var endNode = range.endContainer;
\r
39 var startOffset = range.startOffset;
\r
40 var endOffset = range.endOffset;
\r
42 var removeStartNode;
\r
45 // For text containers, we must simply split the node and point to the
\r
46 // second part. The removal will be handled by the rest of the code .
\r
47 if ( endNode.type == CKEDITOR.NODE_TEXT )
\r
48 endNode = endNode.split( endOffset );
\r
51 // If the end container has children and the offset is pointing
\r
52 // to a child, then we should start from it.
\r
53 if ( endNode.getChildCount() > 0 )
\r
55 // If the offset points after the last node.
\r
56 if ( endOffset >= endNode.getChildCount() )
\r
58 // Let's create a temporary node and mark it for removal.
\r
59 endNode = endNode.append( range.document.createText( '' ) );
\r
60 removeEndNode = true;
\r
63 endNode = endNode.getChild( endOffset );
\r
67 // For text containers, we must simply split the node. The removal will
\r
68 // be handled by the rest of the code .
\r
69 if ( startNode.type == CKEDITOR.NODE_TEXT )
\r
71 startNode.split( startOffset );
\r
73 // In cases the end node is the same as the start node, the above
\r
74 // splitting will also split the end, so me must move the end to
\r
75 // the second part of the split.
\r
76 if ( startNode.equals( endNode ) )
\r
77 endNode = startNode.getNext();
\r
81 // If the start container has children and the offset is pointing
\r
82 // to a child, then we should start from its previous sibling.
\r
84 // If the offset points to the first node, we don't have a
\r
85 // sibling, so let's use the first one, but mark it for removal.
\r
88 // Let's create a temporary node and mark it for removal.
\r
89 startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
\r
90 removeStartNode = true;
\r
92 else if ( startOffset >= startNode.getChildCount() )
\r
94 // Let's create a temporary node and mark it for removal.
\r
95 startNode = startNode.append( range.document.createText( '' ) );
\r
96 removeStartNode = true;
\r
99 startNode = startNode.getChild( startOffset ).getPrevious();
\r
102 // Get the parent nodes tree for the start and end boundaries.
\r
103 var startParents = startNode.getParents();
\r
104 var endParents = endNode.getParents();
\r
106 // Compare them, to find the top most siblings.
\r
107 var i, topStart, topEnd;
\r
109 for ( i = 0 ; i < startParents.length ; i++ )
\r
111 topStart = startParents[ i ];
\r
112 topEnd = endParents[ i ];
\r
114 // The compared nodes will match until we find the top most
\r
115 // siblings (different nodes that have the same parent).
\r
116 // "i" will hold the index in the parents array for the top
\r
118 if ( !topStart.equals( topEnd ) )
\r
122 var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
\r
124 // Remove all successive sibling nodes for every node in the
\r
125 // startParents tree.
\r
126 for ( var j = i ; j < startParents.length ; j++ )
\r
128 levelStartNode = startParents[j];
\r
130 // For Extract and Clone, we must clone this level.
\r
131 if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete
\r
132 levelClone = clone.append( levelStartNode.clone() );
\r
134 currentNode = levelStartNode.getNext();
\r
136 while ( currentNode )
\r
138 // Stop processing when the current node matches a node in the
\r
139 // endParents tree or if it is the endNode.
\r
140 if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
\r
143 // Cache the next sibling.
\r
144 currentSibling = currentNode.getNext();
\r
146 // If cloning, just clone it.
\r
147 if ( action == 2 ) // 2 = Clone
\r
148 clone.append( currentNode.clone( true ) );
\r
151 // Both Delete and Extract will remove the node.
\r
152 currentNode.remove();
\r
154 // When Extracting, move the removed node to the docFrag.
\r
155 if ( action == 1 ) // 1 = Extract
\r
156 clone.append( currentNode );
\r
159 currentNode = currentSibling;
\r
163 clone = levelClone;
\r
168 // Remove all previous sibling nodes for every node in the
\r
169 // endParents tree.
\r
170 for ( var k = i ; k < endParents.length ; k++ )
\r
172 levelStartNode = endParents[ k ];
\r
174 // For Extract and Clone, we must clone this level.
\r
175 if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete
\r
176 levelClone = clone.append( levelStartNode.clone() );
\r
178 // The processing of siblings may have already been done by the parent.
\r
179 if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
\r
181 currentNode = levelStartNode.getPrevious();
\r
183 while ( currentNode )
\r
185 // Stop processing when the current node matches a node in the
\r
186 // startParents tree or if it is the startNode.
\r
187 if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
\r
190 // Cache the next sibling.
\r
191 currentSibling = currentNode.getPrevious();
\r
193 // If cloning, just clone it.
\r
194 if ( action == 2 ) // 2 = Clone
\r
195 clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
\r
198 // Both Delete and Extract will remove the node.
\r
199 currentNode.remove();
\r
201 // When Extracting, mode the removed node to the docFrag.
\r
202 if ( action == 1 ) // 1 = Extract
\r
203 clone.$.insertBefore( currentNode.$, clone.$.firstChild );
\r
206 currentNode = currentSibling;
\r
211 clone = levelClone;
\r
214 if ( action == 2 ) // 2 = Clone.
\r
216 // No changes in the DOM should be done, so fix the split text (if any).
\r
218 var startTextNode = range.startContainer;
\r
219 if ( startTextNode.type == CKEDITOR.NODE_TEXT )
\r
221 startTextNode.$.data += startTextNode.$.nextSibling.data;
\r
222 startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
\r
225 var endTextNode = range.endContainer;
\r
226 if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
\r
228 endTextNode.$.data += endTextNode.$.nextSibling.data;
\r
229 endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
\r
234 // Collapse the range.
\r
236 // If a node has been partially selected, collapse the range between
\r
237 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
\r
238 if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
\r
240 var endIndex = topEnd.getIndex();
\r
242 // If the start node is to be removed, we must correct the
\r
243 // index to reflect the removal.
\r
244 if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
\r
247 range.setStart( topEnd.getParent(), endIndex );
\r
250 // Collapse it to the start.
\r
251 range.collapse( true );
\r
254 // Cleanup any marked node.
\r
255 if ( removeStartNode )
\r
256 startNode.remove();
\r
258 if ( removeEndNode && endNode.$.parentNode )
\r
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 };
\r
264 // Creates the appropriate node evaluator for the dom walker used inside
\r
265 // check(Start|End)OfBlock.
\r
266 function getCheckStartEndBlockEvalFunction( isStart )
\r
268 var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
\r
269 return function( node )
\r
271 // First ignore bookmark nodes.
\r
272 if ( bookmarkEvaluator( node ) )
\r
275 if ( node.type == CKEDITOR.NODE_TEXT )
\r
277 // If there's any visible text, then we're not at the start.
\r
278 if ( CKEDITOR.tools.trim( node.getText() ).length )
\r
281 else if ( node.type == CKEDITOR.NODE_ELEMENT )
\r
283 // If there are non-empty inline elements (e.g. <img />), then we're not
\r
285 if ( !inlineChildReqElements[ node.getName() ] )
\r
287 // If we're working at the end-of-block, forgive the first <br /> in non-IE
\r
289 if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
\r
299 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
\r
300 // text node and non-empty elements unless it's being bookmark text.
\r
301 function elementBoundaryEval( node )
\r
303 // Reject any text node unless it's being bookmark
\r
304 // OR it's spaces. (#3883)
\r
305 return node.type != CKEDITOR.NODE_TEXT
\r
306 && node.getName() in CKEDITOR.dtd.$removeEmpty
\r
307 || !CKEDITOR.tools.trim( node.getText() )
\r
308 || node.getParent().hasAttribute( '_cke_bookmark' );
\r
311 var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
\r
312 bookmarkEval = new CKEDITOR.dom.walker.bookmark();
\r
314 function nonWhitespaceOrBookmarkEval( node )
\r
316 // Whitespaces and bookmark nodes are to be ignored.
\r
317 return !whitespaceEval( node ) && !bookmarkEval( node );
\r
320 CKEDITOR.dom.range.prototype =
\r
324 var clone = new CKEDITOR.dom.range( this.document );
\r
326 clone.startContainer = this.startContainer;
\r
327 clone.startOffset = this.startOffset;
\r
328 clone.endContainer = this.endContainer;
\r
329 clone.endOffset = this.endOffset;
\r
330 clone.collapsed = this.collapsed;
\r
335 collapse : function( toStart )
\r
339 this.endContainer = this.startContainer;
\r
340 this.endOffset = this.startOffset;
\r
344 this.startContainer = this.endContainer;
\r
345 this.startOffset = this.endOffset;
\r
348 this.collapsed = true;
\r
351 // The selection may be lost when cloning (due to the splitText() call).
\r
352 cloneContents : function()
\r
354 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
356 if ( !this.collapsed )
\r
357 execContentsAction( this, 2, docFrag );
\r
362 deleteContents : function()
\r
364 if ( this.collapsed )
\r
367 execContentsAction( this, 0 );
\r
370 extractContents : function()
\r
372 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
374 if ( !this.collapsed )
\r
375 execContentsAction( this, 1, docFrag );
\r
381 * Creates a bookmark object, which can be later used to restore the
\r
382 * range by using the moveToBookmark function.
\r
383 * This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
384 * in the range boundaries. The advantage of it is that it is possible to
\r
385 * handle DOM mutations when moving back to the bookmark.
\r
386 * Attention: the inclusion of nodes in the DOM is a design choice and
\r
387 * should not be changed as there are other points in the code that may be
\r
388 * using those nodes to perform operations. See GetBookmarkNode.
\r
389 * @param {Boolean} [serializable] Indicates that the bookmark nodes
\r
390 * must contain ids, which can be used to restore the range even
\r
391 * when these nodes suffer mutations (like a clonation or innerHTML
\r
393 * @returns {Object} And object representing a bookmark.
\r
395 createBookmark : function( serializable )
\r
397 var startNode, endNode;
\r
400 var collapsed = this.collapsed;
\r
402 startNode = this.document.createElement( 'span' );
\r
403 startNode.setAttribute( '_cke_bookmark', 1 );
\r
404 startNode.setStyle( 'display', 'none' );
\r
406 // For IE, it must have something inside, otherwise it may be
\r
407 // removed during DOM operations.
\r
408 startNode.setHtml( ' ' );
\r
410 if ( serializable )
\r
412 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
\r
413 startNode.setAttribute( 'id', baseId + 'S' );
\r
416 // If collapsed, the endNode will not be created.
\r
419 endNode = startNode.clone();
\r
420 endNode.setHtml( ' ' );
\r
422 if ( serializable )
\r
423 endNode.setAttribute( 'id', baseId + 'E' );
\r
425 clone = this.clone();
\r
427 clone.insertNode( endNode );
\r
430 clone = this.clone();
\r
431 clone.collapse( true );
\r
432 clone.insertNode( startNode );
\r
434 // Update the range position.
\r
437 this.setStartAfter( startNode );
\r
438 this.setEndBefore( endNode );
\r
441 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
\r
444 startNode : serializable ? baseId + 'S' : startNode,
\r
445 endNode : serializable ? baseId + 'E' : endNode,
\r
446 serializable : serializable,
\r
447 collapsed : collapsed
\r
452 * Creates a "non intrusive" and "mutation sensible" bookmark. This
\r
453 * kind of bookmark should be used only when the DOM is supposed to
\r
454 * remain stable after its creation.
\r
455 * @param {Boolean} [normalized] Indicates that the bookmark must
\r
456 * normalized. When normalized, the successive text nodes are
\r
457 * considered a single node. To sucessful load a normalized
\r
458 * bookmark, the DOM tree must be also normalized before calling
\r
460 * @returns {Object} An object representing the bookmark.
\r
462 createBookmark2 : function( normalized )
\r
464 var startContainer = this.startContainer,
\r
465 endContainer = this.endContainer;
\r
467 var startOffset = this.startOffset,
\r
468 endOffset = this.endOffset;
\r
470 var collapsed = this.collapsed;
\r
472 var child, previous;
\r
474 // If there is no range then get out of here.
\r
475 // It happens on initial load in Safari #962 and if the editor it's
\r
476 // hidden also in Firefox
\r
477 if ( !startContainer || !endContainer )
\r
478 return { start : 0, end : 0 };
\r
482 // Find out if the start is pointing to a text node that will
\r
484 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
\r
486 child = startContainer.getChild( startOffset );
\r
488 // In this case, move the start information to that text
\r
490 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
491 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
493 startContainer = child;
\r
498 // Normalize the start.
\r
499 while ( startContainer.type == CKEDITOR.NODE_TEXT
\r
500 && ( previous = startContainer.getPrevious() )
\r
501 && previous.type == CKEDITOR.NODE_TEXT )
\r
503 startContainer = previous;
\r
504 startOffset += previous.getLength();
\r
507 // Process the end only if not normalized.
\r
510 // Find out if the start is pointing to a text node that
\r
511 // will be normalized.
\r
512 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
\r
514 child = endContainer.getChild( endOffset );
\r
516 // In this case, move the start information to that
\r
518 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
519 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
521 endContainer = child;
\r
526 // Normalize the end.
\r
527 while ( endContainer.type == CKEDITOR.NODE_TEXT
\r
528 && ( previous = endContainer.getPrevious() )
\r
529 && previous.type == CKEDITOR.NODE_TEXT )
\r
531 endContainer = previous;
\r
532 endOffset += previous.getLength();
\r
538 start : startContainer.getAddress( normalized ),
\r
539 end : collapsed ? null : endContainer.getAddress( normalized ),
\r
540 startOffset : startOffset,
\r
541 endOffset : endOffset,
\r
542 normalized : normalized,
\r
543 collapsed : collapsed,
\r
544 is2 : true // It's a createBookmark2 bookmark.
\r
548 moveToBookmark : function( bookmark )
\r
550 if ( bookmark.is2 ) // Created with createBookmark2().
\r
552 // Get the start information.
\r
553 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
\r
554 startOffset = bookmark.startOffset;
\r
556 // Get the end information.
\r
557 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
\r
558 endOffset = bookmark.endOffset;
\r
560 // Set the start boundary.
\r
561 this.setStart( startContainer, startOffset );
\r
563 // Set the end boundary. If not available, collapse it.
\r
564 if ( endContainer )
\r
565 this.setEnd( endContainer, endOffset );
\r
567 this.collapse( true );
\r
569 else // Created with createBookmark().
\r
571 var serializable = bookmark.serializable,
\r
572 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
\r
573 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
\r
575 // Set the range start at the bookmark start node position.
\r
576 this.setStartBefore( startNode );
\r
578 // Remove it, because it may interfere in the setEndBefore call.
\r
579 startNode.remove();
\r
581 // Set the range end at the bookmark end node position, or simply
\r
582 // collapse it if it is not available.
\r
585 this.setEndBefore( endNode );
\r
589 this.collapse( true );
\r
593 getBoundaryNodes : function()
\r
595 var startNode = this.startContainer,
\r
596 endNode = this.endContainer,
\r
597 startOffset = this.startOffset,
\r
598 endOffset = this.endOffset,
\r
601 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
\r
603 childCount = startNode.getChildCount();
\r
604 if ( childCount > startOffset )
\r
605 startNode = startNode.getChild( startOffset );
\r
606 else if ( childCount < 1 )
\r
607 startNode = startNode.getPreviousSourceNode();
\r
608 else // startOffset > childCount but childCount is not 0
\r
610 // Try to take the node just after the current position.
\r
611 startNode = startNode.$;
\r
612 while ( startNode.lastChild )
\r
613 startNode = startNode.lastChild;
\r
614 startNode = new CKEDITOR.dom.node( startNode );
\r
616 // Normally we should take the next node in DFS order. But it
\r
617 // is also possible that we've already reached the end of
\r
619 startNode = startNode.getNextSourceNode() || startNode;
\r
622 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
\r
624 childCount = endNode.getChildCount();
\r
625 if ( childCount > endOffset )
\r
626 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
\r
627 else if ( childCount < 1 )
\r
628 endNode = endNode.getPreviousSourceNode();
\r
629 else // endOffset > childCount but childCount is not 0
\r
631 // Try to take the node just before the current position.
\r
632 endNode = endNode.$;
\r
633 while ( endNode.lastChild )
\r
634 endNode = endNode.lastChild;
\r
635 endNode = new CKEDITOR.dom.node( endNode );
\r
639 // Sometimes the endNode will come right before startNode for collapsed
\r
640 // ranges. Fix it. (#3780)
\r
641 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
\r
642 startNode = endNode;
\r
644 return { startNode : startNode, endNode : endNode };
\r
648 * Find the node which fully contains the range.
\r
649 * @param includeSelf
\r
650 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
\r
652 getCommonAncestor : function( includeSelf , ignoreTextNode )
\r
654 var start = this.startContainer,
\r
655 end = this.endContainer,
\r
658 if ( start.equals( end ) )
\r
661 && start.type == CKEDITOR.NODE_ELEMENT
\r
662 && this.startOffset == this.endOffset - 1 )
\r
663 ancestor = start.getChild( this.startOffset );
\r
668 ancestor = start.getCommonAncestor( end );
\r
670 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
\r
674 * Transforms the startContainer and endContainer properties from text
\r
675 * nodes to element nodes, whenever possible. This is actually possible
\r
676 * if either of the boundary containers point to a text node, and its
\r
677 * offset is set to zero, or after the last char in the node.
\r
679 optimize : function()
\r
681 var container = this.startContainer;
\r
682 var offset = this.startOffset;
\r
684 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
687 this.setStartBefore( container );
\r
688 else if ( offset >= container.getLength() )
\r
689 this.setStartAfter( container );
\r
692 container = this.endContainer;
\r
693 offset = this.endOffset;
\r
695 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
698 this.setEndBefore( container );
\r
699 else if ( offset >= container.getLength() )
\r
700 this.setEndAfter( container );
\r
705 * Move the range out of bookmark nodes if they'd been the container.
\r
707 optimizeBookmark: function()
\r
709 var startNode = this.startContainer,
\r
710 endNode = this.endContainer;
\r
712 if ( startNode.is && startNode.is( 'span' )
\r
713 && startNode.hasAttribute( '_cke_bookmark' ) )
\r
714 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
\r
715 if ( endNode && endNode.is && endNode.is( 'span' )
\r
716 && endNode.hasAttribute( '_cke_bookmark' ) )
\r
717 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
\r
720 trim : function( ignoreStart, ignoreEnd )
\r
722 var startContainer = this.startContainer,
\r
723 startOffset = this.startOffset,
\r
724 collapsed = this.collapsed;
\r
725 if ( ( !ignoreStart || collapsed )
\r
726 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
728 // If the offset is zero, we just insert the new node before
\r
730 if ( !startOffset )
\r
732 startOffset = startContainer.getIndex();
\r
733 startContainer = startContainer.getParent();
\r
735 // If the offset is at the end, we'll insert it after the text
\r
737 else if ( startOffset >= startContainer.getLength() )
\r
739 startOffset = startContainer.getIndex() + 1;
\r
740 startContainer = startContainer.getParent();
\r
742 // In other case, we split the text node and insert the new
\r
743 // node at the split point.
\r
746 var nextText = startContainer.split( startOffset );
\r
748 startOffset = startContainer.getIndex() + 1;
\r
749 startContainer = startContainer.getParent();
\r
751 // Check all necessity of updating the end boundary.
\r
752 if ( this.startContainer.equals( this.endContainer ) )
\r
753 this.setEnd( nextText, this.endOffset - this.startOffset );
\r
754 else if ( startContainer.equals( this.endContainer ) )
\r
755 this.endOffset += 1;
\r
758 this.setStart( startContainer, startOffset );
\r
762 this.collapse( true );
\r
767 var endContainer = this.endContainer;
\r
768 var endOffset = this.endOffset;
\r
770 if ( !( ignoreEnd || collapsed )
\r
771 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
773 // If the offset is zero, we just insert the new node before
\r
777 endOffset = endContainer.getIndex();
\r
778 endContainer = endContainer.getParent();
\r
780 // If the offset is at the end, we'll insert it after the text
\r
782 else if ( endOffset >= endContainer.getLength() )
\r
784 endOffset = endContainer.getIndex() + 1;
\r
785 endContainer = endContainer.getParent();
\r
787 // In other case, we split the text node and insert the new
\r
788 // node at the split point.
\r
791 endContainer.split( endOffset );
\r
793 endOffset = endContainer.getIndex() + 1;
\r
794 endContainer = endContainer.getParent();
\r
797 this.setEnd( endContainer, endOffset );
\r
801 enlarge : function( unit )
\r
805 case CKEDITOR.ENLARGE_ELEMENT :
\r
807 if ( this.collapsed )
\r
810 // Get the common ancestor.
\r
811 var commonAncestor = this.getCommonAncestor();
\r
813 var body = this.document.getBody();
\r
815 // For each boundary
\r
816 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
\r
817 // 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.
\r
819 var startTop, endTop;
\r
821 var enlargeable, sibling, commonReached;
\r
823 // Indicates that the node can be added only if whitespace
\r
824 // is available before it.
\r
825 var needsWhiteSpace = false;
\r
829 // Process the start boundary.
\r
831 var container = this.startContainer;
\r
832 var offset = this.startOffset;
\r
834 if ( container.type == CKEDITOR.NODE_TEXT )
\r
838 // Check if there is any non-space text before the
\r
839 // offset. Otherwise, container is null.
\r
840 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
\r
842 // If we found only whitespace in the node, it
\r
843 // means that we'll need more whitespace to be able
\r
844 // to expand. For example, <i> can be expanded in
\r
845 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
846 needsWhiteSpace = !!container;
\r
851 if ( !( sibling = container.getPrevious() ) )
\r
852 enlargeable = container.getParent();
\r
857 // If we have offset, get the node preceeding it as the
\r
858 // first sibling to be checked.
\r
860 sibling = container.getChild( offset - 1 ) || container.getLast();
\r
862 // If there is no sibling, mark the container to be
\r
865 enlargeable = container;
\r
868 while ( enlargeable || sibling )
\r
870 if ( enlargeable && !sibling )
\r
872 // If we reached the common ancestor, mark the flag
\r
874 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
875 commonReached = true;
\r
877 if ( !body.contains( enlargeable ) )
\r
880 // If we don't need space or this element breaks
\r
881 // the line, then enlarge it.
\r
882 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
884 needsWhiteSpace = false;
\r
886 // If the common ancestor has been reached,
\r
887 // we'll not enlarge it immediately, but just
\r
888 // mark it to be enlarged later if the end
\r
889 // boundary also enlarges it.
\r
890 if ( commonReached )
\r
891 startTop = enlargeable;
\r
893 this.setStartBefore( enlargeable );
\r
896 sibling = enlargeable.getPrevious();
\r
899 // Check all sibling nodes preceeding the enlargeable
\r
900 // node. The node wil lbe enlarged only if none of them
\r
904 // This flag indicates that this node has
\r
905 // whitespaces at the end.
\r
906 isWhiteSpace = false;
\r
908 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
910 siblingText = sibling.getText();
\r
912 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
915 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
\r
919 // If this is a visible element.
\r
920 // We need to check for the bookmark attribute because IE insists on
\r
921 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
922 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
\r
924 // We'll accept it only if we need
\r
925 // whitespace, and this is an inline
\r
926 // element with whitespace only.
\r
927 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
929 // It must contains spaces and inline elements only.
\r
931 siblingText = sibling.getText();
\r
933 if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
\r
937 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
938 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
\r
940 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
949 isWhiteSpace = !!siblingText.length;
\r
956 // A node with whitespaces has been found.
\r
957 if ( isWhiteSpace )
\r
959 // Enlarge the last enlargeable node, if we
\r
960 // were waiting for spaces.
\r
961 if ( needsWhiteSpace )
\r
963 if ( commonReached )
\r
964 startTop = enlargeable;
\r
965 else if ( enlargeable )
\r
966 this.setStartBefore( enlargeable );
\r
969 needsWhiteSpace = true;
\r
974 var next = sibling.getPrevious();
\r
976 if ( !enlargeable && !next )
\r
978 // Set the sibling as enlargeable, so it's
\r
979 // parent will be get later outside this while.
\r
980 enlargeable = sibling;
\r
989 // If sibling has been set to null, then we
\r
990 // need to stop enlarging.
\r
991 enlargeable = null;
\r
996 enlargeable = enlargeable.getParent();
\r
999 // Process the end boundary. This is basically the same
\r
1000 // code used for the start boundary, with small changes to
\r
1001 // make it work in the oposite side (to the right). This
\r
1002 // makes it difficult to reuse the code here. So, fixes to
\r
1003 // the above code are likely to be replicated here.
\r
1005 container = this.endContainer;
\r
1006 offset = this.endOffset;
\r
1008 // Reset the common variables.
\r
1009 enlargeable = sibling = null;
\r
1010 commonReached = needsWhiteSpace = false;
\r
1012 if ( container.type == CKEDITOR.NODE_TEXT )
\r
1014 // Check if there is any non-space text after the
\r
1015 // offset. Otherwise, container is null.
\r
1016 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
\r
1018 // If we found only whitespace in the node, it
\r
1019 // means that we'll need more whitespace to be able
\r
1020 // to expand. For example, <i> can be expanded in
\r
1021 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
1022 needsWhiteSpace = !( container && container.getLength() );
\r
1026 if ( !( sibling = container.getNext() ) )
\r
1027 enlargeable = container.getParent();
\r
1032 // Get the node right after the boudary to be checked
\r
1034 sibling = container.getChild( offset );
\r
1037 enlargeable = container;
\r
1040 while ( enlargeable || sibling )
\r
1042 if ( enlargeable && !sibling )
\r
1044 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
1045 commonReached = true;
\r
1047 if ( !body.contains( enlargeable ) )
\r
1050 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
1052 needsWhiteSpace = false;
\r
1054 if ( commonReached )
\r
1055 endTop = enlargeable;
\r
1056 else if ( enlargeable )
\r
1057 this.setEndAfter( enlargeable );
\r
1060 sibling = enlargeable.getNext();
\r
1065 isWhiteSpace = false;
\r
1067 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
1069 siblingText = sibling.getText();
\r
1071 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
1074 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
\r
1078 // If this is a visible element.
\r
1079 // We need to check for the bookmark attribute because IE insists on
\r
1080 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
1081 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
\r
1083 // We'll accept it only if we need
\r
1084 // whitespace, and this is an inline
\r
1085 // element with whitespace only.
\r
1086 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
1088 // It must contains spaces and inline elements only.
\r
1090 siblingText = sibling.getText();
\r
1092 if ( (/[^\s\ufeff]/).test( siblingText ) )
\r
1096 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
1097 for ( i = 0 ; child = allChildren[ i++ ] ; )
\r
1099 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
1108 isWhiteSpace = !!siblingText.length;
\r
1115 if ( isWhiteSpace )
\r
1117 if ( needsWhiteSpace )
\r
1119 if ( commonReached )
\r
1120 endTop = enlargeable;
\r
1122 this.setEndAfter( enlargeable );
\r
1128 next = sibling.getNext();
\r
1130 if ( !enlargeable && !next )
\r
1132 enlargeable = sibling;
\r
1141 // If sibling has been set to null, then we
\r
1142 // need to stop enlarging.
\r
1143 enlargeable = null;
\r
1147 if ( enlargeable )
\r
1148 enlargeable = enlargeable.getParent();
\r
1151 // If the common ancestor can be enlarged by both boundaries, then include it also.
\r
1152 if ( startTop && endTop )
\r
1154 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
\r
1156 this.setStartBefore( commonAncestor );
\r
1157 this.setEndAfter( commonAncestor );
\r
1161 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
\r
1162 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
\r
1164 // Enlarging the start boundary.
\r
1165 var walkerRange = new CKEDITOR.dom.range( this.document );
\r
1167 body = this.document.getBody();
\r
1169 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
\r
1170 walkerRange.setEnd( this.startContainer, this.startOffset );
\r
1172 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1173 blockBoundary, // The node on which the enlarging should stop.
\r
1174 tailBr, // In case BR as block boundary.
\r
1175 notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
\r
1176 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
\r
1177 // Record the encountered 'blockBoundary' for later use.
\r
1178 boundaryGuard = function( node )
\r
1180 var retval = notBlockBoundary( node );
\r
1182 blockBoundary = node;
\r
1185 // Record the encounted 'tailBr' for later use.
\r
1186 tailBrGuard = function( node )
\r
1188 var retval = boundaryGuard( node );
\r
1189 if ( !retval && node.is && node.is( 'br' ) )
\r
1194 walker.guard = boundaryGuard;
\r
1196 enlargeable = walker.lastBackward();
\r
1198 // It's the body which stop the enlarging if no block boundary found.
\r
1199 blockBoundary = blockBoundary || body;
\r
1201 // Start the range either after the end of found block (<p>...</p>[text)
\r
1202 // or at the start of block (<p>[text...), by comparing the document position
\r
1203 // with 'enlargeable' node.
\r
1206 !blockBoundary.is( 'br' ) &&
\r
1207 ( !enlargeable && this.checkStartOfBlock()
\r
1208 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1209 CKEDITOR.POSITION_AFTER_START :
\r
1210 CKEDITOR.POSITION_AFTER_END );
\r
1212 // Enlarging the end boundary.
\r
1213 walkerRange = this.clone();
\r
1214 walkerRange.collapse();
\r
1215 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
\r
1216 walker = new CKEDITOR.dom.walker( walkerRange );
\r
1218 // tailBrGuard only used for on range end.
\r
1219 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
\r
1220 tailBrGuard : boundaryGuard;
\r
1221 blockBoundary = null;
\r
1222 // End the range right before the block boundary node.
\r
1224 enlargeable = walker.lastForward();
\r
1226 // It's the body which stop the enlarging if no block boundary found.
\r
1227 blockBoundary = blockBoundary || body;
\r
1229 // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)
\r
1230 // by comparing the document position with 'enlargeable' node.
\r
1233 ( !enlargeable && this.checkEndOfBlock()
\r
1234 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1235 CKEDITOR.POSITION_BEFORE_END :
\r
1236 CKEDITOR.POSITION_BEFORE_START );
\r
1237 // We must include the <br> at the end of range if there's
\r
1238 // one and we're expanding list item contents
\r
1240 this.setEndAfter( tailBr );
\r
1245 * Descrease the range to make sure that boundaries
\r
1246 * always anchor beside text nodes or innermost element.
\r
1247 * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
\r
1249 * <dt>CKEDITOR.SHRINK_ELEMENT</dt>
\r
1250 * <dd>Shrink the range boundaries to the edge of the innermost element.</dd>
\r
1251 * <dt>CKEDITOR.SHRINK_TEXT</dt>
\r
1252 * <dd>Shrink the range boudaries to anchor by the side of enclosed text node, range remains if there's no text nodes on boundaries at all.</dd>
\r
1254 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
\r
1256 shrink : function( mode, selectContents )
\r
1258 // Unable to shrink a collapsed range.
\r
1259 if ( !this.collapsed )
\r
1261 mode = mode || CKEDITOR.SHRINK_TEXT;
\r
1263 var walkerRange = this.clone();
\r
1265 var startContainer = this.startContainer,
\r
1266 endContainer = this.endContainer,
\r
1267 startOffset = this.startOffset,
\r
1268 endOffset = this.endOffset,
\r
1269 collapsed = this.collapsed;
\r
1271 // Whether the start/end boundary is moveable.
\r
1272 var moveStart = 1,
\r
1275 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1277 if ( !startOffset )
\r
1278 walkerRange.setStartBefore( startContainer );
\r
1279 else if ( startOffset >= startContainer.getLength( ) )
\r
1280 walkerRange.setStartAfter( startContainer );
\r
1283 // Enlarge the range properly to avoid walker making
\r
1284 // DOM changes caused by triming the text nodes later.
\r
1285 walkerRange.setStartBefore( startContainer );
\r
1290 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
1293 walkerRange.setEndBefore( endContainer );
\r
1294 else if ( endOffset >= endContainer.getLength( ) )
\r
1295 walkerRange.setEndAfter( endContainer );
\r
1298 walkerRange.setEndAfter( endContainer );
\r
1303 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1305 walker.evaluator = function( node )
\r
1307 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
\r
1308 CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
\r
1311 var currentElement;
\r
1312 walker.guard = function( node, movingOut )
\r
1314 // Stop when we're shrink in element mode while encountering a text node.
\r
1315 if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
\r
1318 // Stop when we've already walked "through" an element.
\r
1319 if ( movingOut && node.equals( currentElement ) )
\r
1322 if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
\r
1323 currentElement = node;
\r
1330 var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
\r
1331 textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
\r
1337 var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
\r
1338 textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
\r
1341 return !!( moveStart || moveEnd );
\r
1346 * Inserts a node at the start of the range. The range will be expanded
\r
1347 * the contain the node.
\r
1349 insertNode : function( node )
\r
1351 this.optimizeBookmark();
\r
1352 this.trim( false, true );
\r
1354 var startContainer = this.startContainer;
\r
1355 var startOffset = this.startOffset;
\r
1357 var nextNode = startContainer.getChild( startOffset );
\r
1360 node.insertBefore( nextNode );
\r
1362 startContainer.append( node );
\r
1364 // Check if we need to update the end boundary.
\r
1365 if ( node.getParent().equals( this.endContainer ) )
\r
1368 // Expand the range to embrace the new node.
\r
1369 this.setStartBefore( node );
\r
1372 moveToPosition : function( node, position )
\r
1374 this.setStartAt( node, position );
\r
1375 this.collapse( true );
\r
1378 selectNodeContents : function( node )
\r
1380 this.setStart( node, 0 );
\r
1381 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
\r
1385 * Sets the start position of a Range.
\r
1386 * @param {CKEDITOR.dom.node} startNode The node to start the range.
\r
1387 * @param {Number} startOffset An integer greater than or equal to zero
\r
1388 * representing the offset for the start of the range from the start
\r
1391 setStart : function( startNode, startOffset )
\r
1393 // W3C requires a check for the new position. If it is after the end
\r
1394 // boundary, the range should be collapsed to the new start. It seams
\r
1395 // we will not need this check for our use of this class so we can
\r
1396 // ignore it for now.
\r
1398 // Fixing invalid range start inside dtd empty elements.
\r
1399 if( startNode.type == CKEDITOR.NODE_ELEMENT
\r
1400 && CKEDITOR.dtd.$empty[ startNode.getName() ] )
\r
1401 startNode = startNode.getParent(), startOffset = startNode.getIndex();
\r
1403 this.startContainer = startNode;
\r
1404 this.startOffset = startOffset;
\r
1406 if ( !this.endContainer )
\r
1408 this.endContainer = startNode;
\r
1409 this.endOffset = startOffset;
\r
1412 updateCollapsed( this );
\r
1416 * Sets the end position of a Range.
\r
1417 * @param {CKEDITOR.dom.node} endNode The node to end the range.
\r
1418 * @param {Number} endOffset An integer greater than or equal to zero
\r
1419 * representing the offset for the end of the range from the start
\r
1422 setEnd : function( endNode, endOffset )
\r
1424 // W3C requires a check for the new position. If it is before the start
\r
1425 // boundary, the range should be collapsed to the new end. It seams we
\r
1426 // will not need this check for our use of this class so we can ignore
\r
1429 // Fixing invalid range end inside dtd empty elements.
\r
1430 if( endNode.type == CKEDITOR.NODE_ELEMENT
\r
1431 && CKEDITOR.dtd.$empty[ endNode.getName() ] )
\r
1432 endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1;
\r
1434 this.endContainer = endNode;
\r
1435 this.endOffset = endOffset;
\r
1437 if ( !this.startContainer )
\r
1439 this.startContainer = endNode;
\r
1440 this.startOffset = endOffset;
\r
1443 updateCollapsed( this );
\r
1446 setStartAfter : function( node )
\r
1448 this.setStart( node.getParent(), node.getIndex() + 1 );
\r
1451 setStartBefore : function( node )
\r
1453 this.setStart( node.getParent(), node.getIndex() );
\r
1456 setEndAfter : function( node )
\r
1458 this.setEnd( node.getParent(), node.getIndex() + 1 );
\r
1461 setEndBefore : function( node )
\r
1463 this.setEnd( node.getParent(), node.getIndex() );
\r
1466 setStartAt : function( node, position )
\r
1468 switch( position )
\r
1470 case CKEDITOR.POSITION_AFTER_START :
\r
1471 this.setStart( node, 0 );
\r
1474 case CKEDITOR.POSITION_BEFORE_END :
\r
1475 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1476 this.setStart( node, node.getLength() );
\r
1478 this.setStart( node, node.getChildCount() );
\r
1481 case CKEDITOR.POSITION_BEFORE_START :
\r
1482 this.setStartBefore( node );
\r
1485 case CKEDITOR.POSITION_AFTER_END :
\r
1486 this.setStartAfter( node );
\r
1489 updateCollapsed( this );
\r
1492 setEndAt : function( node, position )
\r
1494 switch( position )
\r
1496 case CKEDITOR.POSITION_AFTER_START :
\r
1497 this.setEnd( node, 0 );
\r
1500 case CKEDITOR.POSITION_BEFORE_END :
\r
1501 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1502 this.setEnd( node, node.getLength() );
\r
1504 this.setEnd( node, node.getChildCount() );
\r
1507 case CKEDITOR.POSITION_BEFORE_START :
\r
1508 this.setEndBefore( node );
\r
1511 case CKEDITOR.POSITION_AFTER_END :
\r
1512 this.setEndAfter( node );
\r
1515 updateCollapsed( this );
\r
1518 fixBlock : function( isStart, blockTag )
\r
1520 var bookmark = this.createBookmark(),
\r
1521 fixedBlock = this.document.createElement( blockTag );
\r
1523 this.collapse( isStart );
\r
1525 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
\r
1527 this.extractContents().appendTo( fixedBlock );
\r
1528 fixedBlock.trim();
\r
1530 if ( !CKEDITOR.env.ie )
\r
1531 fixedBlock.appendBogus();
\r
1533 this.insertNode( fixedBlock );
\r
1535 this.moveToBookmark( bookmark );
\r
1537 return fixedBlock;
\r
1540 splitBlock : function( blockTag )
\r
1542 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
\r
1543 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1545 var startBlockLimit = startPath.blockLimit,
\r
1546 endBlockLimit = endPath.blockLimit;
\r
1548 var startBlock = startPath.block,
\r
1549 endBlock = endPath.block;
\r
1551 var elementPath = null;
\r
1552 // Do nothing if the boundaries are in different block limits.
\r
1553 if ( !startBlockLimit.equals( endBlockLimit ) )
\r
1556 // Get or fix current blocks.
\r
1557 if ( blockTag != 'br' )
\r
1559 if ( !startBlock )
\r
1561 startBlock = this.fixBlock( true, blockTag );
\r
1562 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
\r
1566 endBlock = this.fixBlock( false, blockTag );
\r
1569 // Get the range position.
\r
1570 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
\r
1571 isEndOfBlock = endBlock && this.checkEndOfBlock();
\r
1573 // Delete the current contents.
\r
1574 // TODO: Why is 2.x doing CheckIsEmpty()?
\r
1575 this.deleteContents();
\r
1577 if ( startBlock && startBlock.equals( endBlock ) )
\r
1579 if ( isEndOfBlock )
\r
1581 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1582 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
\r
1585 else if ( isStartOfBlock )
\r
1587 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1588 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
\r
1589 startBlock = null;
\r
1593 endBlock = this.splitElement( startBlock );
\r
1595 // In Gecko, the last child node must be a bogus <br>.
\r
1596 // Note: bogus <br> added under <ul> or <ol> would cause
\r
1597 // lists to be incorrectly rendered.
\r
1598 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
\r
1599 startBlock.appendBogus() ;
\r
1604 previousBlock : startBlock,
\r
1605 nextBlock : endBlock,
\r
1606 wasStartOfBlock : isStartOfBlock,
\r
1607 wasEndOfBlock : isEndOfBlock,
\r
1608 elementPath : elementPath
\r
1613 * Branch the specified element from the collapsed range position and
\r
1614 * place the caret between the two result branches.
\r
1615 * Note: The range must be collapsed and been enclosed by this element.
\r
1616 * @param {CKEDITOR.dom.element} element
\r
1617 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
\r
1619 splitElement : function( toSplit )
\r
1621 if ( !this.collapsed )
\r
1624 // Extract the contents of the block from the selection point to the end
\r
1625 // of its contents.
\r
1626 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
\r
1627 var documentFragment = this.extractContents();
\r
1629 // Duplicate the element after it.
\r
1630 var clone = toSplit.clone( false );
\r
1632 // Place the extracted contents into the duplicated element.
\r
1633 documentFragment.appendTo( clone );
\r
1634 clone.insertAfter( toSplit );
\r
1635 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
\r
1640 * Check whether a range boundary is at the inner boundary of a given
\r
1642 * @param {CKEDITOR.dom.element} element The target element to check.
\r
1643 * @param {Number} checkType The boundary to check for both the range
\r
1644 * and the element. It can be CKEDITOR.START or CKEDITOR.END.
\r
1645 * @returns {Boolean} "true" if the range boundary is at the inner
\r
1646 * boundary of the element.
\r
1648 checkBoundaryOfElement : function( element, checkType )
\r
1650 var checkStart = ( checkType == CKEDITOR.START );
\r
1652 // Create a copy of this range, so we can manipulate it for our checks.
\r
1653 var walkerRange = this.clone();
\r
1655 // Collapse the range at the proper size.
\r
1656 walkerRange.collapse( checkStart );
\r
1658 // Expand the range to element boundary.
\r
1659 walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
\r
1660 ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
\r
1662 // Create the walker, which will check if we have anything useful
\r
1664 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1665 walker.evaluator = elementBoundaryEval;
\r
1667 return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
\r
1670 // Calls to this function may produce changes to the DOM. The range may
\r
1671 // be updated to reflect such changes.
\r
1672 checkStartOfBlock : function()
\r
1674 var startContainer = this.startContainer,
\r
1675 startOffset = this.startOffset;
\r
1677 // If the starting node is a text node, and non-empty before the offset,
\r
1678 // then we're surely not at the start of block.
\r
1679 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1681 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
\r
1682 if ( textBefore.length )
\r
1686 // Antecipate the trim() call here, so the walker will not make
\r
1687 // changes to the DOM, which would not get reflected into this
\r
1688 // range otherwise.
\r
1691 // We need to grab the block element holding the start boundary, so
\r
1692 // let's use an element path for it.
\r
1693 var path = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1695 // Creates a range starting at the block start until the range start.
\r
1696 var walkerRange = this.clone();
\r
1697 walkerRange.collapse( true );
\r
1698 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
\r
1700 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1701 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
\r
1703 return walker.checkBackward();
\r
1706 checkEndOfBlock : function()
\r
1708 var endContainer = this.endContainer,
\r
1709 endOffset = this.endOffset;
\r
1711 // If the ending node is a text node, and non-empty after the offset,
\r
1712 // then we're surely not at the end of block.
\r
1713 if ( endContainer.type == CKEDITOR.NODE_TEXT )
\r
1715 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
\r
1716 if ( textAfter.length )
\r
1720 // Antecipate the trim() call here, so the walker will not make
\r
1721 // changes to the DOM, which would not get reflected into this
\r
1722 // range otherwise.
\r
1725 // We need to grab the block element holding the start boundary, so
\r
1726 // let's use an element path for it.
\r
1727 var path = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1729 // Creates a range starting at the block start until the range start.
\r
1730 var walkerRange = this.clone();
\r
1731 walkerRange.collapse( false );
\r
1732 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
\r
1734 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1735 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
\r
1737 return walker.checkForward();
\r
1741 * Moves the range boundaries to the first/end editing point inside an
\r
1742 * element. For example, in an element tree like
\r
1743 * "<p><b><i></i></b> Text</p>", the start editing point is
\r
1744 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
1745 * @param {CKEDITOR.dom.element} el The element into which look for the
\r
1747 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
\r
1749 moveToElementEditablePosition : function( el, isMoveToEnd )
\r
1753 // Empty elements are rejected.
\r
1754 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1757 while ( el && el.type == CKEDITOR.NODE_ELEMENT )
\r
1759 isEditable = el.isEditable();
\r
1761 // If an editable element is found, move inside it.
\r
1763 this.moveToPosition( el, isMoveToEnd ?
\r
1764 CKEDITOR.POSITION_BEFORE_END :
\r
1765 CKEDITOR.POSITION_AFTER_START );
\r
1766 // Stop immediately if we've found a non editable inline element (e.g <img>).
\r
1767 else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
\r
1769 this.moveToPosition( el, isMoveToEnd ?
\r
1770 CKEDITOR.POSITION_AFTER_END :
\r
1771 CKEDITOR.POSITION_BEFORE_START );
\r
1775 // Non-editable non-inline elements are to be bypassed, getting the next one.
\r
1776 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1777 el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
\r
1779 el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
\r
1781 // Stop immediately if we've found a text node.
\r
1782 if ( el && el.type == CKEDITOR.NODE_TEXT )
\r
1784 this.moveToPosition( el, isMoveToEnd ?
\r
1785 CKEDITOR.POSITION_AFTER_END :
\r
1786 CKEDITOR.POSITION_BEFORE_START );
\r
1791 return isEditable;
\r
1795 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1797 moveToElementEditStart : function( target )
\r
1799 return this.moveToElementEditablePosition( target );
\r
1803 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1805 moveToElementEditEnd : function( target )
\r
1807 return this.moveToElementEditablePosition( target, true );
\r
1811 * Get the single node enclosed within the range if there's one.
\r
1813 getEnclosedNode : function()
\r
1815 var walkerRange = this.clone();
\r
1817 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
\r
1818 walkerRange.optimize();
\r
1819 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
\r
1820 || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
\r
1823 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1824 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
\r
1825 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
\r
1826 evaluator = function( node )
\r
1828 return isNotWhitespaces( node ) && isNotBookmarks( node );
\r
1830 walkerRange.evaluator = evaluator;
\r
1831 var node = walker.next();
\r
1833 return node && node.equals( walker.previous() ) ? node : null;
\r
1836 getTouchedStartNode : function()
\r
1838 var container = this.startContainer ;
\r
1840 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1841 return container ;
\r
1843 return container.getChild( this.startOffset ) || container ;
\r
1846 getTouchedEndNode : function()
\r
1848 var container = this.endContainer ;
\r
1850 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1851 return container ;
\r
1853 return container.getChild( this.endOffset - 1 ) || container ;
\r
1858 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
\r
1859 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
\r
1860 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
\r
1861 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
\r
1863 CKEDITOR.ENLARGE_ELEMENT = 1;
\r
1864 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
\r
1865 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
\r
1868 * Check boundary types.
\r
1869 * @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement
\r
1871 CKEDITOR.START = 1;
\r
1873 CKEDITOR.STARTEND = 3;
\r
1876 * Shrink range types.
\r
1877 * @see CKEDITOR.dom.range.prototype.shrink
\r
1879 CKEDITOR.SHRINK_ELEMENT = 1;
\r
1880 CKEDITOR.SHRINK_TEXT = 2;
\r