1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 (function()
  7 {
  8 	// This function is to be called under a "walker" instance scope.
  9 	function iterate( rtl, breakOnFalse )
 10 	{
 11 		// Return null if we have reached the end.
 12 		if ( this._.end )
 13 			return null;
 14
 15 		var node,
 16 			range = this.range,
 17 			guard,
 18 			userGuard = this.guard,
 19 			type = this.type,
 20 			getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );
 21
 22 		// This is the first call. Initialize it.
 23 		if ( !this._.start )
 24 		{
 25 			this._.start = 1;
 26
 27 			// Trim text nodes and optmize the range boundaries. DOM changes
 28 			// may happen at this point.
 29 			range.trim();
 30
 31 			// A collapsed range must return null at first call.
 32 			if ( range.collapsed )
 33 			{
 34 				this.end();
 35 				return null;
 36 			}
 37 		}
 38
 39 		// Create the LTR guard function, if necessary.
 40 		if ( !rtl && !this._.guardLTR )
 41 		{
 42 			// Gets the node that stops the walker when going LTR.
 43 			var limitLTR = range.endContainer,
 44 				blockerLTR = limitLTR.getChild( range.endOffset );
 45
 46 			this._.guardLTR = function( node, movingOut )
 47 			{
 48 				return ( ( !movingOut || !limitLTR.equals( node ) )
 49 					&& ( !blockerLTR || !node.equals( blockerLTR ) )
 50 					&& ( node.type != CKEDITOR.NODE_ELEMENT || node.getName() != 'body' ) );
 51 			};
 52 		}
 53
 54 		// Create the RTL guard function, if necessary.
 55 		if ( rtl && !this._.guardRTL )
 56 		{
 57 			// Gets the node that stops the walker when going LTR.
 58 			var limitRTL = range.startContainer,
 59 				blockerRTL = ( range.startOffset > 0 ) && limitRTL.getChild( range.startOffset - 1 );
 60
 61 			this._.guardRTL = function( node, movingOut )
 62 			{
 63 				return ( ( !movingOut || !limitRTL.equals( node ) )
 64 					&& ( !blockerRTL || !node.equals( blockerRTL ) )
 65 					&& ( node.type != CKEDITOR.NODE_ELEMENT || node.getName() != 'body' ) );
 66 			};
 67 		}
 68
 69 		// Define which guard function to use.
 70 		var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;
 71
 72 		// Make the user defined guard function participate in the process,
 73 		// otherwise simply use the boundary guard.
 74 		if ( userGuard )
 75 		{
 76 			guard = function( node, movingOut )
 77 			{
 78 				if ( stopGuard( node, movingOut ) === false )
 79 					return false;
 80
 81 				return userGuard( node );
 82 			};
 83 		}
 84 		else
 85 			guard = stopGuard;
 86
 87 		if ( this.current )
 88 			node = this.current[ getSourceNodeFn ]( false, type, guard );
 89 		else
 90 		{
 91 			// Get the first node to be returned.
 92
 93 			if ( rtl )
 94 			{
 95 				node = range.endContainer;
 96
 97 				if ( range.endOffset > 0 )
 98 				{
 99 					node = node.getChild( range.endOffset - 1 );
100 					if ( guard( node ) === false )
101 						node = null;
102 				}
103 				else
104 					node = ( guard ( node ) === false ) ?
105 						null : node.getPreviousSourceNode( true, type, guard );
106 			}
107 			else
108 			{
109 				node = range.startContainer;
110 				node = node.getChild( range.startOffset );
111
112 				if ( node )
113 				{
114 					if ( guard( node ) === false )
115 						node = null;
116 				}
117 				else
118 					node = ( guard ( range.startContainer ) === false ) ?
119 						null : range.startContainer.getNextSourceNode( true, type, guard ) ;
120 			}
121 		}
122
123 		while ( node && !this._.end )
124 		{
125 			this.current = node;
126
127 			if ( !this.evaluator || this.evaluator( node ) !== false )
128 			{
129 				if ( !breakOnFalse )
130 					return node;
131 			}
132 			else if ( breakOnFalse && this.evaluator )
133 				return false;
134
135 			node = node[ getSourceNodeFn ]( false, type, guard );
136 		}
137
138 		this.end();
139 		return this.current = null;
140 	}
141
142 	function iterateToLast( rtl )
143 	{
144 		var node, last = null;
145
146 		while ( ( node = iterate.call( this, rtl ) ) )
147 			last = node;
148
149 		return last;
150 	}
151
152 	CKEDITOR.dom.walker = CKEDITOR.tools.createClass(
153 	{
154 		/**
155 		 * Utility class to "walk" the DOM inside a range boundaries. If
156 		 * necessary, partially included nodes (text nodes) are broken to
157 		 * reflect the boundaries limits, so DOM and range changes may happen.
158 		 * Outside changes to the range may break the walker.
159 		 *
160 		 * The walker may return nodes that are not totaly included into the
161 		 * range boundaires. Let's take the following range representation,
162 		 * where the square brackets indicate the boundaries:
163 		 *
164 		 * [<p>Some <b>sample] text</b>
165 		 *
166 		 * While walking forward into the above range, the following nodes are
167 		 * returned: <p>, "Some ", <b> and "sample". Going
168 		 * backwards instead we have: "sample" and "Some ". So note that the
169 		 * walker always returns nodes when "entering" them, but not when
170 		 * "leaving" them. The guard function is instead called both when
171 		 * entering and leaving nodes.
172 		 *
173 		 * @constructor
174 		 * @param {CKEDITOR.dom.range} range The range within which walk.
175 		 */
176 		$ : function( range )
177 		{
178 			this.range = range;
179
180 			/**
181 			 * A function executed for every matched node, to check whether
182 			 * it's to be considered into the walk or not. If not provided, all
183 			 * matched nodes are considered good.
184 			 * If the function returns "false" the node is ignored.
185 			 * @name CKEDITOR.dom.walker.prototype.evaluator
186 			 * @property
187 			 * @type Function
188 			 */
189 			// this.evaluator = null;
190
191 			/**
192 			 * A function executed for every node the walk pass by to check
193 			 * whether the walk is to be finished. It's called when both
194 			 * entering and exiting nodes, as well as for the matched nodes.
195 			 * If this function returns "false", the walking ends and no more
196 			 * nodes are evaluated.
197 			 * @name CKEDITOR.dom.walker.prototype.guard
198 			 * @property
199 			 * @type Function
200 			 */
201 			// this.guard = null;
202
203 			/** @private */
204 			this._ = {};
205 		},
206
207 //		statics :
208 //		{
209 //			/* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes.
210 //			 * @param {CKEDITOR.dom.node} startNode The node from wich the walk
211 //			 *		will start.
212 //			 * @param {CKEDITOR.dom.node} [endNode] The last node to be considered
213 //			 *		in the walk. No more nodes are retrieved after touching or
214 //			 *		passing it. If not provided, the walker stops at the
215 //			 *		<body> closing boundary.
216 //			 * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the
217 //			 *		provided nodes.
218 //			 */
219 //			createOnNodes : function( startNode, endNode, startInclusive, endInclusive )
220 //			{
221 //				var range = new CKEDITOR.dom.range();
222 //				if ( startNode )
223 //					range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;
224 //				else
225 //					range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;
226 //
227 //				if ( endNode )
228 //					range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;
229 //				else
230 //					range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;
231 //
232 //				return new CKEDITOR.dom.walker( range );
233 //			}
234 //		},
235 //
236 		proto :
237 		{
238 			/**
239 			 * Stop walking. No more nodes are retrieved if this function gets
240 			 * called.
241 			 */
242 			end : function()
243 			{
244 				this._.end = 1;
245 			},
246
247 			/**
248 			 * Retrieves the next node (at right).
249 			 * @returns {CKEDITOR.dom.node} The next node or null if no more
250 			 *		nodes are available.
251 			 */
252 			next : function()
253 			{
254 				return iterate.call( this );
255 			},
256
257 			/**
258 			 * Retrieves the previous node (at left).
259 			 * @returns {CKEDITOR.dom.node} The previous node or null if no more
260 			 *		nodes are available.
261 			 */
262 			previous : function()
263 			{
264 				return iterate.call( this, true );
265 			},
266
267 			/**
268 			 * Check all nodes at right, executing the evaluation fuction.
269 			 * @returns {Boolean} "false" if the evaluator function returned
270 			 *		"false" for any of the matched nodes. Otherwise "true".
271 			 */
272 			checkForward : function()
273 			{
274 				return iterate.call( this, false, true ) !== false;
275 			},
276
277 			/**
278 			 * Check all nodes at left, executing the evaluation fuction.
279 			 * @returns {Boolean} "false" if the evaluator function returned
280 			 *		"false" for any of the matched nodes. Otherwise "true".
281 			 */
282 			checkBackward : function()
283 			{
284 				return iterate.call( this, true, true ) !== false;
285 			},
286
287 			/**
288 			 * Executes a full walk forward (to the right), until no more nodes
289 			 * are available, returning the last valid node.
290 			 * @returns {CKEDITOR.dom.node} The last node at the right or null
291 			 *		if no valid nodes are available.
292 			 */
293 			lastForward : function()
294 			{
295 				return iterateToLast.call( this );
296 			},
297
298 			/**
299 			 * Executes a full walk backwards (to the left), until no more nodes
300 			 * are available, returning the last valid node.
301 			 * @returns {CKEDITOR.dom.node} The last node at the left or null
302 			 *		if no valid nodes are available.
303 			 */
304 			lastBackward : function()
305 			{
306 				return iterateToLast.call( this, true );
307 			}
308
309 		}
310 	});
311
312 	/*
313 	 * Anything whose display computed style is block, list-item, table,
314 	 * table-row-group, table-header-group, table-footer-group, table-row,
315 	 * table-column-group, table-column, table-cell, table-caption, or whose node
316 	 * name is hr, br (when enterMode is br only) is a block boundary.
317 	 */
318 	var blockBoundaryDisplayMatch =
319 	{
320 		block : 1,
321 		'list-item' : 1,
322 		table : 1,
323 		'table-row-group' : 1,
324 		'table-header-group' : 1,
325 		'table-footer-group' : 1,
326 		'table-row' : 1,
327 		'table-column-group' : 1,
328 		'table-column' : 1,
329 		'table-cell' : 1,
330 		'table-caption' : 1
331 	},
332 	blockBoundaryNodeNameMatch = { hr : 1 };
333
334 	CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
335 	{
336 		var nodeNameMatches = CKEDITOR.tools.extend( {},
337 													blockBoundaryNodeNameMatch, customNodeNames || {} );
338
339 		return blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ||
340 			nodeNameMatches[ this.getName() ];
341 	};
342
343 	CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )
344 	{
345 		return function( node , type )
346 		{
347 			return ! ( node.type == CKEDITOR.NODE_ELEMENT
348 						&& node.isBlockBoundary( customNodeNames ) );
349 		};
350 	};
351
352 	CKEDITOR.dom.walker.listItemBoundary = function()
353 	{
354 			return this.blockBoundary( { br : 1 } );
355 	};
356 	/**
357 	 * Whether the node is a bookmark node's inner text node.
358 	 */
359 	CKEDITOR.dom.walker.bookmarkContents = function( node )
360 	{
361 	},
362
363 	/**
364 	 * Whether the to-be-evaluated node is a bookmark node OR bookmark node
365 	 * inner contents.
366 	 * @param {Boolean} contentOnly Whether only test againt the text content of
367 	 * bookmark node instead of the element itself(default).
368 	 * @param {Boolean} isReject Whether should return 'false' for the bookmark
369 	 * node instead of 'true'(default).
370 	 */
371 	CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )
372 	{
373 		function isBookmarkNode( node )
374 		{
375 			return ( node && node.getName
376 					&& node.getName() == 'span'
377 					&& node.hasAttribute('_fck_bookmark') );
378 		}
379
380 		return function( node )
381 		{
382 			var retval, parent;
383 			// Is bookmark inner text node?
384 			retval = ( node && !node.getName && ( parent = node.getParent() )
385 						&& isBookmarkNode( parent ) );
386 			// Is bookmark node?
387 			retval = contentOnly ? retval : retval || isBookmarkNode( node );
388 			return isReject ? !retval : !!retval;
389 		};
390 	};
391
392 })();
393