/** * @ignore * dom iterator implementation using walker and nextSourceNode * @author yiminghe@gmail.com */ /* Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ KISSY.add("editor/domIterator", function (S, Editor) { var TRUE = true, FALSE = false, NULL = null, UA = S.UA, Walker = Editor.Walker, KERange = Editor.Range, KER = Editor.RangeType, ElementPath = Editor.ElementPath, Node = S.Node, Dom = S.DOM; /** * iterator for range * @class KISSY.Editor.Iterator * @param range {KISSY.Editor.Range} * @private */ function Iterator(range) { if (arguments.length < 1) return; var self = this; self.range = range; self.forceBrBreak = FALSE; // Whether include <br>s into the enlarged range.(#3730). self.enlargeBr = TRUE; self.enforceRealBlocks = FALSE; self._ || ( self._ = {} ); } var beginWhitespaceRegex = /^[\r\n\t ]*$/;///^[\r\n\t ]+$/,//+:*??不匹配空串 S.augment(Iterator, { //奇怪点: //<ul> // <li> // x // </li> // <li> // y // </li> // </ul> //会返回两次 li,li,而不是一次 ul , // 可能只是返回包含文字的段落概念? getNextParagraph: function (blockTag) { // The block element to be returned. var block, self = this; // The range object used to identify the paragraph contents. var range; // Indicats that the current element in the loop is the last one. var isLast; // Instructs to cleanup remaining BRs. var removePreviousBr, removeLastBr; // self is the first iteration. Let's initialize it. if (!self._.lastNode) { range = self.range.clone(); // 2010-09-30 shrink // 3.4.2 新增, // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435). range.shrink(KER.SHRINK_ELEMENT, TRUE); range.enlarge(self.forceBrBreak || !self.enlargeBr ? KER.ENLARGE_LIST_ITEM_CONTENTS : KER.ENLARGE_BLOCK_CONTENTS); var walker = new Walker(range), ignoreBookmarkTextEvaluator = Walker.bookmark(TRUE, TRUE); // Avoid anchor inside bookmark inner text. walker.evaluator = ignoreBookmarkTextEvaluator; self._.nextNode = walker.next(); // TODO: It's better to have walker.reset() used here. walker = new Walker(range); walker.evaluator = ignoreBookmarkTextEvaluator; var lastNode = walker.previous(); self._.lastNode = lastNode._4e_nextSourceNode(TRUE); // We may have an empty text node at the end of block due to [3770]. // If that node is the lastNode, it would cause our logic to leak to the // next block.(#3887) if (self._.lastNode && self._.lastNode[0].nodeType == Dom.NodeType.TEXT_NODE && !S.trim(self._.lastNode[0].nodeValue) && self._.lastNode.parent()._4e_isBlockBoundary()) { var testRange = new KERange(range.document); testRange.moveToPosition(self._.lastNode, KER.POSITION_AFTER_END); if (testRange.checkEndOfBlock()) { var path = new ElementPath(testRange.endContainer); var lastBlock = path.block || path.blockLimit; self._.lastNode = lastBlock._4e_nextSourceNode(TRUE); } } // Probably the document end is reached, we need a marker node. if (!self._.lastNode) { self._.lastNode = self._.docEndMarker = new Node(range.document.createTextNode('')); Dom.insertAfter(self._.lastNode[0], lastNode[0]); } // Let's reuse self variable. range = NULL; } var currentNode = self._.nextNode; lastNode = self._.lastNode; self._.nextNode = NULL; while (currentNode) { // closeRange indicates that a paragraph boundary has been found, // so the range can be closed. var closeRange = FALSE; // includeNode indicates that the current node is good to be part // of the range. By default, any non-element node is ok for it. var includeNode = ( currentNode[0].nodeType != Dom.NodeType.ELEMENT_NODE ), continueFromSibling = FALSE; // If it is an element node, let's check if it can be part of the // range. if (!includeNode) { var nodeName = currentNode.nodeName(); if (currentNode._4e_isBlockBoundary(self.forceBrBreak && { br: 1 })) { // <br> boundaries must be part of the range. It will // happen only if ForceBrBreak. if (nodeName == 'br') includeNode = TRUE; else if (!range && !currentNode[0].childNodes.length && nodeName != 'hr') { // If we have found an empty block, and haven't started // the range yet, it means we must return self block. block = currentNode; isLast = currentNode.equals(lastNode); break; } // The range must finish right before the boundary, // including possibly skipped empty spaces. (#1603) if (range) { range.setEndAt(currentNode, KER.POSITION_BEFORE_START); // The found boundary must be set as the next one at self // point. (#1717) if (nodeName != 'br') self._.nextNode = currentNode; } closeRange = TRUE; } else { // If we have child nodes, let's check them. if (currentNode[0].firstChild) { // If we don't have a range yet, let's start it. if (!range) { range = new KERange(self.range.document); range.setStartAt(currentNode, KER.POSITION_BEFORE_START); } currentNode = new Node(currentNode[0].firstChild); continue; } includeNode = TRUE; } } else if (currentNode[0].nodeType == Dom.NodeType.TEXT_NODE) { // Ignore normal whitespaces (i.e. not including or // other unicode whitespaces) before/after a block node. if (beginWhitespaceRegex.test(currentNode[0].nodeValue)) includeNode = FALSE; } // The current node is good to be part of the range and we are // starting a new range, initialize it first. if (includeNode && !range) { range = new KERange(self.range.document); range.setStartAt(currentNode, KER.POSITION_BEFORE_START); } // The last node has been found. isLast = ( !closeRange || includeNode ) && currentNode.equals(lastNode); // If we are in an element boundary, let's check if it is time // to close the range, otherwise we include the parent within it. if (range && !closeRange) { while (!currentNode[0].nextSibling && !isLast) { var parentNode = currentNode.parent(); if (parentNode._4e_isBlockBoundary(self.forceBrBreak && { br: 1 })) { closeRange = TRUE; isLast = isLast || parentNode.equals(lastNode); break; } currentNode = parentNode; includeNode = TRUE; isLast = currentNode.equals(lastNode); continueFromSibling = TRUE; } } // Now finally include the node. if (includeNode) range.setEndAt(currentNode, KER.POSITION_AFTER_END); currentNode = currentNode._4e_nextSourceNode(continueFromSibling, NULL, lastNode); isLast = !currentNode; // We have found a block boundary. Let's close the range and move out of the // loop. if (isLast || ( closeRange && range )) break; } // Now, based on the processed range, look for (or create) the block to be returned. if (!block) { // If no range has been found, self is the end. if (!range) { self._.docEndMarker && self._.docEndMarker._4e_remove(); self._.nextNode = NULL; return NULL; } var startPath = new ElementPath(range.startContainer); var startBlockLimit = startPath.blockLimit, checkLimits = { div: 1, th: 1, td: 1 }; block = startPath.block; if ((!block || !block[0]) && !self.enforceRealBlocks && checkLimits[ startBlockLimit.nodeName() ] && range.checkStartOfBlock() && range.checkEndOfBlock()) block = startBlockLimit; else if (!block || ( self.enforceRealBlocks && block.nodeName() == 'li' )) { // Create the fixed block. block = new Node(self.range.document.createElement(blockTag || 'p')); // Move the contents of the temporary range to the fixed block. block[0].appendChild(range.extractContents()); block._4e_trim(); // Insert the fixed block into the Dom. range.insertNode(block); removePreviousBr = removeLastBr = TRUE; } else if (block.nodeName() != 'li') { // If the range doesn't includes the entire contents of the // block, we must split it, isolating the range in a dedicated // block. if (!range.checkStartOfBlock() || !range.checkEndOfBlock()) { // The resulting block will be a clone of the current one. block = block.clone(FALSE); // Extract the range contents, moving it to the new block. block[0].appendChild(range.extractContents()); block._4e_trim(); // Split the block. At self point, the range will be in the // right position for our intents. var splitInfo = range.splitBlock(); removePreviousBr = !splitInfo.wasStartOfBlock; removeLastBr = !splitInfo.wasEndOfBlock; // Insert the new block into the Dom. range.insertNode(block); } } else if (!isLast) { // LIs are returned as is, with all their children (due to the // nested lists). But, the next node is the node right after // the current range, which could be an <li> child (nested // lists) or the next sibling <li>. self._.nextNode = ( block.equals(lastNode) ? NULL : range.getBoundaryNodes().endNode._4e_nextSourceNode(TRUE, NULL, lastNode) ); } } if (removePreviousBr) { var previousSibling = new Node(block[0].previousSibling); if (previousSibling[0] && previousSibling[0].nodeType == Dom.NodeType.ELEMENT_NODE) { if (previousSibling.nodeName() == 'br') previousSibling._4e_remove(); else if (previousSibling[0].lastChild && Dom.nodeName(previousSibling[0].lastChild) == 'br') Dom._4e_remove(previousSibling[0].lastChild); } } if (removeLastBr) { // Ignore bookmark nodes.(#3783) var bookmarkGuard = Walker.bookmark(FALSE, TRUE); var lastChild = new Node(block[0].lastChild); if (lastChild[0] && lastChild[0].nodeType == Dom.NodeType.ELEMENT_NODE && lastChild.nodeName() == 'br') { // Take care not to remove the block expanding <br> in non-IE browsers. if (UA['ie'] || lastChild.prev(bookmarkGuard, 1) || lastChild.next(bookmarkGuard, 1)) lastChild.remove(); } } // Get a reference for the next element. self is important because the // above block can be removed or changed, so we can rely on it for the // next interation. if (!self._.nextNode) { self._.nextNode = ( isLast || block.equals(lastNode) ) ? NULL : block._4e_nextSourceNode(TRUE, NULL, lastNode); } return block; } }); /** * get iterator for range * @member KISSY.Editor.Range * @returns {KISSY.Editor.Iterator} */ KERange.prototype.createIterator = function () { return new Iterator(this); }; return Iterator; }, { requires: ['./base', './range', './elementPath', './walker', 'node'] });