      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