JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.4.2
[ckeditor.git] / _source / core / dom / rangelist.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 (function()\r
7 {\r
8         /**\r
9          * Represents a list os CKEDITOR.dom.range objects, which can be easily\r
10          * iterated sequentially.\r
11          * @constructor\r
12          * @param {CKEDITOR.dom.range|Array} [ranges] The ranges contained on this list.\r
13          *              Note that, if an array of ranges is specified, the range sequence\r
14          *              should match its DOM order. This class will not help to sort them.\r
15          */\r
16         CKEDITOR.dom.rangeList = function( ranges )\r
17         {\r
18                 if ( ranges instanceof CKEDITOR.dom.rangeList )\r
19                         return ranges;\r
20 \r
21                 if ( !ranges )\r
22                         ranges = [];\r
23                 else if ( ranges instanceof CKEDITOR.dom.range )\r
24                         ranges = [ ranges ];\r
25 \r
26                 return CKEDITOR.tools.extend( ranges, mixins );\r
27         };\r
28 \r
29         var mixins =\r
30         /** @lends CKEDITOR.dom.rangeList.prototype */\r
31         {\r
32                         /**\r
33                          * Creates an instance of the rangeList iterator, it should be used\r
34                          * only when the ranges processing could be DOM intrusive, which\r
35                          * means it may pollute and break other ranges in this list.\r
36                          * Otherwise, it's enough to just iterate over this array in a for loop.\r
37                          * @returns {CKEDITOR.dom.rangeListIterator}\r
38                          */\r
39                         createIterator : function()\r
40                         {\r
41                                 var rangeList = this,\r
42                                         bookmark = CKEDITOR.dom.walker.bookmark(),\r
43                                         guard = function( node ) { return ! ( node.is && node.is( 'tr' ) ); },\r
44                                                 bookmarks = [],\r
45                                         current;\r
46 \r
47                                 /**\r
48                                  * @lends CKEDITOR.dom.rangeListIterator.prototype\r
49                                  */\r
50                                 return {\r
51 \r
52                                         /**\r
53                                          * Retrieves the next range in the list.\r
54                                          * @param {Boolean} mergeConsequent Whether join two adjacent ranges into single, e.g. consequent table cells.\r
55                                          */\r
56                                         getNextRange : function( mergeConsequent )\r
57                                         {\r
58                                                 current = current == undefined ? 0 : current + 1;\r
59 \r
60                                                 var range = rangeList[ current ];\r
61 \r
62                                                 // Multiple ranges might be mangled by each other.\r
63                                                 if ( range && rangeList.length > 1 )\r
64                                                 {\r
65                                                         // Bookmarking all other ranges on the first iteration,\r
66                                                         // the range correctness after it doesn't matter since we'll\r
67                                                         // restore them before the next iteration.\r
68                                                         if ( !current )\r
69                                                         {\r
70                                                                 // Make sure bookmark correctness by reverse processing.\r
71                                                                 for ( var i = rangeList.length - 1; i >= 0; i-- )\r
72                                                                         bookmarks.unshift( rangeList[ i ].createBookmark( true ) );\r
73                                                         }\r
74 \r
75                                                         if ( mergeConsequent )\r
76                                                         {\r
77                                                                 // Figure out how many ranges should be merged.\r
78                                                                 var mergeCount = 0;\r
79                                                                 while ( rangeList[ current + mergeCount + 1 ] )\r
80                                                                 {\r
81                                                                         var doc = range.document,\r
82                                                                                 found = 0,\r
83                                                                                 left =  doc.getById( bookmarks[ mergeCount ].endNode ),\r
84                                                                                 right = doc.getById( bookmarks[ mergeCount + 1 ].startNode ),\r
85                                                                                 next;\r
86 \r
87                                                                         // Check subsequent range.\r
88                                                                         while ( 1 )\r
89                                                                         {\r
90                                                                                 next = left.getNextSourceNode( false );\r
91                                                                                 if ( !right.equals( next ) )\r
92                                                                                 {\r
93                                                                                         // This could be yet another bookmark or\r
94                                                                                         // walking across block boundaries.\r
95                                                                                         if ( bookmark( next ) || ( next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() ) )\r
96                                                                                         {\r
97                                                                                                 left = next;\r
98                                                                                                 continue;\r
99                                                                                         }\r
100                                                                                 }\r
101                                                                                 else\r
102                                                                                         found = 1;\r
103 \r
104                                                                                 break;\r
105                                                                         }\r
106 \r
107                                                                         if ( !found )\r
108                                                                                 break;\r
109 \r
110                                                                         mergeCount++;\r
111                                                                 }\r
112                                                         }\r
113 \r
114                                                         range.moveToBookmark( bookmarks.shift() );\r
115 \r
116                                                         var next;\r
117                                                         // Merge ranges finally after moving to bookmarks.\r
118                                                         while( mergeCount-- )\r
119                                                         {\r
120                                                                 next = rangeList[ ++current ];\r
121                                                                 next.moveToBookmark( bookmarks.shift() );\r
122                                                                 range.setEnd( next.endContainer, next.endOffset );\r
123                                                         }\r
124                                                 }\r
125 \r
126                                                 return range;\r
127                                         }\r
128                                 };\r
129                         },\r
130 \r
131                         createBookmarks : function( serializable )\r
132                         {\r
133                                 var retval = [], bookmark;\r
134                                 for ( var i = 0; i < this.length ; i++ )\r
135                                 {\r
136                                         retval.push( bookmark = this[ i ].createBookmark( serializable, true) );\r
137 \r
138                                         // Updating the container & offset values for ranges\r
139                                         // that have been touched.\r
140                                         for ( var j = i + 1; j < this.length; j++ )\r
141                                         {\r
142                                                 this[ j ] = updateDirtyRange( bookmark, this[ j ] );\r
143                                                 this[ j ] = updateDirtyRange( bookmark, this[ j ], true );\r
144                                         }\r
145                                 }\r
146                                 return retval;\r
147                         },\r
148 \r
149                         createBookmarks2 : function( normalized )\r
150                         {\r
151                                 var bookmarks = [];\r
152 \r
153                                 for ( var i = 0 ; i < this.length ; i++ )\r
154                                         bookmarks.push( this[ i ].createBookmark2( normalized ) );\r
155 \r
156                                 return bookmarks;\r
157                         },\r
158 \r
159                         /**\r
160                          * Move each range in the list to the position specified by a list of bookmarks.\r
161                          * @param {Array} bookmarks The list of bookmarks, each one matching a range in the list.\r
162                          */\r
163                         moveToBookmarks :  function( bookmarks )\r
164                         {\r
165                                 for ( var i = 0 ; i < this.length ; i++ )\r
166                                         this[ i ].moveToBookmark( bookmarks[ i ] );\r
167                         }\r
168         };\r
169 \r
170         // Update the specified range which has been mangled by previous insertion of\r
171         // range bookmark nodes.(#3256)\r
172         function updateDirtyRange( bookmark, dirtyRange, checkEnd )\r
173         {\r
174                 var serializable = bookmark.serializable,\r
175                         container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ],\r
176                         offset = checkEnd ? 'endOffset' : 'startOffset';\r
177 \r
178                 var bookmarkStart = serializable ?\r
179                                 dirtyRange.document.getById( bookmark.startNode )\r
180                                 : bookmark.startNode;\r
181 \r
182                 var bookmarkEnd = serializable ?\r
183                                 dirtyRange.document.getById( bookmark.endNode )\r
184                                 : bookmark.endNode;\r
185 \r
186                 if ( container.equals( bookmarkStart.getPrevious() ) )\r
187                 {\r
188                         dirtyRange.startOffset = dirtyRange.startOffset\r
189                                         - container.getLength()\r
190                                         - bookmarkEnd.getPrevious().getLength();\r
191                         container = bookmarkEnd.getNext();\r
192                 }\r
193                 else if ( container.equals( bookmarkEnd.getPrevious() ) )\r
194                 {\r
195                         dirtyRange.startOffset = dirtyRange.startOffset - container.getLength();\r
196                         container = bookmarkEnd.getNext();\r
197                 }\r
198 \r
199                 container.equals( bookmarkStart.getParent() ) && dirtyRange[ offset ]++;\r
200                 container.equals( bookmarkEnd.getParent() ) && dirtyRange[ offset ]++;\r
201 \r
202                 // Update and return this range.\r
203                 dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ] = container;\r
204                 return dirtyRange;\r
205         }\r
206 })();\r
207 \r
208 /**\r
209  * (Virtual Class) Do not call this constructor. This class is not really part\r
210  *      of the API. It just describes the return type of {@link CKEDITOR.dom.rangeList#createIterator}.\r
211  * @name CKEDITOR.dom.rangeListIterator\r
212  * @constructor\r
213  * @example\r
214  */\r