1 /** 2 * modified from ckeditor for kissy editor ,walker implementation 3 * refer: http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal#TreeWalker 4 * @author yiminghe@gmail.com 5 */ 6 /* 7 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 8 For licensing, see LICENSE.html or http://ckeditor.com/license 9 */ 10 KISSY.add("editor/core/walker", function (S, Editor) { 11 12 var TRUE = true, 13 FALSE = false, 14 NULL = null, 15 UA = S.UA, 16 DOM = S.DOM, 17 dtd = Editor.XHTML_DTD, 18 Node = S.Node; 19 20 21 function iterate(rtl, breakOnFalseRetFalse) { 22 var self = this; 23 // Return NULL if we have reached the end. 24 if (self._.end) { 25 return NULL; 26 } 27 var node, 28 range = self.range, 29 guard, 30 userGuard = self.guard, 31 type = self.type, 32 getSourceNodeFn = ( rtl ? '_4e_previousSourceNode' : 33 '_4e_nextSourceNode' ); 34 35 // This is the first call. Initialize it. 36 if (!self._.start) { 37 self._.start = 1; 38 39 // Trim text nodes and optimize the range boundaries. DOM changes 40 // may happen at this point. 41 range.trim(); 42 43 // A collapsed range must return NULL at first call. 44 if (range.collapsed) { 45 self.end(); 46 return NULL; 47 } 48 } 49 50 // Create the LTR guard function, if necessary. 51 if (!rtl && !self._.guardLTR) { 52 // Gets the node that stops the walker when going LTR. 53 var limitLTR = range.endContainer[0], 54 blockerLTR = limitLTR.childNodes[range.endOffset]; 55 // 从左到右保证在 range 区间内获取 nextSourceNode 56 this._.guardLTR = function (node, movingOut) { 57 // 从endContainer移出去,失败返回false 58 if (movingOut && (limitLTR == node || DOM.nodeName(node) == "body")) { 59 return false; 60 } 61 // 达到边界的下一个节点,注意 null 的情况 62 // node 永远不能为 null 63 return node != blockerLTR; 64 65 }; 66 } 67 68 // Create the RTL guard function, if necessary. 69 if (rtl && !self._.guardRTL) { 70 // Gets the node that stops the walker when going LTR. 71 var limitRTL = range.startContainer[0], 72 blockerRTL = ( range.startOffset > 0 ) && 73 limitRTL.childNodes[range.startOffset - 1] || null; 74 75 self._.guardRTL = function (node, movingOut) { 76 // 从endContainer移出去,失败返回false 77 if (movingOut && (limitRTL == node || DOM.nodeName(node) == "body")) { 78 return false; 79 } 80 // 达到边界的下一个节点,注意 null 的情况 81 // node 永远不能为 null 82 return node != blockerRTL; 83 }; 84 } 85 86 // Define which guard function to use. 87 var stopGuard = rtl ? self._.guardRTL : self._.guardLTR; 88 89 // Make the user defined guard function participate in the process, 90 // otherwise simply use the boundary guard. 91 if (userGuard) { 92 guard = function (node, movingOut) { 93 if (stopGuard(node, movingOut) === FALSE) { 94 return FALSE; 95 } 96 return userGuard(node, movingOut); 97 }; 98 } 99 else { 100 guard = stopGuard; 101 } 102 103 if (self.current) { 104 node = this.current[ getSourceNodeFn ](FALSE, type, guard); 105 } else { 106 // Get the first node to be returned. 107 108 if (rtl) { 109 node = range.endContainer; 110 if (range.endOffset > 0) { 111 node = new Node(node[0].childNodes[range.endOffset - 1]); 112 if (guard(node[0]) === FALSE) { 113 node = NULL; 114 } 115 } else { 116 node = ( guard(node, TRUE) === FALSE ) ? 117 NULL : node._4e_previousSourceNode(TRUE, type, guard, undefined); 118 } 119 } 120 else { 121 node = range.startContainer; 122 node = new Node(node[0].childNodes[range.startOffset]); 123 124 if (node.length) { 125 if (guard(node[0]) === FALSE) { 126 node = NULL; 127 } 128 } else { 129 node = ( guard(range.startContainer, TRUE) === FALSE ) ? 130 NULL : range.startContainer._4e_nextSourceNode(TRUE, type, guard, undefined); 131 } 132 } 133 } 134 135 while (node && !self._.end) { 136 self.current = node; 137 if (!self.evaluator || self.evaluator(node[0]) !== FALSE) { 138 if (!breakOnFalseRetFalse) { 139 return node; 140 } 141 } else if (breakOnFalseRetFalse && self.evaluator) { 142 return FALSE; 143 } 144 node = node[ getSourceNodeFn ](FALSE, type, guard); 145 } 146 147 self.end(); 148 return self.current = NULL; 149 } 150 151 function iterateToLast(rtl) { 152 var node, 153 last = NULL; 154 while (node = iterate.call(this, rtl)) { 155 last = node; 156 } 157 return last; 158 } 159 160 /** 161 * @name Walker 162 * @param {Editor.Range} range 163 * @class 164 * Walker for DOM. 165 * @memberOf Editor 166 */ 167 function Walker(range) { 168 this.range = range; 169 170 /** 171 * A function executed for every matched node, to check whether 172 * it's to be considered into the walk or not. If not provided, all 173 * matched nodes are considered good. 174 * If the function returns "FALSE" the node is ignored. 175 * @type Function 176 * @memberOf Editor.Walker# 177 */ 178 this.evaluator = NULL;// 当前 range 范围内深度遍历的元素调用 179 180 /** 181 * A function executed for every node the walk pass by to check 182 * whether the walk is to be finished. It's called when both 183 * entering and exiting nodes, as well as for the matched nodes. 184 * If this function returns "FALSE", the walking ends and no more 185 * nodes are evaluated. 186 * @type Function 187 * @memberOf Editor.Walker# 188 */ 189 this.guard = NULL;// 人为缩小当前 range 范围 190 191 192 /** @private */ 193 this._ = {}; 194 } 195 196 197 S.augment(Walker, 198 /** 199 * @lends Editor.Walker# 200 */ 201 { 202 /** 203 * Stop walking. No more nodes are retrieved if this function gets 204 * called. 205 */ 206 end:function () { 207 this._.end = 1; 208 }, 209 210 /** 211 * Retrieves the next node (at right). 212 * @returns {Boolean} The next node or NULL if no more 213 * nodes are available. 214 */ 215 next:function () { 216 return iterate.call(this); 217 }, 218 219 /** 220 * Retrieves the previous node (at left). 221 * @returns {Boolean} The previous node or NULL if no more 222 * nodes are available. 223 */ 224 previous:function () { 225 return iterate.call(this, TRUE); 226 }, 227 228 /** 229 * Check all nodes at right, executing the evaluation function. 230 * @returns {Boolean} "FALSE" if the evaluator function returned 231 * "FALSE" for any of the matched nodes. Otherwise "TRUE". 232 */ 233 checkForward:function () { 234 return iterate.call(this, FALSE, TRUE) !== FALSE; 235 }, 236 237 /** 238 * Check all nodes at left, executing the evaluation function. 239 * 是不是 (不能后退了) 240 * @returns {Boolean} "FALSE" if the evaluator function returned 241 * "FALSE" for any of the matched nodes. Otherwise "TRUE". 242 */ 243 checkBackward:function () { 244 // 在当前 range 范围内不会出现 evaluator 返回 false 的情况 245 return iterate.call(this, TRUE, TRUE) !== FALSE; 246 }, 247 248 /** 249 * Executes a full walk forward (to the right), until no more nodes 250 * are available, returning the last valid node. 251 * @returns {Boolean} The last node at the right or NULL 252 * if no valid nodes are available. 253 */ 254 lastForward:function () { 255 return iterateToLast.call(this); 256 }, 257 258 /** 259 * Executes a full walk backwards (to the left), until no more nodes 260 * are available, returning the last valid node. 261 * @returns {Boolean} The last node at the left or NULL 262 * if no valid nodes are available. 263 */ 264 lastBackward:function () { 265 return iterateToLast.call(this, TRUE); 266 }, 267 268 reset:function () { 269 delete this.current; 270 this._ = {}; 271 }, 272 273 // for unit test 274 _iterator:iterate 275 276 }); 277 278 279 S.mix(Walker, 280 /** 281 * @lends Editor.Walker 282 */ 283 { 284 /** 285 * Whether the to-be-evaluated node is not a block node and does not match given node name map. 286 * @param {Object} customNodeNames Given node name map. 287 * @return {Function} Function for evaluation. 288 */ 289 blockBoundary:function (customNodeNames) { 290 return function (node) { 291 return !(node.nodeType == DOM.ELEMENT_NODE && 292 DOM._4e_isBlockBoundary(node, customNodeNames) ); 293 }; 294 }, 295 296 /** 297 * Whether the to-be-evaluated node is a bookmark node OR bookmark node 298 * inner contents. 299 * @param {Boolean} [contentOnly] Whether only test againt the text content of 300 * bookmark node instead of the element itself(default). 301 * @param {Boolean} [isReject] Whether should return 'FALSE' for the bookmark 302 * node instead of 'TRUE'(default). 303 * @return {Function} Function for evaluation. 304 */ 305 bookmark:function (contentOnly, isReject) { 306 function isBookmarkNode(node) { 307 return DOM.nodeName(node) == 'span' && 308 DOM.attr(node, '_ke_bookmark'); 309 } 310 311 return function (node) { 312 var isBookmark, parent; 313 // Is bookmark inner text node? 314 isBookmark = ( node.nodeType == DOM.TEXT_NODE && 315 ( parent = node.parentNode ) && 316 isBookmarkNode(parent) ); 317 // Is bookmark node? 318 isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode(node); 319 // !! 2012-05-15 320 // evaluator check ===false, must turn it to boolean false 321 return !!(isReject ^ isBookmark); 322 }; 323 }, 324 325 /** 326 * Whether the node is a text node containing only whitespaces characters. 327 * @param {Boolean} [isReject] 328 */ 329 whitespaces:function (isReject) { 330 return function (node) { 331 var isWhitespace = node.nodeType == DOM.TEXT_NODE && 332 !S.trim(node.nodeValue); 333 return !!(isReject ^ isWhitespace); 334 }; 335 }, 336 /** 337 * Whether the node is invisible in wysiwyg mode. 338 * @param isReject 339 */ 340 invisible:function (isReject) { 341 var whitespace = Walker.whitespaces(); 342 return function (node) { 343 // Nodes that take no spaces in wysiwyg: 344 // 1. White-spaces but not including NBSP; 345 // 2. Empty inline elements, e.g. <b></b> we're checking here 346 // 'offsetHeight' instead of 'offsetWidth' for properly excluding 347 // all sorts of empty paragraph, e.g. <br />. 348 var isInvisible = whitespace(node) || 349 node.nodeType == DOM.ELEMENT_NODE && !node.offsetHeight; 350 return !!(isReject ^ isInvisible); 351 }; 352 } 353 }); 354 355 var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/, 356 isWhitespaces = Walker.whitespaces(), 357 isBookmark = Walker.bookmark(), 358 toSkip = function (node) { 359 var name = DOM.nodeName(node); 360 return isBookmark(node) || 361 isWhitespaces(node) || 362 node.nodeType == 1 && 363 name in dtd.$inline && 364 !( name in dtd.$empty ); 365 }; 366 367 // Check if there's a filler node at the end of an element, and return it. 368 function getBogus(tail) { 369 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> 370 do { 371 tail = tail._4e_previousSourceNode(); 372 } while (tail && toSkip(tail[0])); 373 374 if (tail && ( !UA.ie ? tail.nodeName() == "br" 375 : tail[0].nodeType == 3 && tailNbspRegex.test(tail.text()) )) { 376 return tail[0]; 377 } 378 return false; 379 } 380 381 Editor.Utils.injectDom({ 382 _4e_getBogus:function (el) { 383 return getBogus(new Node(el)); 384 } 385 }); 386 387 Editor.Walker = Walker; 388 389 return Walker; 390 }, { 391 requires:['./base', './utils', './dom'] 392 }); 393