1 /**
  2  * modified from ckeditor ,dom iterator implementation using walker and nextSourceNode
  3  * @author yiminghe@gmail.com
  4  */
  5 /*
  6  Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
  7  For licensing, see LICENSE.html or http://ckeditor.com/license
  8  */
  9 KISSY.add("editor/core/domIterator", function (S) {
 10     var TRUE = true,
 11         FALSE = false,
 12         NULL = null,
 13         Editor = S.Editor,
 14         UA = S.UA,
 15         Walker = Editor.Walker,
 16         KERange = Editor.Range,
 17         KER = Editor.RANGE,
 18         ElementPath = Editor.ElementPath,
 19         Node = S.Node,
 20         DOM = S.DOM;
 21 
 22     /**
 23      * @constructor
 24      * @param range {KISSY.Editor.Range}
 25      */
 26     function Iterator(range) {
 27         if (arguments.length < 1)
 28             return;
 29         var self = this;
 30         self.range = range;
 31         self.forceBrBreak = FALSE;
 32 
 33         // Whether include <br>s into the enlarged range.(#3730).
 34         self.enlargeBr = TRUE;
 35         self.enforceRealBlocks = FALSE;
 36 
 37         self._ || ( self._ = {} );
 38     }
 39 
 40     var beginWhitespaceRegex = /^[\r\n\t ]*$/;///^[\r\n\t ]+$/,//+:*??不匹配空串
 41 
 42     S.augment(Iterator, {
 43         //奇怪点:
 44         //<ul>
 45         // <li>
 46         // x
 47         // </li>
 48         // <li>
 49         // y
 50         // </li>
 51         // </ul>
 52         //会返回两次 li,li,而不是一次 ul ,
 53         // 可能只是返回包含文字的段落概念?
 54         getNextParagraph:function (blockTag) {
 55             // The block element to be returned.
 56             var block, self = this;
 57 
 58             // The range object used to identify the paragraph contents.
 59             var range;
 60 
 61             // Indicats that the current element in the loop is the last one.
 62             var isLast;
 63 
 64             // Instructs to cleanup remaining BRs.
 65             var removePreviousBr, removeLastBr;
 66 
 67             // self is the first iteration. Let's initialize it.
 68             if (!self._.lastNode) {
 69                 range = self.range.clone();
 70 
 71                 // 2010-09-30 shrink
 72                 // 3.4.2 新增,
 73                 // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435).
 74                 range.shrink(KER.SHRINK_ELEMENT, TRUE);
 75 
 76                 range.enlarge(self.forceBrBreak || !self.enlargeBr ?
 77                     KER.ENLARGE_LIST_ITEM_CONTENTS : KER.ENLARGE_BLOCK_CONTENTS);
 78 
 79                 var walker = new Walker(range),
 80                     ignoreBookmarkTextEvaluator = Walker.bookmark(TRUE, TRUE);
 81                 // Avoid anchor inside bookmark inner text.
 82                 walker.evaluator = ignoreBookmarkTextEvaluator;
 83                 self._.nextNode = walker.next();
 84                 // TODO: It's better to have walker.reset() used here.
 85                 walker = new Walker(range);
 86                 walker.evaluator = ignoreBookmarkTextEvaluator;
 87                 var lastNode = walker.previous();
 88                 self._.lastNode = lastNode._4e_nextSourceNode(TRUE);
 89 
 90                 // We may have an empty text node at the end of block due to [3770].
 91                 // If that node is the lastNode, it would cause our logic to leak to the
 92                 // next block.(#3887)
 93                 if (self._.lastNode &&
 94                     self._.lastNode[0].nodeType == DOM.TEXT_NODE &&
 95                     !S.trim(self._.lastNode[0].nodeValue) &&
 96                     self._.lastNode.parent()._4e_isBlockBoundary()) {
 97                     var testRange = new KERange(range.document);
 98                     testRange.moveToPosition(self._.lastNode, KER.POSITION_AFTER_END);
 99                     if (testRange.checkEndOfBlock()) {
100                         var path = new ElementPath(testRange.endContainer);
101                         var lastBlock = path.block || path.blockLimit;
102                         self._.lastNode = lastBlock._4e_nextSourceNode(TRUE);
103                     }
104                 }
105 
106                 // Probably the document end is reached, we need a marker node.
107                 if (!self._.lastNode) {
108                     self._.lastNode = self._.docEndMarker = new Node(range.document.createTextNode(''));
109                     DOM.insertAfter(self._.lastNode[0], lastNode[0]);
110                 }
111 
112                 // Let's reuse self variable.
113                 range = NULL;
114             }
115 
116             var currentNode = self._.nextNode;
117             lastNode = self._.lastNode;
118 
119             self._.nextNode = NULL;
120             while (currentNode) {
121                 // closeRange indicates that a paragraph boundary has been found,
122                 // so the range can be closed.
123                 var closeRange = FALSE;
124 
125                 // includeNode indicates that the current node is good to be part
126                 // of the range. By default, any non-element node is ok for it.
127                 var includeNode = ( currentNode[0].nodeType != DOM.ELEMENT_NODE ),
128                     continueFromSibling = FALSE;
129 
130                 // If it is an element node, let's check if it can be part of the
131                 // range.
132                 if (!includeNode) {
133                     var nodeName = currentNode.nodeName();
134 
135                     if (currentNode._4e_isBlockBoundary(self.forceBrBreak && { br:1 })) {
136                         // <br> boundaries must be part of the range. It will
137                         // happen only if ForceBrBreak.
138                         if (nodeName == 'br')
139                             includeNode = TRUE;
140                         else if (!range && !currentNode[0].childNodes.length && nodeName != 'hr') {
141                             // If we have found an empty block, and haven't started
142                             // the range yet, it means we must return self block.
143                             block = currentNode;
144                             isLast = currentNode.equals(lastNode);
145                             break;
146                         }
147 
148                         // The range must finish right before the boundary,
149                         // including possibly skipped empty spaces. (#1603)
150                         if (range) {
151                             range.setEndAt(currentNode, KER.POSITION_BEFORE_START);
152 
153                             // The found boundary must be set as the next one at self
154                             // point. (#1717)
155                             if (nodeName != 'br')
156                                 self._.nextNode = currentNode;
157                         }
158 
159                         closeRange = TRUE;
160                     } else {
161                         // If we have child nodes, let's check them.
162                         if (currentNode[0].firstChild) {
163                             // If we don't have a range yet, let's start it.
164                             if (!range) {
165                                 range = new KERange(self.range.document);
166                                 range.setStartAt(currentNode, KER.POSITION_BEFORE_START);
167                             }
168 
169                             currentNode = new Node(currentNode[0].firstChild);
170                             continue;
171                         }
172                         includeNode = TRUE;
173                     }
174                 }
175                 else if (currentNode[0].nodeType == DOM.TEXT_NODE) {
176                     // Ignore normal whitespaces (i.e. not including   or
177                     // other unicode whitespaces) before/after a block node.
178                     if (beginWhitespaceRegex.test(currentNode[0].nodeValue))
179                         includeNode = FALSE;
180                 }
181 
182                 // The current node is good to be part of the range and we are
183                 // starting a new range, initialize it first.
184                 if (includeNode && !range) {
185                     range = new KERange(self.range.document);
186                     range.setStartAt(currentNode, KER.POSITION_BEFORE_START);
187                 }
188 
189                 // The last node has been found.
190                 isLast = ( !closeRange || includeNode ) && currentNode.equals(lastNode);
191 
192                 // If we are in an element boundary, let's check if it is time
193                 // to close the range, otherwise we include the parent within it.
194                 if (range && !closeRange) {
195                     while (!currentNode[0].nextSibling && !isLast) {
196                         var parentNode = currentNode.parent();
197 
198                         if (parentNode._4e_isBlockBoundary(self.forceBrBreak && { br:1 })) {
199                             closeRange = TRUE;
200                             isLast = isLast || parentNode.equals(lastNode);
201                             break;
202                         }
203 
204                         currentNode = parentNode;
205                         includeNode = TRUE;
206                         isLast = currentNode.equals(lastNode);
207                         continueFromSibling = TRUE;
208                     }
209                 }
210 
211                 // Now finally include the node.
212                 if (includeNode)
213                     range.setEndAt(currentNode, KER.POSITION_AFTER_END);
214 
215                 currentNode = currentNode._4e_nextSourceNode(continueFromSibling, NULL, lastNode);
216                 isLast = !currentNode;
217 
218                 // We have found a block boundary. Let's close the range and move out of the
219                 // loop.
220                 if (isLast || ( closeRange && range ))
221                     break;
222             }
223 
224             // Now, based on the processed range, look for (or create) the block to be returned.
225             if (!block) {
226                 // If no range has been found, self is the end.
227                 if (!range) {
228                     self._.docEndMarker && self._.docEndMarker._4e_remove();
229                     self._.nextNode = NULL;
230                     return NULL;
231                 }
232 
233                 var startPath = new ElementPath(range.startContainer);
234                 var startBlockLimit = startPath.blockLimit,
235                     checkLimits = { div:1, th:1, td:1 };
236                 block = startPath.block;
237 
238                 if ((!block || !block[0])
239                     && !self.enforceRealBlocks
240                     && checkLimits[ startBlockLimit.nodeName() ]
241                     && range.checkStartOfBlock()
242                     && range.checkEndOfBlock())
243                     block = startBlockLimit;
244                 else if (!block || ( self.enforceRealBlocks && block.nodeName() == 'li' )) {
245                     // Create the fixed block.
246                     block = new Node(self.range.document.createElement(blockTag || 'p'));
247                     // Move the contents of the temporary range to the fixed block.
248                     block[0].appendChild(range.extractContents());
249                     block._4e_trim();
250                     // Insert the fixed block into the DOM.
251                     range.insertNode(block);
252                     removePreviousBr = removeLastBr = TRUE;
253                 }
254                 else if (block.nodeName() != 'li') {
255                     // If the range doesn't includes the entire contents of the
256                     // block, we must split it, isolating the range in a dedicated
257                     // block.
258                     if (!range.checkStartOfBlock() || !range.checkEndOfBlock()) {
259                         // The resulting block will be a clone of the current one.
260                         block = block.clone(FALSE);
261 
262                         // Extract the range contents, moving it to the new block.
263                         block[0].appendChild(range.extractContents());
264                         block._4e_trim();
265 
266                         // Split the block. At self point, the range will be in the
267                         // right position for our intents.
268                         var splitInfo = range.splitBlock();
269 
270                         removePreviousBr = !splitInfo.wasStartOfBlock;
271                         removeLastBr = !splitInfo.wasEndOfBlock;
272 
273                         // Insert the new block into the DOM.
274                         range.insertNode(block);
275                     }
276                 }
277                 else if (!isLast) {
278                     // LIs are returned as is, with all their children (due to the
279                     // nested lists). But, the next node is the node right after
280                     // the current range, which could be an <li> child (nested
281                     // lists) or the next sibling <li>.
282 
283                     self._.nextNode = ( block.equals(lastNode) ? NULL :
284                         range.getBoundaryNodes().endNode._4e_nextSourceNode(TRUE, NULL, lastNode) );
285                 }
286             }
287 
288             if (removePreviousBr) {
289                 var previousSibling = new Node(block[0].previousSibling);
290                 if (previousSibling[0] && previousSibling[0].nodeType == DOM.ELEMENT_NODE) {
291                     if (previousSibling.nodeName() == 'br')
292                         previousSibling._4e_remove();
293                     else if (previousSibling[0].lastChild && DOM.nodeName(previousSibling[0].lastChild) == 'br')
294                         DOM._4e_remove(previousSibling[0].lastChild);
295                 }
296             }
297 
298             if (removeLastBr) {
299                 // Ignore bookmark nodes.(#3783)
300                 var bookmarkGuard = Walker.bookmark(FALSE, TRUE);
301 
302                 var lastChild = new Node(block[0].lastChild);
303                 if (lastChild[0] && lastChild[0].nodeType == DOM.ELEMENT_NODE && lastChild.nodeName() == 'br') {
304                     // Take care not to remove the block expanding <br> in non-IE browsers.
305                     if (UA['ie']
306                         || lastChild.prev(bookmarkGuard, 1)
307                         || lastChild.next(bookmarkGuard, 1))
308                         lastChild.remove();
309                 }
310             }
311 
312             // Get a reference for the next element. self is important because the
313             // above block can be removed or changed, so we can rely on it for the
314             // next interation.
315             if (!self._.nextNode) {
316                 self._.nextNode = ( isLast || block.equals(lastNode) ) ? NULL :
317                     block._4e_nextSourceNode(TRUE, NULL, lastNode);
318             }
319 
320             return block;
321         }
322     });
323 
324     KERange.prototype.createIterator = function () {
325         return new Iterator(this);
326     };
327 
328     return Iterator;
329 }, {
330     requires:['./base', './range', './elementPath', './walker']
331 });
332