2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
8 // This function is to be called under a "walker" instance scope.
9 function iterate( rtl
, breakOnFalse
)
11 // Return null if we have reached the end.
18 userGuard
= this.guard
,
20 getSourceNodeFn
= ( rtl
? 'getPreviousSourceNode' : 'getNextSourceNode' );
22 // This is the first call. Initialize it.
27 // Trim text nodes and optmize the range boundaries. DOM changes
28 // may happen at this point.
31 // A collapsed range must return null at first call.
32 if ( range
.collapsed
)
39 // Create the LTR guard function, if necessary.
40 if ( !rtl
&& !this._
.guardLTR
)
42 // Gets the node that stops the walker when going LTR.
43 var limitLTR
= range
.endContainer
,
44 blockerLTR
= limitLTR
.getChild( range
.endOffset
);
46 this._
.guardLTR = function( node
, movingOut
)
48 return ( ( !movingOut
|| !limitLTR
.equals( node
) )
49 && ( !blockerLTR
|| !node
.equals( blockerLTR
) )
50 && ( node
.type
!= CKEDITOR
.NODE_ELEMENT
|| !movingOut
|| node
.getName() != 'body' ) );
54 // Create the RTL guard function, if necessary.
55 if ( rtl
&& !this._
.guardRTL
)
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 );
61 this._
.guardRTL = function( node
, movingOut
)
63 return ( ( !movingOut
|| !limitRTL
.equals( node
) )
64 && ( !blockerRTL
|| !node
.equals( blockerRTL
) )
65 && ( node
.type
!= CKEDITOR
.NODE_ELEMENT
|| !movingOut
|| node
.getName() != 'body' ) );
69 // Define which guard function to use.
70 var stopGuard
= rtl
? this._
.guardRTL
: this._
.guardLTR
;
72 // Make the user defined guard function participate in the process,
73 // otherwise simply use the boundary guard.
76 guard = function( node
, movingOut
)
78 if ( stopGuard( node
, movingOut
) === false )
81 return userGuard( node
, movingOut
);
88 node
= this.current
[ getSourceNodeFn
]( false, type
, guard
);
91 // Get the first node to be returned.
95 node
= range
.endContainer
;
97 if ( range
.endOffset
> 0 )
99 node
= node
.getChild( range
.endOffset
- 1 );
100 if ( guard( node
) === false )
104 node
= ( guard ( node
, true ) === false ) ?
105 null : node
.getPreviousSourceNode( true, type
, guard
);
109 node
= range
.startContainer
;
110 node
= node
.getChild( range
.startOffset
);
114 if ( guard( node
) === false )
118 node
= ( guard ( range
.startContainer
, true ) === false ) ?
119 null : range
.startContainer
.getNextSourceNode( true, type
, guard
) ;
123 while ( node
&& !this._
.end
)
127 if ( !this.evaluator
|| this.evaluator( node
) !== false )
132 else if ( breakOnFalse
&& this.evaluator
)
135 node
= node
[ getSourceNodeFn
]( false, type
, guard
);
139 return this.current
= null;
142 function iterateToLast( rtl
)
144 var node
, last
= null;
146 while ( ( node
= iterate
.call( this, rtl
) ) )
152 CKEDITOR
.dom
.walker
= CKEDITOR
.tools
.createClass(
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.
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:
164 * [<p>Some <b>sample] text</b>
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.
174 * @param {CKEDITOR.dom.range} range The range within which walk.
176 $ : function( range
)
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
189 // this.evaluator = null;
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
201 // this.guard = null;
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
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
219 // createOnNodes : function( startNode, endNode, startInclusive, endInclusive )
221 // var range = new CKEDITOR.dom.range();
223 // range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;
225 // range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;
228 // range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;
230 // range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;
232 // return new CKEDITOR.dom.walker( range );
239 * Stop walking. No more nodes are retrieved if this function gets
248 * Retrieves the next node (at right).
249 * @returns {CKEDITOR.dom.node} The next node or null if no more
250 * nodes are available.
254 return iterate
.call( this );
258 * Retrieves the previous node (at left).
259 * @returns {CKEDITOR.dom.node} The previous node or null if no more
260 * nodes are available.
262 previous : function()
264 return iterate
.call( this, 1 );
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".
272 checkForward : function()
274 return iterate
.call( this, 0, 1 ) !== false;
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".
282 checkBackward : function()
284 return iterate
.call( this, 1, 1 ) !== false;
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.
293 lastForward : function()
295 return iterateToLast
.call( this );
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.
304 lastBackward : function()
306 return iterateToLast
.call( this, 1 );
319 * Anything whose display computed style is block, list-item, table,
320 * table-row-group, table-header-group, table-footer-group, table-row,
321 * table-column-group, table-column, table-cell, table-caption, or whose node
322 * name is hr, br (when enterMode is br only) is a block boundary.
324 var blockBoundaryDisplayMatch
=
329 'table-row-group' : 1,
330 'table-header-group' : 1,
331 'table-footer-group' : 1,
333 'table-column-group' : 1,
339 CKEDITOR
.dom
.element
.prototype.isBlockBoundary = function( customNodeNames
)
341 var nodeNameMatches
= customNodeNames
?
342 CKEDITOR
.tools
.extend( {}, CKEDITOR
.dtd
.$block
, customNodeNames
|| {} ) :
345 // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)
346 return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch
[ this.getComputedStyle( 'display' ) ]
347 || nodeNameMatches
[ this.getName() ];
350 CKEDITOR
.dom
.walker
.blockBoundary = function( customNodeNames
)
352 return function( node
, type
)
354 return ! ( node
.type
== CKEDITOR
.NODE_ELEMENT
355 && node
.isBlockBoundary( customNodeNames
) );
359 CKEDITOR
.dom
.walker
.listItemBoundary = function()
361 return this.blockBoundary( { br
: 1 } );
365 * Whether the to-be-evaluated node is a bookmark node OR bookmark node
367 * @param {Boolean} contentOnly Whether only test againt the text content of
368 * bookmark node instead of the element itself(default).
369 * @param {Boolean} isReject Whether should return 'false' for the bookmark
370 * node instead of 'true'(default).
372 CKEDITOR
.dom
.walker
.bookmark = function( contentOnly
, isReject
)
374 function isBookmarkNode( node
)
376 return ( node
&& node
.getName
377 && node
.getName() == 'span'
378 && node
.data( 'cke-bookmark' ) );
381 return function( node
)
383 var isBookmark
, parent
;
384 // Is bookmark inner text node?
385 isBookmark
= ( node
&& !node
.getName
&& ( parent
= node
.getParent() )
386 && isBookmarkNode( parent
) );
388 isBookmark
= contentOnly
? isBookmark
: isBookmark
|| isBookmarkNode( node
);
389 return !! ( isReject
^ isBookmark
);
394 * Whether the node is a text node containing only whitespaces characters.
397 CKEDITOR
.dom
.walker
.whitespaces = function( isReject
)
399 return function( node
)
401 var isWhitespace
= node
&& ( node
.type
== CKEDITOR
.NODE_TEXT
)
402 && !CKEDITOR
.tools
.trim( node
.getText() );
403 return !! ( isReject
^ isWhitespace
);
408 * Whether the node is invisible in wysiwyg mode.
411 CKEDITOR
.dom
.walker
.invisible = function( isReject
)
413 var whitespace
= CKEDITOR
.dom
.walker
.whitespaces();
414 return function( node
)
416 // Nodes that take no spaces in wysiwyg:
417 // 1. White-spaces but not including NBSP;
418 // 2. Empty inline elements, e.g. <b></b> we're checking here
419 // 'offsetHeight' instead of 'offsetWidth' for properly excluding
420 // all sorts of empty paragraph, e.g. <br />.
421 var isInvisible
= whitespace( node
) || node
.is
&& !node
.$.offsetHeight
;
422 return !! ( isReject
^ isInvisible
);
426 CKEDITOR
.dom
.walker
.nodeType = function( type
, isReject
)
428 return function( node
)
430 return !! ( isReject
^ ( node
.type
== type
) );
434 var tailNbspRegex
= /^[\t\r\n ]*(?: |\xa0)$/,
435 isWhitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
436 isBookmark
= CKEDITOR
.dom
.walker
.bookmark(),
437 toSkip = function( node
)
439 return isBookmark( node
)
440 || isWhitespaces( node
)
441 || node
.type
== CKEDITOR
.NODE_ELEMENT
442 && node
.getName() in CKEDITOR
.dtd
.$inline
443 && !( node
.getName() in CKEDITOR
.dtd
.$empty
);
446 // Check if there's a filler node at the end of an element, and return it.
447 CKEDITOR
.dom
.element
.prototype.getBogus = function()
449 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
451 do { tail
= tail
.getPreviousSourceNode(); }
452 while ( toSkip( tail
) )
454 if ( tail
&& ( !CKEDITOR
.env
.ie
? tail
.is
&& tail
.is( 'br' )
455 : tail
.getText
&& tailNbspRegex
.test( tail
.getText() ) ) )