/* * FCKeditor - The text editor for Internet - http://www.fckeditor.net * Copyright (C) 2003-2007 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
elements must be used as paragraph boundaries. */ this.ForceBrBreak = false ; /** * Guarantees that the iterator will always return "real" block elements. * If "false", elements like
  • , and 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 ; 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 ] ) { //
    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 ; } 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   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 ; 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
  • child (nested // lists) or the next sibling
  • . 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 && previousSibling.nodeName.toLowerCase() == 'br' ) previousSibling.parentNode.removeChild( previousSibling ) ; } 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. this._NextNode = ( isLast || block == lastNode ) ? null : FCKDomTools.GetNextSourceNode( block, true, null, lastNode ) ; return block ; } } ;