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