1/*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
4 *
5 * == BEGIN LICENSE ==
6 *
7 * Licensed under the terms of any of the following licenses at your
8 * choice:
9 *
10 *  - GNU General Public License Version 2 or later (the "GPL")
11 *    http://www.gnu.org/licenses/gpl.html
12 *
13 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14 *    http://www.gnu.org/licenses/lgpl.html
15 *
16 *  - Mozilla Public License Version 1.1 or later (the "MPL")
17 *    http://www.mozilla.org/MPL/MPL-1.1.html
18 *
19 * == END LICENSE ==
20 *
21 * This class can be used to interate through nodes inside a range.
22 *
23 * During interation, the provided range can become invalid, due to document
24 * mutations, so CreateBookmark() used to restore it after processing, if
25 * needed.
26 */
27
28var FCKDomRangeIterator = function( range )
29{
30	/**
31	 * The FCKDomRange object that marks the interation boundaries.
32	 */
33	this.Range = range ;
34
35	/**
36	 * Indicates that <br> elements must be used as paragraph boundaries.
37	 */
38	this.ForceBrBreak = false ;
39
40	/**
41	 * Guarantees that the iterator will always return "real" block elements.
42	 * If "false", elements like <li>, <th> and <td> are returned. If "true", a
43	 * dedicated block element block element will be created inside those
44	 * elements to hold the selected content.
45	 */
46	this.EnforceRealBlocks = false ;
47}
48
49FCKDomRangeIterator.CreateFromSelection = function( targetWindow )
50{
51	var range = new FCKDomRange( targetWindow ) ;
52	range.MoveToSelection() ;
53	return new FCKDomRangeIterator( range ) ;
54}
55
56FCKDomRangeIterator.prototype =
57{
58	/**
59	 * Get the next paragraph element. It automatically breaks the document
60	 * when necessary to generate block elements for the paragraphs.
61	 */
62	GetNextParagraph : function()
63	{
64		// The block element to be returned.
65		var block ;
66
67		// The range object used to identify the paragraph contents.
68		var range ;
69
70		// Indicated that the current element in the loop is the last one.
71		var isLast ;
72
73		// Instructs to cleanup remaining BRs.
74		var removePreviousBr ;
75		var removeLastBr ;
76
77		var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;
78
79		// This is the first iteration. Let's initialize it.
80		if ( !this._LastNode )
81		{
82			var range = this.Range.Clone() ;
83			range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;
84
85			this._NextNode = range.GetTouchedStartNode() ;
86			this._LastNode = range.GetTouchedEndNode() ;
87
88			// Let's reuse this variable.
89			range = null ;
90		}
91
92		var currentNode = this._NextNode ;
93		var lastNode = this._LastNode ;
94
95		while ( currentNode )
96		{
97			// closeRange indicates that a paragraph boundary has been found,
98			// so the range can be closed.
99			var closeRange = false ;
100
101			// includeNode indicates that the current node is good to be part
102			// of the range. By default, any non-element node is ok for it.
103			var includeNode = ( currentNode.nodeType != 1 ) ;
104
105			var continueFromSibling = false ;
106
107			// If it is an element node, let's check if it can be part of the
108			// range.
109			if ( !includeNode )
110			{
111				var nodeName = currentNode.nodeName.toLowerCase() ;
112
113				if ( boundarySet[ nodeName ] )
114				{
115					// <br> boundaries must be part of the range. It will
116					// happen only if ForceBrBreak.
117					if ( nodeName == 'br' )
118						includeNode = true ;
119					else if ( !range && currentNode.childNodes.length == 0 && nodeName != 'hr' )
120					{
121						// If we have found an empty block, and haven't started
122						// the range yet, it means we must return this block.
123						block = currentNode ;
124						isLast = currentNode == lastNode ;
125						break ;
126					}
127
128					closeRange = true ;
129				}
130				else
131				{
132					// If we have child nodes, let's check them.
133					if ( currentNode.firstChild )
134					{
135						// If we don't have a range yet, let's start it.
136						if ( !range )
137						{
138							range = new FCKDomRange( this.Range.Window ) ;
139							range.SetStart( currentNode, 3, true ) ;
140						}
141
142						currentNode = currentNode.firstChild ;
143						continue ;
144					}
145					includeNode = true ;
146				}
147			}
148			else if ( currentNode.nodeType == 3 )
149			{
150				// Ignore normal whitespaces (i.e. not including &nbsp; or
151				// other unicode whitespaces) before/after a block node.
152				if ( /^[\r\n\t ]+$/.test( currentNode.nodeValue ) )
153					includeNode = false ;
154			}
155
156			// The current node is good to be part of the range and we are
157			// starting a new range, initialize it first.
158			if ( includeNode && !range )
159			{
160				range = new FCKDomRange( this.Range.Window ) ;
161				range.SetStart( currentNode, 3, true ) ;
162			}
163
164			// The last node has been found.
165			isLast = ( ( !closeRange || includeNode ) && currentNode == lastNode ) ;
166//			isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;
167
168			// If we are in an element boundary, let's check if it is time
169			// to close the range, otherwise we include the parent within it.
170			if ( range && !closeRange )
171			{
172				while ( !currentNode.nextSibling && !isLast )
173				{
174					var parentNode = currentNode.parentNode ;
175
176					if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )
177					{
178						closeRange = true ;
179						isLast = isLast || ( parentNode == lastNode ) ;
180						break ;
181					}
182
183					currentNode = parentNode ;
184					isLast = ( currentNode == lastNode ) ;
185					continueFromSibling = true ;
186				}
187			}
188
189			// Now finally include the node.
190			if ( includeNode )
191				range.SetEnd( currentNode, 4, true ) ;
192
193			// We have found a block boundary. Let's close the range and move out of the
194			// loop.
195			if ( ( closeRange || isLast ) && range )
196			{
197				range._UpdateElementInfo() ;
198
199				if ( range.StartNode == range.EndNode
200						&& range.StartNode.parentNode == range.StartBlockLimit
201						&& range.StartNode.getAttribute && range.StartNode.getAttribute( '_fck_bookmark' ) )
202					range = null ;
203				else
204					break ;
205			}
206
207			if ( isLast )
208				break ;
209
210			currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;
211		}
212
213		// Now, based on the processed range, look for (or create) the block to be returned.
214		if ( !block )
215		{
216			// If no range has been found, this is the end.
217			if ( !range )
218			{
219				this._NextNode = null ;
220				return null ;
221			}
222
223			block = range.StartBlock ;
224
225			if ( !block
226				&& !this.EnforceRealBlocks
227				&& range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )
228				&& range.CheckStartOfBlock()
229				&& range.CheckEndOfBlock() )
230			{
231				block = range.StartBlockLimit ;
232			}
233			else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )
234			{
235				// Create the fixed block.
236				block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;
237
238				// Move the contents of the temporary range to the fixed block.
239				range.ExtractContents().AppendTo( block ) ;
240				FCKDomTools.TrimNode( block ) ;
241
242				// Insert the fixed block into the DOM.
243				range.InsertNode( block ) ;
244
245				removePreviousBr = true ;
246				removeLastBr = true ;
247			}
248			else if ( block.nodeName.toLowerCase() != 'li' )
249			{
250				// If the range doesn't includes the entire contents of the
251				// block, we must split it, isolating the range in a dedicated
252				// block.
253				if ( !range.CheckStartOfBlock() || !range.CheckEndOfBlock() )
254				{
255					// The resulting block will be a clone of the current one.
256					block = block.cloneNode( false ) ;
257
258					// Extract the range contents, moving it to the new block.
259					range.ExtractContents().AppendTo( block ) ;
260					FCKDomTools.TrimNode( block ) ;
261
262					// Split the block. At this point, the range will be in the
263					// right position for our intents.
264					var splitInfo = range.SplitBlock() ;
265
266					removePreviousBr = !splitInfo.WasStartOfBlock ;
267					removeLastBr = !splitInfo.WasEndOfBlock ;
268
269					// Insert the new block into the DOM.
270					range.InsertNode( block ) ;
271				}
272			}
273			else if ( !isLast )
274			{
275				// LIs are returned as is, with all their children (due to the
276				// nested lists). But, the next node is the node right after
277				// the current range, which could be an <li> child (nested
278				// lists) or the next sibling <li>.
279
280				this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;
281				return block ;
282			}
283		}
284
285		if ( removePreviousBr )
286		{
287			var previousSibling = block.previousSibling ;
288			if ( previousSibling && previousSibling.nodeType == 1 && previousSibling.nodeName.toLowerCase() == 'br' )
289				previousSibling.parentNode.removeChild( previousSibling ) ;
290		}
291
292		if ( removeLastBr )
293		{
294			var lastChild = block.lastChild ;
295			if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )
296				block.removeChild( lastChild ) ;
297		}
298
299		// Get a reference for the next element. This is important because the
300		// above block can be removed or changed, so we can rely on it for the
301		// next interation.
302		this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ;
303
304		return block ;
305	}
306} ;
307