﻿/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2010 Frederico Caldeira Knabben
 *
 * == BEGIN LICENSE ==
 *
 * Licensed under the terms of any of the following licenses at your
 * choice:
 *
 *  - GNU General Public License Version 2 or later (the "GPL")
 *    http://www.gnu.org/licenses/gpl.html
 *
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 *    http://www.gnu.org/licenses/lgpl.html
 *
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 *
 * == END LICENSE ==
 *
 * This class can be used to interate through nodes inside a range.
 *
 * During interation, the provided range can become invalid, due to document
 * mutations, so CreateBookmark() used to restore it after processing, if
 * needed.
 */

var FCKDomRangeIterator = function( range )
{
	/**
	 * The FCKDomRange object that marks the interation boundaries.
	 */
	this.Range = range ;

	/**
	 * Indicates that <br> elements must be used as paragraph boundaries.
	 */
	this.ForceBrBreak = false ;

	/**
	 * Guarantees that the iterator will always return "real" block elements.
	 * If "false", elements like <li>, <th> and <td> are returned. If "true", a
	 * dedicated block element block element will be created inside those
	 * elements to hold the selected content.
	 */
	this.EnforceRealBlocks = false ;
}

FCKDomRangeIterator.CreateFromSelection = function( targetWindow )
{
	var range = new FCKDomRange( targetWindow ) ;
	range.MoveToSelection() ;
	return new FCKDomRangeIterator( range ) ;
}

FCKDomRangeIterator.prototype =
{
	/**
	 * Get the next paragraph element. It automatically breaks the document
	 * when necessary to generate block elements for the paragraphs.
	 */
	GetNextParagraph : function()
	{
		// The block element to be returned.
		var block ;

		// The range object used to identify the paragraph contents.
		var range ;

		// Indicated that the current element in the loop is the last one.
		var isLast ;

		// Instructs to cleanup remaining BRs.
		var removePreviousBr ;
		var removeLastBr ;

		var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;

		// This is the first iteration. Let's initialize it.
		if ( !this._LastNode )
		{
			var range = this.Range.Clone() ;
			range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;

			this._NextNode = range.GetTouchedStartNode() ;
			this._LastNode = range.GetTouchedEndNode() ;

			// Let's reuse this variable.
			range = null ;
		}

		var currentNode = this._NextNode ;
		var lastNode = this._LastNode ;

		this._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.nodeType != 1 ) ;

			var 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.toLowerCase() ;

				if ( boundarySet[ nodeName ] && ( !FCKBrowserInfo.IsIE || currentNode.scopeName == 'HTML' ) )
				{
					// <br> boundaries must be part of the range. It will
					// happen only if ForceBrBreak.
					if ( nodeName == 'br' )
						includeNode = true ;
					else if ( !range && currentNode.childNodes.length == 0 && nodeName != 'hr' )
					{
						// If we have found an empty block, and haven't started
						// the range yet, it means we must return this block.
						block = currentNode ;
						isLast = currentNode == lastNode ;
						break ;
					}

					// The range must finish right before the boundary,
					// including possibly skipped empty spaces. (#1603)
					if ( range )
					{
						range.SetEnd( currentNode, 3, true ) ;

						// The found boundary must be set as the next one at this
						// point. (#1717)
						if ( nodeName != 'br' )
							this._NextNode = FCKDomTools.GetNextSourceNode( currentNode, true, null, lastNode ) || currentNode ;
					}

					closeRange = true ;
				}
				else
				{
					// If we have child nodes, let's check them.
					if ( currentNode.firstChild )
					{
						// If we don't have a range yet, let's start it.
						if ( !range )
						{
							range = new FCKDomRange( this.Range.Window ) ;
							range.SetStart( currentNode, 3, true ) ;
						}

						currentNode = currentNode.firstChild ;
						continue ;
					}
					includeNode = true ;
				}
			}
			else if ( currentNode.nodeType == 3 )
			{
				// Ignore normal whitespaces (i.e. not including &nbsp; or
				// other unicode whitespaces) before/after a block node.
				if ( /^[\r\n\t ]+$/.test( currentNode.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 FCKDomRange( this.Range.Window ) ;
				range.SetStart( currentNode, 3, true ) ;
			}

			// The last node has been found.
			isLast = ( ( !closeRange || includeNode ) && currentNode == lastNode ) ;
//			isLast = ( currentNode == lastNode && ( currentNode.nodeType != 1 || currentNode.childNodes.length == 0 ) ) ;

			// 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.nextSibling && !isLast )
				{
					var parentNode = currentNode.parentNode ;

					if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )
					{
						closeRange = true ;
						isLast = isLast || ( parentNode == lastNode ) ;
						break ;
					}

					currentNode = parentNode ;
					includeNode = true ;
					isLast = ( currentNode == lastNode ) ;
					continueFromSibling = true ;
				}
			}

			// Now finally include the node.
			if ( includeNode )
				range.SetEnd( currentNode, 4, true ) ;

			// We have found a block boundary. Let's close the range and move out of the
			// loop.
			if ( ( closeRange || isLast ) && range )
			{
				range._UpdateElementInfo() ;

				if ( range.StartNode == range.EndNode
						&& range.StartNode.parentNode == range.StartBlockLimit
						&& range.StartNode.getAttribute && range.StartNode.getAttribute( '_fck_bookmark' ) )
					range = null ;
				else
					break ;
			}

			if ( isLast )
				break ;

			currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;
		}

		// Now, based on the processed range, look for (or create) the block to be returned.
		if ( !block )
		{
			// If no range has been found, this is the end.
			if ( !range )
			{
				this._NextNode = null ;
				return null ;
			}

			block = range.StartBlock ;

			if ( !block
				&& !this.EnforceRealBlocks
				&& range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )
				&& range.CheckStartOfBlock()
				&& range.CheckEndOfBlock() )
			{
				block = range.StartBlockLimit ;
			}
			else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )
			{
				// Create the fixed block.
				block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;

				// Move the contents of the temporary range to the fixed block.
				range.ExtractContents().AppendTo( block ) ;
				FCKDomTools.TrimNode( block ) ;

				// Insert the fixed block into the DOM.
				range.InsertNode( block ) ;

				removePreviousBr = true ;
				removeLastBr = true ;
			}
			else if ( block.nodeName.toLowerCase() != '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.cloneNode( false ) ;

					// Extract the range contents, moving it to the new block.
					range.ExtractContents().AppendTo( block ) ;
					FCKDomTools.TrimNode( block ) ;

					// Split the block. At this 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>.

				this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;
				return block ;
			}
		}

		if ( removePreviousBr )
		{
			var previousSibling = block.previousSibling ;
			if ( previousSibling && previousSibling.nodeType == 1 )
			{
				if ( previousSibling.nodeName.toLowerCase() == 'br' )
					previousSibling.parentNode.removeChild( previousSibling ) ;
				else if ( previousSibling.lastChild && previousSibling.lastChild.nodeName.IEquals( 'br' ) )
					previousSibling.removeChild( previousSibling.lastChild ) ;
			}
		}

		if ( removeLastBr )
		{
			var lastChild = block.lastChild ;
			if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )
				block.removeChild( lastChild ) ;
		}

		// Get a reference for the next element. This is important because the
		// above block can be removed or changed, so we can rely on it for the
		// next interation.
		if ( !this._NextNode )
			this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ;

		return block ;
	}
} ;
