--- /dev/null
+/*\r
+Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
+For licensing, see LICENSE.html or http://ckeditor.com/license\r
+*/\r
+\r
+(function()\r
+{\r
+ // This function is to be called under a "walker" instance scope.\r
+ function iterate( rtl, breakOnFalse )\r
+ {\r
+ // Return null if we have reached the end.\r
+ if ( this._.end )\r
+ return null;\r
+\r
+ var node,\r
+ range = this.range,\r
+ guard,\r
+ userGuard = this.guard,\r
+ type = this.type,\r
+ getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );\r
+\r
+ // This is the first call. Initialize it.\r
+ if ( !this._.start )\r
+ {\r
+ this._.start = 1;\r
+\r
+ // Trim text nodes and optmize the range boundaries. DOM changes\r
+ // may happen at this point.\r
+ range.trim();\r
+\r
+ // A collapsed range must return null at first call.\r
+ if ( range.collapsed )\r
+ {\r
+ this.end();\r
+ return null;\r
+ }\r
+ }\r
+\r
+ // Create the LTR guard function, if necessary.\r
+ if ( !rtl && !this._.guardLTR )\r
+ {\r
+ // Gets the node that stops the walker when going LTR.\r
+ var limitLTR = range.endContainer,\r
+ blockerLTR = limitLTR.getChild( range.endOffset );\r
+\r
+ this._.guardLTR = function( node, movingOut )\r
+ {\r
+ return ( ( !movingOut || !limitLTR.equals( node ) )\r
+ && ( !blockerLTR || !node.equals( blockerLTR ) )\r
+ && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );\r
+ };\r
+ }\r
+\r
+ // Create the RTL guard function, if necessary.\r
+ if ( rtl && !this._.guardRTL )\r
+ {\r
+ // Gets the node that stops the walker when going LTR.\r
+ var limitRTL = range.startContainer,\r
+ blockerRTL = ( range.startOffset > 0 ) && limitRTL.getChild( range.startOffset - 1 );\r
+\r
+ this._.guardRTL = function( node, movingOut )\r
+ {\r
+ return ( ( !movingOut || !limitRTL.equals( node ) )\r
+ && ( !blockerRTL || !node.equals( blockerRTL ) )\r
+ && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );\r
+ };\r
+ }\r
+\r
+ // Define which guard function to use.\r
+ var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;\r
+\r
+ // Make the user defined guard function participate in the process,\r
+ // otherwise simply use the boundary guard.\r
+ if ( userGuard )\r
+ {\r
+ guard = function( node, movingOut )\r
+ {\r
+ if ( stopGuard( node, movingOut ) === false )\r
+ return false;\r
+\r
+ return userGuard( node, movingOut );\r
+ };\r
+ }\r
+ else\r
+ guard = stopGuard;\r
+\r
+ if ( this.current )\r
+ node = this.current[ getSourceNodeFn ]( false, type, guard );\r
+ else\r
+ {\r
+ // Get the first node to be returned.\r
+\r
+ if ( rtl )\r
+ {\r
+ node = range.endContainer;\r
+\r
+ if ( range.endOffset > 0 )\r
+ {\r
+ node = node.getChild( range.endOffset - 1 );\r
+ if ( guard( node ) === false )\r
+ node = null;\r
+ }\r
+ else\r
+ node = ( guard ( node, true ) === false ) ?\r
+ null : node.getPreviousSourceNode( true, type, guard );\r
+ }\r
+ else\r
+ {\r
+ node = range.startContainer;\r
+ node = node.getChild( range.startOffset );\r
+\r
+ if ( node )\r
+ {\r
+ if ( guard( node ) === false )\r
+ node = null;\r
+ }\r
+ else\r
+ node = ( guard ( range.startContainer, true ) === false ) ?\r
+ null : range.startContainer.getNextSourceNode( true, type, guard ) ;\r
+ }\r
+ }\r
+\r
+ while ( node && !this._.end )\r
+ {\r
+ this.current = node;\r
+\r
+ if ( !this.evaluator || this.evaluator( node ) !== false )\r
+ {\r
+ if ( !breakOnFalse )\r
+ return node;\r
+ }\r
+ else if ( breakOnFalse && this.evaluator )\r
+ return false;\r
+\r
+ node = node[ getSourceNodeFn ]( false, type, guard );\r
+ }\r
+\r
+ this.end();\r
+ return this.current = null;\r
+ }\r
+\r
+ function iterateToLast( rtl )\r
+ {\r
+ var node, last = null;\r
+\r
+ while ( ( node = iterate.call( this, rtl ) ) )\r
+ last = node;\r
+\r
+ return last;\r
+ }\r
+\r
+ CKEDITOR.dom.walker = CKEDITOR.tools.createClass(\r
+ {\r
+ /**\r
+ * Utility class to "walk" the DOM inside a range boundaries. If\r
+ * necessary, partially included nodes (text nodes) are broken to\r
+ * reflect the boundaries limits, so DOM and range changes may happen.\r
+ * Outside changes to the range may break the walker.\r
+ *\r
+ * The walker may return nodes that are not totaly included into the\r
+ * range boundaires. Let's take the following range representation,\r
+ * where the square brackets indicate the boundaries:\r
+ *\r
+ * [<p>Some <b>sample] text</b>\r
+ *\r
+ * While walking forward into the above range, the following nodes are\r
+ * returned: <p>, "Some ", <b> and "sample". Going\r
+ * backwards instead we have: "sample" and "Some ". So note that the\r
+ * walker always returns nodes when "entering" them, but not when\r
+ * "leaving" them. The guard function is instead called both when\r
+ * entering and leaving nodes.\r
+ *\r
+ * @constructor\r
+ * @param {CKEDITOR.dom.range} range The range within which walk.\r
+ */\r
+ $ : function( range )\r
+ {\r
+ this.range = range;\r
+\r
+ /**\r
+ * A function executed for every matched node, to check whether\r
+ * it's to be considered into the walk or not. If not provided, all\r
+ * matched nodes are considered good.\r
+ * If the function returns "false" the node is ignored.\r
+ * @name CKEDITOR.dom.walker.prototype.evaluator\r
+ * @property\r
+ * @type Function\r
+ */\r
+ // this.evaluator = null;\r
+\r
+ /**\r
+ * A function executed for every node the walk pass by to check\r
+ * whether the walk is to be finished. It's called when both\r
+ * entering and exiting nodes, as well as for the matched nodes.\r
+ * If this function returns "false", the walking ends and no more\r
+ * nodes are evaluated.\r
+ * @name CKEDITOR.dom.walker.prototype.guard\r
+ * @property\r
+ * @type Function\r
+ */\r
+ // this.guard = null;\r
+\r
+ /** @private */\r
+ this._ = {};\r
+ },\r
+\r
+// statics :\r
+// {\r
+// /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes.\r
+// * @param {CKEDITOR.dom.node} startNode The node from wich the walk\r
+// * will start.\r
+// * @param {CKEDITOR.dom.node} [endNode] The last node to be considered\r
+// * in the walk. No more nodes are retrieved after touching or\r
+// * passing it. If not provided, the walker stops at the\r
+// * <body> closing boundary.\r
+// * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the\r
+// * provided nodes.\r
+// */\r
+// createOnNodes : function( startNode, endNode, startInclusive, endInclusive )\r
+// {\r
+// var range = new CKEDITOR.dom.range();\r
+// if ( startNode )\r
+// range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;\r
+// else\r
+// range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;\r
+//\r
+// if ( endNode )\r
+// range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;\r
+// else\r
+// range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;\r
+//\r
+// return new CKEDITOR.dom.walker( range );\r
+// }\r
+// },\r
+//\r
+ proto :\r
+ {\r
+ /**\r
+ * Stop walking. No more nodes are retrieved if this function gets\r
+ * called.\r
+ */\r
+ end : function()\r
+ {\r
+ this._.end = 1;\r
+ },\r
+\r
+ /**\r
+ * Retrieves the next node (at right).\r
+ * @returns {CKEDITOR.dom.node} The next node or null if no more\r
+ * nodes are available.\r
+ */\r
+ next : function()\r
+ {\r
+ return iterate.call( this );\r
+ },\r
+\r
+ /**\r
+ * Retrieves the previous node (at left).\r
+ * @returns {CKEDITOR.dom.node} The previous node or null if no more\r
+ * nodes are available.\r
+ */\r
+ previous : function()\r
+ {\r
+ return iterate.call( this, 1 );\r
+ },\r
+\r
+ /**\r
+ * Check all nodes at right, executing the evaluation fuction.\r
+ * @returns {Boolean} "false" if the evaluator function returned\r
+ * "false" for any of the matched nodes. Otherwise "true".\r
+ */\r
+ checkForward : function()\r
+ {\r
+ return iterate.call( this, 0, 1 ) !== false;\r
+ },\r
+\r
+ /**\r
+ * Check all nodes at left, executing the evaluation fuction.\r
+ * @returns {Boolean} "false" if the evaluator function returned\r
+ * "false" for any of the matched nodes. Otherwise "true".\r
+ */\r
+ checkBackward : function()\r
+ {\r
+ return iterate.call( this, 1, 1 ) !== false;\r
+ },\r
+\r
+ /**\r
+ * Executes a full walk forward (to the right), until no more nodes\r
+ * are available, returning the last valid node.\r
+ * @returns {CKEDITOR.dom.node} The last node at the right or null\r
+ * if no valid nodes are available.\r
+ */\r
+ lastForward : function()\r
+ {\r
+ return iterateToLast.call( this );\r
+ },\r
+\r
+ /**\r
+ * Executes a full walk backwards (to the left), until no more nodes\r
+ * are available, returning the last valid node.\r
+ * @returns {CKEDITOR.dom.node} The last node at the left or null\r
+ * if no valid nodes are available.\r
+ */\r
+ lastBackward : function()\r
+ {\r
+ return iterateToLast.call( this, 1 );\r
+ },\r
+\r
+ reset : function()\r
+ {\r
+ delete this.current;\r
+ this._ = {};\r
+ }\r
+\r
+ }\r
+ });\r
+\r
+ /*\r
+ * Anything whose display computed style is block, list-item, table,\r
+ * table-row-group, table-header-group, table-footer-group, table-row,\r
+ * table-column-group, table-column, table-cell, table-caption, or whose node\r
+ * name is hr, br (when enterMode is br only) is a block boundary.\r
+ */\r
+ var blockBoundaryDisplayMatch =\r
+ {\r
+ block : 1,\r
+ 'list-item' : 1,\r
+ table : 1,\r
+ 'table-row-group' : 1,\r
+ 'table-header-group' : 1,\r
+ 'table-footer-group' : 1,\r
+ 'table-row' : 1,\r
+ 'table-column-group' : 1,\r
+ 'table-column' : 1,\r
+ 'table-cell' : 1,\r
+ 'table-caption' : 1\r
+ };\r
+\r
+ CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )\r
+ {\r
+ var nodeNameMatches = CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} );\r
+\r
+ // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)\r
+ return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ]\r
+ || nodeNameMatches[ this.getName() ];\r
+ };\r
+\r
+ CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )\r
+ {\r
+ return function( node , type )\r
+ {\r
+ return ! ( node.type == CKEDITOR.NODE_ELEMENT\r
+ && node.isBlockBoundary( customNodeNames ) );\r
+ };\r
+ };\r
+\r
+ CKEDITOR.dom.walker.listItemBoundary = function()\r
+ {\r
+ return this.blockBoundary( { br : 1 } );\r
+ };\r
+\r
+ /**\r
+ * Whether the to-be-evaluated node is a bookmark node OR bookmark node\r
+ * inner contents.\r
+ * @param {Boolean} contentOnly Whether only test againt the text content of\r
+ * bookmark node instead of the element itself(default).\r
+ * @param {Boolean} isReject Whether should return 'false' for the bookmark\r
+ * node instead of 'true'(default).\r
+ */\r
+ CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )\r
+ {\r
+ function isBookmarkNode( node )\r
+ {\r
+ return ( node && node.getName\r
+ && node.getName() == 'span'\r
+ && node.data( 'cke-bookmark' ) );\r
+ }\r
+\r
+ return function( node )\r
+ {\r
+ var isBookmark, parent;\r
+ // Is bookmark inner text node?\r
+ isBookmark = ( node && !node.getName && ( parent = node.getParent() )\r
+ && isBookmarkNode( parent ) );\r
+ // Is bookmark node?\r
+ isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );\r
+ return !! ( isReject ^ isBookmark );\r
+ };\r
+ };\r
+\r
+ /**\r
+ * Whether the node is a text node containing only whitespaces characters.\r
+ * @param isReject\r
+ */\r
+ CKEDITOR.dom.walker.whitespaces = function( isReject )\r
+ {\r
+ return function( node )\r
+ {\r
+ var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT )\r
+ && !CKEDITOR.tools.trim( node.getText() );\r
+ return !! ( isReject ^ isWhitespace );\r
+ };\r
+ };\r
+\r
+ /**\r
+ * Whether the node is invisible in wysiwyg mode.\r
+ * @param isReject\r
+ */\r
+ CKEDITOR.dom.walker.invisible = function( isReject )\r
+ {\r
+ var whitespace = CKEDITOR.dom.walker.whitespaces();\r
+ return function( node )\r
+ {\r
+ // Nodes that take no spaces in wysiwyg:\r
+ // 1. White-spaces but not including NBSP;\r
+ // 2. Empty inline elements, e.g. <b></b> we're checking here\r
+ // 'offsetHeight' instead of 'offsetWidth' for properly excluding\r
+ // all sorts of empty paragraph, e.g. <br />.\r
+ var isInvisible = whitespace( node ) || node.is && !node.$.offsetHeight;\r
+ return !! ( isReject ^ isInvisible );\r
+ };\r
+ };\r
+\r
+ CKEDITOR.dom.walker.nodeType = function( type, isReject )\r
+ {\r
+ return function( node )\r
+ {\r
+ return !! ( isReject ^ ( node.type == type ) );\r
+ };\r
+ };\r
+\r
+ var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/,\r
+ isWhitespaces = CKEDITOR.dom.walker.whitespaces(),\r
+ isBookmark = CKEDITOR.dom.walker.bookmark(),\r
+ toSkip = function( node )\r
+ {\r
+ return isBookmark( node )\r
+ || isWhitespaces( node )\r
+ || node.type == CKEDITOR.NODE_ELEMENT\r
+ && node.getName() in CKEDITOR.dtd.$inline\r
+ && !( node.getName() in CKEDITOR.dtd.$empty );\r
+ };\r
+\r
+ // Check if there's a filler node at the end of an element, and return it.\r
+ CKEDITOR.dom.element.prototype.getBogus = function()\r
+ {\r
+ // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).\r
+ var tail = this;\r
+ do { tail = tail.getPreviousSourceNode(); }\r
+ while ( toSkip( tail ) )\r
+\r
+ if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' )\r
+ : tail.getText && tailNbspRegex.test( tail.getText() ) ) )\r
+ {\r
+ return tail;\r
+ }\r
+ return false;\r
+ };\r
+\r
+})();\r