2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
\r
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
\r
5 * == BEGIN LICENSE ==
\r
7 * Licensed under the terms of any of the following licenses at your
\r
10 * - GNU General Public License Version 2 or later (the "GPL")
\r
11 * http://www.gnu.org/licenses/gpl.html
\r
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
\r
14 * http://www.gnu.org/licenses/lgpl.html
\r
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
\r
17 * http://www.mozilla.org/MPL/MPL-1.1.html
\r
21 * This class can be used to interate through nodes inside a range.
\r
23 * During interation, the provided range can become invalid, due to document
\r
24 * mutations, so CreateBookmark() used to restore it after processing, if
\r
28 var FCKDomRangeIterator = function( range )
\r
31 * The FCKDomRange object that marks the interation boundaries.
\r
33 this.Range = range ;
\r
36 * Indicates that <br> elements must be used as paragraph boundaries.
\r
38 this.ForceBrBreak = false ;
\r
41 * Guarantees that the iterator will always return "real" block elements.
\r
42 * If "false", elements like <li>, <th> and <td> are returned. If "true", a
\r
43 * dedicated block element block element will be created inside those
\r
44 * elements to hold the selected content.
\r
46 this.EnforceRealBlocks = false ;
\r
49 FCKDomRangeIterator.CreateFromSelection = function( targetWindow )
\r
51 var range = new FCKDomRange( targetWindow ) ;
\r
52 range.MoveToSelection() ;
\r
53 return new FCKDomRangeIterator( range ) ;
\r
56 FCKDomRangeIterator.prototype =
\r
59 * Get the next paragraph element. It automatically breaks the document
\r
60 * when necessary to generate block elements for the paragraphs.
\r
62 GetNextParagraph : function()
\r
64 // The block element to be returned.
\r
67 // The range object used to identify the paragraph contents.
\r
70 // Indicated that the current element in the loop is the last one.
\r
73 // Instructs to cleanup remaining BRs.
\r
74 var removePreviousBr ;
\r
77 var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;
\r
79 // This is the first iteration. Let's initialize it.
\r
80 if ( !this._LastNode )
\r
82 var range = this.Range.Clone() ;
\r
83 range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;
\r
85 this._NextNode = range.GetTouchedStartNode() ;
\r
86 this._LastNode = range.GetTouchedEndNode() ;
\r
88 // Let's reuse this variable.
\r
92 var currentNode = this._NextNode ;
\r
93 var lastNode = this._LastNode ;
\r
95 this._NextNode = null ;
\r
97 while ( currentNode )
\r
99 // closeRange indicates that a paragraph boundary has been found,
\r
100 // so the range can be closed.
\r
101 var closeRange = false ;
\r
103 // includeNode indicates that the current node is good to be part
\r
104 // of the range. By default, any non-element node is ok for it.
\r
105 var includeNode = ( currentNode.nodeType != 1 ) ;
\r
107 var continueFromSibling = false ;
\r
109 // If it is an element node, let's check if it can be part of the
\r
111 if ( !includeNode )
\r
113 var nodeName = currentNode.nodeName.toLowerCase() ;
\r
115 if ( boundarySet[ nodeName ] && ( !FCKBrowserInfo.IsIE || currentNode.scopeName == 'HTML' ) )
\r
117 // <br> boundaries must be part of the range. It will
\r
118 // happen only if ForceBrBreak.
\r
119 if ( nodeName == 'br' )
\r
120 includeNode = true ;
\r
121 else if ( !range && currentNode.childNodes.length == 0 && nodeName != 'hr' )
\r
123 // If we have found an empty block, and haven't started
\r
124 // the range yet, it means we must return this block.
\r
125 block = currentNode ;
\r
126 isLast = currentNode == lastNode ;
\r
130 // The range must finish right before the boundary,
\r
131 // including possibly skipped empty spaces. (#1603)
\r
134 range.SetEnd( currentNode, 3, true ) ;
\r
136 // The found boundary must be set as the next one at this
\r
138 if ( nodeName != 'br' )
\r
139 this._NextNode = FCKDomTools.GetNextSourceNode( currentNode, true, null, lastNode ) || currentNode ;
\r
142 closeRange = true ;
\r
146 // If we have child nodes, let's check them.
\r
147 if ( currentNode.firstChild )
\r
149 // If we don't have a range yet, let's start it.
\r
152 range = new FCKDomRange( this.Range.Window ) ;
\r
153 range.SetStart( currentNode, 3, true ) ;
\r
156 currentNode = currentNode.firstChild ;
\r
159 includeNode = true ;
\r
162 else if ( currentNode.nodeType == 3 )
\r
164 // Ignore normal whitespaces (i.e. not including or
\r
165 // other unicode whitespaces) before/after a block node.
\r
166 if ( /^[\r\n\t ]+$/.test( currentNode.nodeValue ) )
\r
167 includeNode = false ;
\r
170 // The current node is good to be part of the range and we are
\r
171 // starting a new range, initialize it first.
\r
172 if ( includeNode && !range )
\r
174 range = new FCKDomRange( this.Range.Window ) ;
\r
175 range.SetStart( currentNode, 3, true ) ;
\r
178 // The last node has been found.
\r
179 isLast = ( ( !closeRange || includeNode ) && currentNode == lastNode ) ;
\r
180 // isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;
\r
182 // If we are in an element boundary, let's check if it is time
\r
183 // to close the range, otherwise we include the parent within it.
\r
184 if ( range && !closeRange )
\r
186 while ( !currentNode.nextSibling && !isLast )
\r
188 var parentNode = currentNode.parentNode ;
\r
190 if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )
\r
192 closeRange = true ;
\r
193 isLast = isLast || ( parentNode == lastNode ) ;
\r
197 currentNode = parentNode ;
\r
198 includeNode = true ;
\r
199 isLast = ( currentNode == lastNode ) ;
\r
200 continueFromSibling = true ;
\r
204 // Now finally include the node.
\r
206 range.SetEnd( currentNode, 4, true ) ;
\r
208 // We have found a block boundary. Let's close the range and move out of the
\r
210 if ( ( closeRange || isLast ) && range )
\r
212 range._UpdateElementInfo() ;
\r
214 if ( range.StartNode == range.EndNode
\r
215 && range.StartNode.parentNode == range.StartBlockLimit
\r
216 && range.StartNode.getAttribute && range.StartNode.getAttribute( '_fck_bookmark' ) )
\r
225 currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;
\r
228 // Now, based on the processed range, look for (or create) the block to be returned.
\r
231 // If no range has been found, this is the end.
\r
234 this._NextNode = null ;
\r
238 block = range.StartBlock ;
\r
241 && !this.EnforceRealBlocks
\r
242 && range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )
\r
243 && range.CheckStartOfBlock()
\r
244 && range.CheckEndOfBlock() )
\r
246 block = range.StartBlockLimit ;
\r
248 else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )
\r
250 // Create the fixed block.
\r
251 block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;
\r
253 // Move the contents of the temporary range to the fixed block.
\r
254 range.ExtractContents().AppendTo( block ) ;
\r
255 FCKDomTools.TrimNode( block ) ;
\r
257 // Insert the fixed block into the DOM.
\r
258 range.InsertNode( block ) ;
\r
260 removePreviousBr = true ;
\r
261 removeLastBr = true ;
\r
263 else if ( block.nodeName.toLowerCase() != 'li' )
\r
265 // If the range doesn't includes the entire contents of the
\r
266 // block, we must split it, isolating the range in a dedicated
\r
268 if ( !range.CheckStartOfBlock() || !range.CheckEndOfBlock() )
\r
270 // The resulting block will be a clone of the current one.
\r
271 block = block.cloneNode( false ) ;
\r
273 // Extract the range contents, moving it to the new block.
\r
274 range.ExtractContents().AppendTo( block ) ;
\r
275 FCKDomTools.TrimNode( block ) ;
\r
277 // Split the block. At this point, the range will be in the
\r
278 // right position for our intents.
\r
279 var splitInfo = range.SplitBlock() ;
\r
281 removePreviousBr = !splitInfo.WasStartOfBlock ;
\r
282 removeLastBr = !splitInfo.WasEndOfBlock ;
\r
284 // Insert the new block into the DOM.
\r
285 range.InsertNode( block ) ;
\r
288 else if ( !isLast )
\r
290 // LIs are returned as is, with all their children (due to the
\r
291 // nested lists). But, the next node is the node right after
\r
292 // the current range, which could be an <li> child (nested
\r
293 // lists) or the next sibling <li>.
\r
295 this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;
\r
300 if ( removePreviousBr )
\r
302 var previousSibling = block.previousSibling ;
\r
303 if ( previousSibling && previousSibling.nodeType == 1 )
\r
305 if ( previousSibling.nodeName.toLowerCase() == 'br' )
\r
306 previousSibling.parentNode.removeChild( previousSibling ) ;
\r
307 else if ( previousSibling.lastChild && previousSibling.lastChild.nodeName.IEquals( 'br' ) )
\r
308 previousSibling.removeChild( previousSibling.lastChild ) ;
\r
312 if ( removeLastBr )
\r
314 var lastChild = block.lastChild ;
\r
315 if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )
\r
316 block.removeChild( lastChild ) ;
\r
319 // Get a reference for the next element. This is important because the
\r
320 // above block can be removed or changed, so we can rely on it for the
\r
321 // next interation.
\r
322 if ( !this._NextNode )
\r
323 this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ;
\r