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 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