2 Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.
\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license
\r
8 // This function is to be called under a "walker" instance scope.
\r
9 function iterate( rtl, breakOnFalse )
\r
11 var range = this.range;
\r
13 // Return null if we have reached the end.
\r
17 // This is the first call. Initialize it.
\r
18 if ( !this._.start )
\r
22 // A collapsed range must return null at first call.
\r
23 if ( range.collapsed )
\r
29 // Move outside of text node edges.
\r
34 startCt = range.startContainer,
\r
35 endCt = range.endContainer,
\r
36 startOffset = range.startOffset,
\r
37 endOffset = range.endOffset,
\r
39 userGuard = this.guard,
\r
41 getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );
\r
43 // Create the LTR guard function, if necessary.
\r
44 if ( !rtl && !this._.guardLTR )
\r
46 // The node that stops walker from moving up.
\r
47 var limitLTR = endCt.type == CKEDITOR.NODE_ELEMENT ?
\r
51 // The node that stops the walker from going to next.
\r
52 var blockerLTR = endCt.type == CKEDITOR.NODE_ELEMENT ?
\r
53 endCt.getChild( endOffset ) :
\r
56 this._.guardLTR = function( node, movingOut )
\r
58 return ( ( !movingOut || !limitLTR.equals( node ) )
\r
59 && ( !blockerLTR || !node.equals( blockerLTR ) )
\r
60 && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );
\r
64 // Create the RTL guard function, if necessary.
\r
65 if ( rtl && !this._.guardRTL )
\r
67 // The node that stops walker from moving up.
\r
68 var limitRTL = startCt.type == CKEDITOR.NODE_ELEMENT ?
\r
70 startCt.getParent();
\r
72 // The node that stops the walker from going to next.
\r
73 var blockerRTL = startCt.type == CKEDITOR.NODE_ELEMENT ?
\r
75 startCt.getChild( startOffset - 1 ) : null :
\r
76 startCt.getPrevious();
\r
78 this._.guardRTL = function( node, movingOut )
\r
80 return ( ( !movingOut || !limitRTL.equals( node ) )
\r
81 && ( !blockerRTL || !node.equals( blockerRTL ) )
\r
82 && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );
\r
86 // Define which guard function to use.
\r
87 var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;
\r
89 // Make the user defined guard function participate in the process,
\r
90 // otherwise simply use the boundary guard.
\r
93 guard = function( node, movingOut )
\r
95 if ( stopGuard( node, movingOut ) === false )
\r
98 return userGuard( node, movingOut );
\r
104 if ( this.current )
\r
105 node = this.current[ getSourceNodeFn ]( false, type, guard );
\r
108 // Get the first node to be returned.
\r
113 if ( node.type == CKEDITOR.NODE_ELEMENT )
\r
115 if ( endOffset > 0 )
\r
116 node = node.getChild( endOffset - 1 );
\r
118 node = ( guard ( node, true ) === false ) ?
\r
119 null : node.getPreviousSourceNode( true, type, guard );
\r
126 if ( node.type == CKEDITOR.NODE_ELEMENT )
\r
128 if ( ! ( node = node.getChild( startOffset ) ) )
\r
129 node = ( guard ( startCt, true ) === false ) ?
\r
130 null : startCt.getNextSourceNode( true, type, guard ) ;
\r
134 if ( node && guard( node ) === false )
\r
138 while ( node && !this._.end )
\r
140 this.current = node;
\r
142 if ( !this.evaluator || this.evaluator( node ) !== false )
\r
144 if ( !breakOnFalse )
\r
147 else if ( breakOnFalse && this.evaluator )
\r
150 node = node[ getSourceNodeFn ]( false, type, guard );
\r
154 return this.current = null;
\r
157 function iterateToLast( rtl )
\r
159 var node, last = null;
\r
161 while ( ( node = iterate.call( this, rtl ) ) )
\r
167 CKEDITOR.dom.walker = CKEDITOR.tools.createClass(
\r
170 * Utility class to "walk" the DOM inside a range boundaries. If
\r
171 * necessary, partially included nodes (text nodes) are broken to
\r
172 * reflect the boundaries limits, so DOM and range changes may happen.
\r
173 * Outside changes to the range may break the walker.
\r
175 * The walker may return nodes that are not totaly included into the
\r
176 * range boundaires. Let's take the following range representation,
\r
177 * where the square brackets indicate the boundaries:
\r
179 * [<p>Some <b>sample] text</b>
\r
181 * While walking forward into the above range, the following nodes are
\r
182 * returned: <p>, "Some ", <b> and "sample". Going
\r
183 * backwards instead we have: "sample" and "Some ". So note that the
\r
184 * walker always returns nodes when "entering" them, but not when
\r
185 * "leaving" them. The guard function is instead called both when
\r
186 * entering and leaving nodes.
\r
189 * @param {CKEDITOR.dom.range} range The range within which walk.
\r
191 $ : function( range )
\r
193 this.range = range;
\r
196 * A function executed for every matched node, to check whether
\r
197 * it's to be considered into the walk or not. If not provided, all
\r
198 * matched nodes are considered good.
\r
199 * If the function returns "false" the node is ignored.
\r
200 * @name CKEDITOR.dom.walker.prototype.evaluator
\r
204 // this.evaluator = null;
\r
207 * A function executed for every node the walk pass by to check
\r
208 * whether the walk is to be finished. It's called when both
\r
209 * entering and exiting nodes, as well as for the matched nodes.
\r
210 * If this function returns "false", the walking ends and no more
\r
211 * nodes are evaluated.
\r
212 * @name CKEDITOR.dom.walker.prototype.guard
\r
216 // this.guard = null;
\r
224 // /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes.
\r
225 // * @param {CKEDITOR.dom.node} startNode The node from wich the walk
\r
227 // * @param {CKEDITOR.dom.node} [endNode] The last node to be considered
\r
228 // * in the walk. No more nodes are retrieved after touching or
\r
229 // * passing it. If not provided, the walker stops at the
\r
230 // * <body> closing boundary.
\r
231 // * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the
\r
232 // * provided nodes.
\r
234 // createOnNodes : function( startNode, endNode, startInclusive, endInclusive )
\r
236 // var range = new CKEDITOR.dom.range();
\r
237 // if ( startNode )
\r
238 // range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;
\r
240 // range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;
\r
243 // range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;
\r
245 // range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;
\r
247 // return new CKEDITOR.dom.walker( range );
\r
254 * Stop walking. No more nodes are retrieved if this function gets
\r
263 * Retrieves the next node (at right).
\r
264 * @returns {CKEDITOR.dom.node} The next node or null if no more
\r
265 * nodes are available.
\r
269 return iterate.call( this );
\r
273 * Retrieves the previous node (at left).
\r
274 * @returns {CKEDITOR.dom.node} The previous node or null if no more
\r
275 * nodes are available.
\r
277 previous : function()
\r
279 return iterate.call( this, 1 );
\r
283 * Check all nodes at right, executing the evaluation fuction.
\r
284 * @returns {Boolean} "false" if the evaluator function returned
\r
285 * "false" for any of the matched nodes. Otherwise "true".
\r
287 checkForward : function()
\r
289 return iterate.call( this, 0, 1 ) !== false;
\r
293 * Check all nodes at left, executing the evaluation fuction.
\r
294 * @returns {Boolean} "false" if the evaluator function returned
\r
295 * "false" for any of the matched nodes. Otherwise "true".
\r
297 checkBackward : function()
\r
299 return iterate.call( this, 1, 1 ) !== false;
\r
303 * Executes a full walk forward (to the right), until no more nodes
\r
304 * are available, returning the last valid node.
\r
305 * @returns {CKEDITOR.dom.node} The last node at the right or null
\r
306 * if no valid nodes are available.
\r
308 lastForward : function()
\r
310 return iterateToLast.call( this );
\r
314 * Executes a full walk backwards (to the left), until no more nodes
\r
315 * are available, returning the last valid node.
\r
316 * @returns {CKEDITOR.dom.node} The last node at the left or null
\r
317 * if no valid nodes are available.
\r
319 lastBackward : function()
\r
321 return iterateToLast.call( this, 1 );
\r
326 delete this.current;
\r
334 * Anything whose display computed style is block, list-item, table,
\r
335 * table-row-group, table-header-group, table-footer-group, table-row,
\r
336 * table-column-group, table-column, table-cell, table-caption, or whose node
\r
337 * name is hr, br (when enterMode is br only) is a block boundary.
\r
339 var blockBoundaryDisplayMatch =
\r
344 'table-row-group' : 1,
\r
345 'table-header-group' : 1,
\r
346 'table-footer-group' : 1,
\r
348 'table-column-group' : 1,
\r
349 'table-column' : 1,
\r
351 'table-caption' : 1
\r
354 CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
\r
356 var nodeNameMatches = customNodeNames ?
\r
357 CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} ) :
\r
358 CKEDITOR.dtd.$block;
\r
360 // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)
\r
361 return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ]
\r
362 || nodeNameMatches[ this.getName() ];
\r
365 CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )
\r
367 return function( node , type )
\r
369 return ! ( node.type == CKEDITOR.NODE_ELEMENT
\r
370 && node.isBlockBoundary( customNodeNames ) );
\r
374 CKEDITOR.dom.walker.listItemBoundary = function()
\r
376 return this.blockBoundary( { br : 1 } );
\r
380 * Whether the to-be-evaluated node is a bookmark node OR bookmark node
\r
382 * @param {Boolean} contentOnly Whether only test againt the text content of
\r
383 * bookmark node instead of the element itself(default).
\r
384 * @param {Boolean} isReject Whether should return 'false' for the bookmark
\r
385 * node instead of 'true'(default).
\r
387 CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )
\r
389 function isBookmarkNode( node )
\r
391 return ( node && node.getName
\r
392 && node.getName() == 'span'
\r
393 && node.data( 'cke-bookmark' ) );
\r
396 return function( node )
\r
398 var isBookmark, parent;
\r
399 // Is bookmark inner text node?
\r
400 isBookmark = ( node && !node.getName && ( parent = node.getParent() )
\r
401 && isBookmarkNode( parent ) );
\r
402 // Is bookmark node?
\r
403 isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
\r
404 return !! ( isReject ^ isBookmark );
\r
409 * Whether the node is a text node containing only whitespaces characters.
\r
412 CKEDITOR.dom.walker.whitespaces = function( isReject )
\r
414 return function( node )
\r
416 var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT )
\r
417 && !CKEDITOR.tools.trim( node.getText() );
\r
418 return !! ( isReject ^ isWhitespace );
\r
423 * Whether the node is invisible in wysiwyg mode.
\r
426 CKEDITOR.dom.walker.invisible = function( isReject )
\r
428 var whitespace = CKEDITOR.dom.walker.whitespaces();
\r
429 return function( node )
\r
431 // Nodes that take no spaces in wysiwyg:
\r
432 // 1. White-spaces but not including NBSP;
\r
433 // 2. Empty inline elements, e.g. <b></b> we're checking here
\r
434 // 'offsetHeight' instead of 'offsetWidth' for properly excluding
\r
435 // all sorts of empty paragraph, e.g. <br />.
\r
436 var isInvisible = whitespace( node ) || node.is && !node.$.offsetHeight;
\r
437 return !! ( isReject ^ isInvisible );
\r
441 CKEDITOR.dom.walker.nodeType = function( type, isReject )
\r
443 return function( node )
\r
445 return !! ( isReject ^ ( node.type == type ) );
\r
449 CKEDITOR.dom.walker.bogus = function( isReject )
\r
451 function nonEmpty( node )
\r
453 return !isWhitespaces( node ) && !isBookmark( node );
\r
456 return function( node )
\r
458 var isBogus = !CKEDITOR.env.ie ? node.is && node.is( 'br' ) :
\r
459 node.getText && tailNbspRegex.test( node.getText() );
\r
463 var parent = node.getParent(), next = node.getNext( nonEmpty );
\r
464 isBogus = parent.isBlockBoundary() &&
\r
466 next.type == CKEDITOR.NODE_ELEMENT &&
\r
467 next.isBlockBoundary() );
\r
470 return !! ( isReject ^ isBogus );
\r
474 var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/,
\r
475 isWhitespaces = CKEDITOR.dom.walker.whitespaces(),
\r
476 isBookmark = CKEDITOR.dom.walker.bookmark(),
\r
477 toSkip = function( node )
\r
479 return isBookmark( node )
\r
480 || isWhitespaces( node )
\r
481 || node.type == CKEDITOR.NODE_ELEMENT
\r
482 && node.getName() in CKEDITOR.dtd.$inline
\r
483 && !( node.getName() in CKEDITOR.dtd.$empty );
\r
486 // Check if there's a filler node at the end of an element, and return it.
\r
487 CKEDITOR.dom.element.prototype.getBogus = function()
\r
489 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
\r
491 do { tail = tail.getPreviousSourceNode(); }
\r
492 while ( toSkip( tail ) )
\r
494 if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' )
\r
495 : tail.getText && tailNbspRegex.test( tail.getText() ) ) )
\r