[239] | 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 | // 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 || !movingOut || 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 || !movingOut || 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, movingOut );
|
---|
| 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, true ) === 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, true ) === 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, 1 );
|
---|
| 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, 0, 1 ) !== 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, 1, 1 ) !== 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, 1 );
|
---|
| 307 | },
|
---|
| 308 |
|
---|
| 309 | reset : function()
|
---|
| 310 | {
|
---|
| 311 | delete this.current;
|
---|
| 312 | this._ = {};
|
---|
| 313 | }
|
---|
| 314 |
|
---|
| 315 | }
|
---|
| 316 | });
|
---|
| 317 |
|
---|
| 318 | /*
|
---|
| 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.
|
---|
| 323 | */
|
---|
| 324 | var blockBoundaryDisplayMatch =
|
---|
| 325 | {
|
---|
| 326 | block : 1,
|
---|
| 327 | 'list-item' : 1,
|
---|
| 328 | table : 1,
|
---|
| 329 | 'table-row-group' : 1,
|
---|
| 330 | 'table-header-group' : 1,
|
---|
| 331 | 'table-footer-group' : 1,
|
---|
| 332 | 'table-row' : 1,
|
---|
| 333 | 'table-column-group' : 1,
|
---|
| 334 | 'table-column' : 1,
|
---|
| 335 | 'table-cell' : 1,
|
---|
| 336 | 'table-caption' : 1
|
---|
| 337 | };
|
---|
| 338 |
|
---|
| 339 | CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
|
---|
| 340 | {
|
---|
| 341 | var nodeNameMatches = customNodeNames ?
|
---|
| 342 | CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} ) :
|
---|
| 343 | CKEDITOR.dtd.$block;
|
---|
| 344 |
|
---|
| 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() ];
|
---|
| 348 | };
|
---|
| 349 |
|
---|
| 350 | CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )
|
---|
| 351 | {
|
---|
| 352 | return function( node , type )
|
---|
| 353 | {
|
---|
| 354 | return ! ( node.type == CKEDITOR.NODE_ELEMENT
|
---|
| 355 | && node.isBlockBoundary( customNodeNames ) );
|
---|
| 356 | };
|
---|
| 357 | };
|
---|
| 358 |
|
---|
| 359 | CKEDITOR.dom.walker.listItemBoundary = function()
|
---|
| 360 | {
|
---|
| 361 | return this.blockBoundary( { br : 1 } );
|
---|
| 362 | };
|
---|
| 363 |
|
---|
| 364 | /**
|
---|
| 365 | * Whether the to-be-evaluated node is a bookmark node OR bookmark node
|
---|
| 366 | * inner contents.
|
---|
| 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).
|
---|
| 371 | */
|
---|
| 372 | CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )
|
---|
| 373 | {
|
---|
| 374 | function isBookmarkNode( node )
|
---|
| 375 | {
|
---|
| 376 | return ( node && node.getName
|
---|
| 377 | && node.getName() == 'span'
|
---|
| 378 | && node.data( 'cke-bookmark' ) );
|
---|
| 379 | }
|
---|
| 380 |
|
---|
| 381 | return function( node )
|
---|
| 382 | {
|
---|
| 383 | var isBookmark, parent;
|
---|
| 384 | // Is bookmark inner text node?
|
---|
| 385 | isBookmark = ( node && !node.getName && ( parent = node.getParent() )
|
---|
| 386 | && isBookmarkNode( parent ) );
|
---|
| 387 | // Is bookmark node?
|
---|
| 388 | isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
|
---|
| 389 | return !! ( isReject ^ isBookmark );
|
---|
| 390 | };
|
---|
| 391 | };
|
---|
| 392 |
|
---|
| 393 | /**
|
---|
| 394 | * Whether the node is a text node containing only whitespaces characters.
|
---|
| 395 | * @param isReject
|
---|
| 396 | */
|
---|
| 397 | CKEDITOR.dom.walker.whitespaces = function( isReject )
|
---|
| 398 | {
|
---|
| 399 | return function( node )
|
---|
| 400 | {
|
---|
| 401 | var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT )
|
---|
| 402 | && !CKEDITOR.tools.trim( node.getText() );
|
---|
| 403 | return !! ( isReject ^ isWhitespace );
|
---|
| 404 | };
|
---|
| 405 | };
|
---|
| 406 |
|
---|
| 407 | /**
|
---|
| 408 | * Whether the node is invisible in wysiwyg mode.
|
---|
| 409 | * @param isReject
|
---|
| 410 | */
|
---|
| 411 | CKEDITOR.dom.walker.invisible = function( isReject )
|
---|
| 412 | {
|
---|
| 413 | var whitespace = CKEDITOR.dom.walker.whitespaces();
|
---|
| 414 | return function( node )
|
---|
| 415 | {
|
---|
| 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 );
|
---|
| 423 | };
|
---|
| 424 | };
|
---|
| 425 |
|
---|
| 426 | CKEDITOR.dom.walker.nodeType = function( type, isReject )
|
---|
| 427 | {
|
---|
| 428 | return function( node )
|
---|
| 429 | {
|
---|
| 430 | return !! ( isReject ^ ( node.type == type ) );
|
---|
| 431 | };
|
---|
| 432 | };
|
---|
| 433 |
|
---|
| 434 | var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/,
|
---|
| 435 | isWhitespaces = CKEDITOR.dom.walker.whitespaces(),
|
---|
| 436 | isBookmark = CKEDITOR.dom.walker.bookmark(),
|
---|
| 437 | toSkip = function( node )
|
---|
| 438 | {
|
---|
| 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 );
|
---|
| 444 | };
|
---|
| 445 |
|
---|
| 446 | // Check if there's a filler node at the end of an element, and return it.
|
---|
| 447 | CKEDITOR.dom.element.prototype.getBogus = function()
|
---|
| 448 | {
|
---|
| 449 | // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
|
---|
| 450 | var tail = this;
|
---|
| 451 | do { tail = tail.getPreviousSourceNode(); }
|
---|
| 452 | while ( toSkip( tail ) )
|
---|
| 453 |
|
---|
| 454 | if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' )
|
---|
| 455 | : tail.getText && tailNbspRegex.test( tail.getText() ) ) )
|
---|
| 456 | {
|
---|
| 457 | return tail;
|
---|
| 458 | }
|
---|
| 459 | return false;
|
---|
| 460 | };
|
---|
| 461 |
|
---|
| 462 | })();
|
---|