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