import torrus 1.0.9
[freeside.git] / rt / share / html / NoAuth / RichText / FCKeditor / editor / _source / classes / fckdomrangeiterator.js
1 /*\r
2  * FCKeditor - The text editor for Internet - http://www.fckeditor.net\r
3  * Copyright (C) 2003-2009 Frederico Caldeira Knabben\r
4  *\r
5  * == BEGIN LICENSE ==\r
6  *\r
7  * Licensed under the terms of any of the following licenses at your\r
8  * choice:\r
9  *\r
10  *  - GNU General Public License Version 2 or later (the "GPL")\r
11  *    http://www.gnu.org/licenses/gpl.html\r
12  *\r
13  *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")\r
14  *    http://www.gnu.org/licenses/lgpl.html\r
15  *\r
16  *  - Mozilla Public License Version 1.1 or later (the "MPL")\r
17  *    http://www.mozilla.org/MPL/MPL-1.1.html\r
18  *\r
19  * == END LICENSE ==\r
20  *\r
21  * This class can be used to interate through nodes inside a range.\r
22  *\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
25  * needed.\r
26  */\r
27 \r
28 var FCKDomRangeIterator = function( range )\r
29 {\r
30         /**\r
31          * The FCKDomRange object that marks the interation boundaries.\r
32          */\r
33         this.Range = range ;\r
34 \r
35         /**\r
36          * Indicates that <br> elements must be used as paragraph boundaries.\r
37          */\r
38         this.ForceBrBreak = false ;\r
39 \r
40         /**\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
45          */\r
46         this.EnforceRealBlocks = false ;\r
47 }\r
48 \r
49 FCKDomRangeIterator.CreateFromSelection = function( targetWindow )\r
50 {\r
51         var range = new FCKDomRange( targetWindow ) ;\r
52         range.MoveToSelection() ;\r
53         return new FCKDomRangeIterator( range ) ;\r
54 }\r
55 \r
56 FCKDomRangeIterator.prototype =\r
57 {\r
58         /**\r
59          * Get the next paragraph element. It automatically breaks the document\r
60          * when necessary to generate block elements for the paragraphs.\r
61          */\r
62         GetNextParagraph : function()\r
63         {\r
64                 // The block element to be returned.\r
65                 var block ;\r
66 \r
67                 // The range object used to identify the paragraph contents.\r
68                 var range ;\r
69 \r
70                 // Indicated that the current element in the loop is the last one.\r
71                 var isLast ;\r
72 \r
73                 // Instructs to cleanup remaining BRs.\r
74                 var removePreviousBr ;\r
75                 var removeLastBr ;\r
76 \r
77                 var boundarySet = this.ForceBrBreak ? FCKListsLib.ListBoundaries : FCKListsLib.BlockBoundaries ;\r
78 \r
79                 // This is the first iteration. Let's initialize it.\r
80                 if ( !this._LastNode )\r
81                 {\r
82                         var range = this.Range.Clone() ;\r
83                         range.Expand( this.ForceBrBreak ? 'list_contents' : 'block_contents' ) ;\r
84 \r
85                         this._NextNode = range.GetTouchedStartNode() ;\r
86                         this._LastNode = range.GetTouchedEndNode() ;\r
87 \r
88                         // Let's reuse this variable.\r
89                         range = null ;\r
90                 }\r
91 \r
92                 var currentNode = this._NextNode ;\r
93                 var lastNode = this._LastNode ;\r
94 \r
95                 this._NextNode = null ;\r
96 \r
97                 while ( currentNode )\r
98                 {\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
102 \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
106 \r
107                         var continueFromSibling = false ;\r
108 \r
109                         // If it is an element node, let's check if it can be part of the\r
110                         // range.\r
111                         if ( !includeNode )\r
112                         {\r
113                                 var nodeName = currentNode.nodeName.toLowerCase() ;\r
114 \r
115                                 if ( boundarySet[ nodeName ] && ( !FCKBrowserInfo.IsIE || currentNode.scopeName == 'HTML' ) )\r
116                                 {\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
122                                         {\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
127                                                 break ;\r
128                                         }\r
129 \r
130                                         // The range must finish right before the boundary,\r
131                                         // including possibly skipped empty spaces. (#1603)\r
132                                         if ( range )\r
133                                         {\r
134                                                 range.SetEnd( currentNode, 3, true ) ;\r
135 \r
136                                                 // The found boundary must be set as the next one at this\r
137                                                 // point. (#1717)\r
138                                                 if ( nodeName != 'br' )\r
139                                                         this._NextNode = FCKDomTools.GetNextSourceNode( currentNode, true, null, lastNode ) || currentNode ;\r
140                                         }\r
141 \r
142                                         closeRange = true ;\r
143                                 }\r
144                                 else\r
145                                 {\r
146                                         // If we have child nodes, let's check them.\r
147                                         if ( currentNode.firstChild )\r
148                                         {\r
149                                                 // If we don't have a range yet, let's start it.\r
150                                                 if ( !range )\r
151                                                 {\r
152                                                         range = new FCKDomRange( this.Range.Window ) ;\r
153                                                         range.SetStart( currentNode, 3, true ) ;\r
154                                                 }\r
155 \r
156                                                 currentNode = currentNode.firstChild ;\r
157                                                 continue ;\r
158                                         }\r
159                                         includeNode = true ;\r
160                                 }\r
161                         }\r
162                         else if ( currentNode.nodeType == 3 )\r
163                         {\r
164                                 // Ignore normal whitespaces (i.e. not including &nbsp; 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
168                         }\r
169 \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
173                         {\r
174                                 range = new FCKDomRange( this.Range.Window ) ;\r
175                                 range.SetStart( currentNode, 3, true ) ;\r
176                         }\r
177 \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
181 \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
185                         {\r
186                                 while ( !currentNode.nextSibling && !isLast )\r
187                                 {\r
188                                         var parentNode = currentNode.parentNode ;\r
189 \r
190                                         if ( boundarySet[ parentNode.nodeName.toLowerCase() ] )\r
191                                         {\r
192                                                 closeRange = true ;\r
193                                                 isLast = isLast || ( parentNode == lastNode ) ;\r
194                                                 break ;\r
195                                         }\r
196 \r
197                                         currentNode = parentNode ;\r
198                                         includeNode = true ;\r
199                                         isLast = ( currentNode == lastNode ) ;\r
200                                         continueFromSibling = true ;\r
201                                 }\r
202                         }\r
203 \r
204                         // Now finally include the node.\r
205                         if ( includeNode )\r
206                                 range.SetEnd( currentNode, 4, true ) ;\r
207 \r
208                         // We have found a block boundary. Let's close the range and move out of the\r
209                         // loop.\r
210                         if ( ( closeRange || isLast ) && range )\r
211                         {\r
212                                 range._UpdateElementInfo() ;\r
213 \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
217                                         range = null ;\r
218                                 else\r
219                                         break ;\r
220                         }\r
221 \r
222                         if ( isLast )\r
223                                 break ;\r
224 \r
225                         currentNode = FCKDomTools.GetNextSourceNode( currentNode, continueFromSibling, null, lastNode ) ;\r
226                 }\r
227 \r
228                 // Now, based on the processed range, look for (or create) the block to be returned.\r
229                 if ( !block )\r
230                 {\r
231                         // If no range has been found, this is the end.\r
232                         if ( !range )\r
233                         {\r
234                                 this._NextNode = null ;\r
235                                 return null ;\r
236                         }\r
237 \r
238                         block = range.StartBlock ;\r
239 \r
240                         if ( !block\r
241                                 && !this.EnforceRealBlocks\r
242                                 && range.StartBlockLimit.nodeName.IEquals( 'DIV', 'TH', 'TD' )\r
243                                 && range.CheckStartOfBlock()\r
244                                 && range.CheckEndOfBlock() )\r
245                         {\r
246                                 block = range.StartBlockLimit ;\r
247                         }\r
248                         else if ( !block || ( this.EnforceRealBlocks && block.nodeName.toLowerCase() == 'li' ) )\r
249                         {\r
250                                 // Create the fixed block.\r
251                                 block = this.Range.Window.document.createElement( FCKConfig.EnterMode == 'p' ? 'p' : 'div' ) ;\r
252 \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
256 \r
257                                 // Insert the fixed block into the DOM.\r
258                                 range.InsertNode( block ) ;\r
259 \r
260                                 removePreviousBr = true ;\r
261                                 removeLastBr = true ;\r
262                         }\r
263                         else if ( block.nodeName.toLowerCase() != 'li' )\r
264                         {\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
267                                 // block.\r
268                                 if ( !range.CheckStartOfBlock() || !range.CheckEndOfBlock() )\r
269                                 {\r
270                                         // The resulting block will be a clone of the current one.\r
271                                         block = block.cloneNode( false ) ;\r
272 \r
273                                         // Extract the range contents, moving it to the new block.\r
274                                         range.ExtractContents().AppendTo( block ) ;\r
275                                         FCKDomTools.TrimNode( block ) ;\r
276 \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
280 \r
281                                         removePreviousBr = !splitInfo.WasStartOfBlock ;\r
282                                         removeLastBr = !splitInfo.WasEndOfBlock ;\r
283 \r
284                                         // Insert the new block into the DOM.\r
285                                         range.InsertNode( block ) ;\r
286                                 }\r
287                         }\r
288                         else if ( !isLast )\r
289                         {\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
294 \r
295                                 this._NextNode = block == lastNode ? null : FCKDomTools.GetNextSourceNode( range.EndNode, true, null, lastNode ) ;\r
296                                 return block ;\r
297                         }\r
298                 }\r
299 \r
300                 if ( removePreviousBr )\r
301                 {\r
302                         var previousSibling = block.previousSibling ;\r
303                         if ( previousSibling && previousSibling.nodeType == 1 )\r
304                         {\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
309                         }\r
310                 }\r
311 \r
312                 if ( removeLastBr )\r
313                 {\r
314                         var lastChild = block.lastChild ;\r
315                         if ( lastChild && lastChild.nodeType == 1 && lastChild.nodeName.toLowerCase() == 'br' )\r
316                                 block.removeChild( lastChild ) ;\r
317                 }\r
318 \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
324 \r
325                 return block ;\r
326         }\r
327 } ;\r