2685506214ca4d393e86850188c3ffad8e2c7286
[ckeditor.git] / _source / core / dom / rangelist.js
1 /*
2 Copyright (c) 2003-2011, 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 */