import rt 3.8.7
[freeside.git] / rt / share / html / NoAuth / RichText / FCKeditor / editor / _source / internals / fckdomtools.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  * Utility functions to work with the DOM.\r
22  */\r
23 \r
24 var FCKDomTools =\r
25 {\r
26         /**\r
27          * Move all child nodes from one node to another.\r
28          */\r
29         MoveChildren : function( source, target, toTargetStart )\r
30         {\r
31                 if ( source == target )\r
32                         return ;\r
33 \r
34                 var eChild ;\r
35 \r
36                 if ( toTargetStart )\r
37                 {\r
38                         while ( (eChild = source.lastChild) )\r
39                                 target.insertBefore( source.removeChild( eChild ), target.firstChild ) ;\r
40                 }\r
41                 else\r
42                 {\r
43                         while ( (eChild = source.firstChild) )\r
44                                 target.appendChild( source.removeChild( eChild ) ) ;\r
45                 }\r
46         },\r
47 \r
48         MoveNode : function( source, target, toTargetStart )\r
49         {\r
50                 if ( toTargetStart )\r
51                         target.insertBefore( FCKDomTools.RemoveNode( source ), target.firstChild ) ;\r
52                 else\r
53                         target.appendChild( FCKDomTools.RemoveNode( source ) ) ;\r
54         },\r
55 \r
56         // Remove blank spaces from the beginning and the end of the contents of a node.\r
57         TrimNode : function( node )\r
58         {\r
59                 this.LTrimNode( node ) ;\r
60                 this.RTrimNode( node ) ;\r
61         },\r
62 \r
63         LTrimNode : function( node )\r
64         {\r
65                 var eChildNode ;\r
66 \r
67                 while ( (eChildNode = node.firstChild) )\r
68                 {\r
69                         if ( eChildNode.nodeType == 3 )\r
70                         {\r
71                                 var sTrimmed = eChildNode.nodeValue.LTrim() ;\r
72                                 var iOriginalLength = eChildNode.nodeValue.length ;\r
73 \r
74                                 if ( sTrimmed.length == 0 )\r
75                                 {\r
76                                         node.removeChild( eChildNode ) ;\r
77                                         continue ;\r
78                                 }\r
79                                 else if ( sTrimmed.length < iOriginalLength )\r
80                                 {\r
81                                         eChildNode.splitText( iOriginalLength - sTrimmed.length ) ;\r
82                                         node.removeChild( node.firstChild ) ;\r
83                                 }\r
84                         }\r
85                         break ;\r
86                 }\r
87         },\r
88 \r
89         RTrimNode : function( node )\r
90         {\r
91                 var eChildNode ;\r
92 \r
93                 while ( (eChildNode = node.lastChild) )\r
94                 {\r
95                         if ( eChildNode.nodeType == 3 )\r
96                         {\r
97                                 var sTrimmed = eChildNode.nodeValue.RTrim() ;\r
98                                 var iOriginalLength = eChildNode.nodeValue.length ;\r
99 \r
100                                 if ( sTrimmed.length == 0 )\r
101                                 {\r
102                                         // If the trimmed text node is empty, just remove it.\r
103 \r
104                                         // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#81).\r
105                                         eChildNode.parentNode.removeChild( eChildNode ) ;\r
106                                         continue ;\r
107                                 }\r
108                                 else if ( sTrimmed.length < iOriginalLength )\r
109                                 {\r
110                                         // If the trimmed text length is less than the original\r
111                                         // length, strip all spaces from the end by splitting\r
112                                         // the text and removing the resulting useless node.\r
113 \r
114                                         eChildNode.splitText( sTrimmed.length ) ;\r
115                                         // Use "node.lastChild.parentNode" instead of "node" to avoid IE bug (#81).\r
116                                         node.lastChild.parentNode.removeChild( node.lastChild ) ;\r
117                                 }\r
118                         }\r
119                         break ;\r
120                 }\r
121 \r
122                 if ( !FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsOpera )\r
123                 {\r
124                         eChildNode = node.lastChild ;\r
125 \r
126                         if ( eChildNode && eChildNode.nodeType == 1 && eChildNode.nodeName.toLowerCase() == 'br' )\r
127                         {\r
128                                 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324).\r
129                                 eChildNode.parentNode.removeChild( eChildNode ) ;\r
130                         }\r
131                 }\r
132         },\r
133 \r
134         RemoveNode : function( node, excludeChildren )\r
135         {\r
136                 if ( excludeChildren )\r
137                 {\r
138                         // Move all children before the node.\r
139                         var eChild ;\r
140                         while ( (eChild = node.firstChild) )\r
141                                 node.parentNode.insertBefore( node.removeChild( eChild ), node ) ;\r
142                 }\r
143 \r
144                 return node.parentNode.removeChild( node ) ;\r
145         },\r
146 \r
147         GetFirstChild : function( node, childNames )\r
148         {\r
149                 // If childNames is a string, transform it in a Array.\r
150                 if ( typeof ( childNames ) == 'string' )\r
151                         childNames = [ childNames ] ;\r
152 \r
153                 var eChild = node.firstChild ;\r
154                 while( eChild )\r
155                 {\r
156                         if ( eChild.nodeType == 1 && eChild.tagName.Equals.apply( eChild.tagName, childNames ) )\r
157                                 return eChild ;\r
158 \r
159                         eChild = eChild.nextSibling ;\r
160                 }\r
161 \r
162                 return null ;\r
163         },\r
164 \r
165         GetLastChild : function( node, childNames )\r
166         {\r
167                 // If childNames is a string, transform it in a Array.\r
168                 if ( typeof ( childNames ) == 'string' )\r
169                         childNames = [ childNames ] ;\r
170 \r
171                 var eChild = node.lastChild ;\r
172                 while( eChild )\r
173                 {\r
174                         if ( eChild.nodeType == 1 && ( !childNames || eChild.tagName.Equals( childNames ) ) )\r
175                                 return eChild ;\r
176 \r
177                         eChild = eChild.previousSibling ;\r
178                 }\r
179 \r
180                 return null ;\r
181         },\r
182 \r
183         /*\r
184          * Gets the previous element (nodeType=1) in the source order. Returns\r
185          * "null" If no element is found.\r
186          *              @param {Object} currentNode The node to start searching from.\r
187          *              @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be\r
188          *                              handled. If set to "true", only white spaces text nodes\r
189          *                              will be ignored, while non white space text nodes will stop\r
190          *                              the search, returning null. If "false" or omitted, all\r
191          *                              text nodes are ignored.\r
192          *              @param {string[]} stopSearchElements An array of element names that\r
193          *                              will cause the search to stop when found, returning null.\r
194          *                              May be omitted (or null).\r
195          *              @param {string[]} ignoreElements An array of element names that\r
196          *                              must be ignored during the search.\r
197          */\r
198         GetPreviousSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements )\r
199         {\r
200                 if ( !currentNode )\r
201                         return null ;\r
202 \r
203                 if ( stopSearchElements && currentNode.nodeType == 1 && currentNode.nodeName.IEquals( stopSearchElements ) )\r
204                         return null ;\r
205 \r
206                 if ( currentNode.previousSibling )\r
207                         currentNode = currentNode.previousSibling ;\r
208                 else\r
209                         return this.GetPreviousSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;\r
210 \r
211                 while ( currentNode )\r
212                 {\r
213                         if ( currentNode.nodeType == 1 )\r
214                         {\r
215                                 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )\r
216                                         break ;\r
217 \r
218                                 if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) )\r
219                                         return currentNode ;\r
220                         }\r
221                         else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )\r
222                                 break ;\r
223 \r
224                         if ( currentNode.lastChild )\r
225                                 currentNode = currentNode.lastChild ;\r
226                         else\r
227                                 return this.GetPreviousSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;\r
228                 }\r
229 \r
230                 return null ;\r
231         },\r
232 \r
233         /*\r
234          * Gets the next element (nodeType=1) in the source order. Returns\r
235          * "null" If no element is found.\r
236          *              @param {Object} currentNode The node to start searching from.\r
237          *              @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be\r
238          *                              handled. If set to "true", only white spaces text nodes\r
239          *                              will be ignored, while non white space text nodes will stop\r
240          *                              the search, returning null. If "false" or omitted, all\r
241          *                              text nodes are ignored.\r
242          *              @param {string[]} stopSearchElements An array of element names that\r
243          *                              will cause the search to stop when found, returning null.\r
244          *                              May be omitted (or null).\r
245          *              @param {string[]} ignoreElements An array of element names that\r
246          *                              must be ignored during the search.\r
247          */\r
248         GetNextSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements, startFromSibling )\r
249         {\r
250                 while( ( currentNode = this.GetNextSourceNode( currentNode, startFromSibling ) ) )      // Only one "=".\r
251                 {\r
252                         if ( currentNode.nodeType == 1 )\r
253                         {\r
254                                 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )\r
255                                         break ;\r
256 \r
257                                 if ( ignoreElements && currentNode.nodeName.IEquals( ignoreElements ) )\r
258                                         return this.GetNextSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;\r
259 \r
260                                 return currentNode ;\r
261                         }\r
262                         else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )\r
263                                 break ;\r
264                 }\r
265 \r
266                 return null ;\r
267         },\r
268 \r
269         /*\r
270          * Get the next DOM node available in source order.\r
271          */\r
272         GetNextSourceNode : function( currentNode, startFromSibling, nodeType, stopSearchNode )\r
273         {\r
274                 if ( !currentNode )\r
275                         return null ;\r
276 \r
277                 var node ;\r
278 \r
279                 if ( !startFromSibling && currentNode.firstChild )\r
280                         node = currentNode.firstChild ;\r
281                 else\r
282                 {\r
283                         if ( stopSearchNode && currentNode == stopSearchNode )\r
284                                 return null ;\r
285 \r
286                         node = currentNode.nextSibling ;\r
287 \r
288                         if ( !node && ( !stopSearchNode || stopSearchNode != currentNode.parentNode ) )\r
289                                 return this.GetNextSourceNode( currentNode.parentNode, true, nodeType, stopSearchNode ) ;\r
290                 }\r
291 \r
292                 if ( nodeType && node && node.nodeType != nodeType )\r
293                         return this.GetNextSourceNode( node, false, nodeType, stopSearchNode ) ;\r
294 \r
295                 return node ;\r
296         },\r
297 \r
298         /*\r
299          * Get the next DOM node available in source order.\r
300          */\r
301         GetPreviousSourceNode : function( currentNode, startFromSibling, nodeType, stopSearchNode )\r
302         {\r
303                 if ( !currentNode )\r
304                         return null ;\r
305 \r
306                 var node ;\r
307 \r
308                 if ( !startFromSibling && currentNode.lastChild )\r
309                         node = currentNode.lastChild ;\r
310                 else\r
311                 {\r
312                         if ( stopSearchNode && currentNode == stopSearchNode )\r
313                                 return null ;\r
314 \r
315                         node = currentNode.previousSibling ;\r
316 \r
317                         if ( !node && ( !stopSearchNode || stopSearchNode != currentNode.parentNode ) )\r
318                                 return this.GetPreviousSourceNode( currentNode.parentNode, true, nodeType, stopSearchNode ) ;\r
319                 }\r
320 \r
321                 if ( nodeType && node && node.nodeType != nodeType )\r
322                         return this.GetPreviousSourceNode( node, false, nodeType, stopSearchNode ) ;\r
323 \r
324                 return node ;\r
325         },\r
326 \r
327         // Inserts a element after a existing one.\r
328         InsertAfterNode : function( existingNode, newNode )\r
329         {\r
330                 return existingNode.parentNode.insertBefore( newNode, existingNode.nextSibling ) ;\r
331         },\r
332 \r
333         GetParents : function( node )\r
334         {\r
335                 var parents = new Array() ;\r
336 \r
337                 while ( node )\r
338                 {\r
339                         parents.unshift( node ) ;\r
340                         node = node.parentNode ;\r
341                 }\r
342 \r
343                 return parents ;\r
344         },\r
345 \r
346         GetCommonParents : function( node1, node2 )\r
347         {\r
348                 var p1 = this.GetParents( node1 ) ;\r
349                 var p2 = this.GetParents( node2 ) ;\r
350                 var retval = [] ;\r
351                 for ( var i = 0 ; i < p1.length ; i++ )\r
352                 {\r
353                         if ( p1[i] == p2[i] )\r
354                                 retval.push( p1[i] ) ;\r
355                 }\r
356                 return retval ;\r
357         },\r
358 \r
359         GetCommonParentNode : function( node1, node2, tagList )\r
360         {\r
361                 var tagMap = {} ;\r
362                 if ( ! tagList.pop )\r
363                         tagList = [ tagList ] ;\r
364                 while ( tagList.length > 0 )\r
365                         tagMap[tagList.pop().toLowerCase()] = 1 ;\r
366 \r
367                 var commonParents = this.GetCommonParents( node1, node2 ) ;\r
368                 var currentParent = null ;\r
369                 while ( ( currentParent = commonParents.pop() ) )\r
370                 {\r
371                         if ( tagMap[currentParent.nodeName.toLowerCase()] )\r
372                                 return currentParent ;\r
373                 }\r
374                 return null ;\r
375         },\r
376 \r
377         GetIndexOf : function( node )\r
378         {\r
379                 var currentNode = node.parentNode ? node.parentNode.firstChild : null ;\r
380                 var currentIndex = -1 ;\r
381 \r
382                 while ( currentNode )\r
383                 {\r
384                         currentIndex++ ;\r
385 \r
386                         if ( currentNode == node )\r
387                                 return currentIndex ;\r
388 \r
389                         currentNode = currentNode.nextSibling ;\r
390                 }\r
391 \r
392                 return -1 ;\r
393         },\r
394 \r
395         PaddingNode : null,\r
396 \r
397         EnforcePaddingNode : function( doc, tagName )\r
398         {\r
399                 // In IE it can happen when the page is reloaded that doc or doc.body is null, so exit here\r
400                 try\r
401                 {\r
402                         if ( !doc || !doc.body )\r
403                                 return ;\r
404                 }\r
405                 catch (e)\r
406                 {\r
407                         return ;\r
408                 }\r
409 \r
410                 this.CheckAndRemovePaddingNode( doc, tagName, true ) ;\r
411                 try\r
412                 {\r
413                         if ( doc.body.lastChild && ( doc.body.lastChild.nodeType != 1\r
414                                         || doc.body.lastChild.tagName.toLowerCase() == tagName.toLowerCase() ) )\r
415                                 return ;\r
416                 }\r
417                 catch (e)\r
418                 {\r
419                         return ;\r
420                 }\r
421 \r
422                 var node = doc.createElement( tagName ) ;\r
423                 if ( FCKBrowserInfo.IsGecko && FCKListsLib.NonEmptyBlockElements[ tagName ] )\r
424                         FCKTools.AppendBogusBr( node ) ;\r
425                 this.PaddingNode = node ;\r
426                 if ( doc.body.childNodes.length == 1\r
427                                 && doc.body.firstChild.nodeType == 1\r
428                                 && doc.body.firstChild.tagName.toLowerCase() == 'br'\r
429                                 && ( doc.body.firstChild.getAttribute( '_moz_dirty' ) != null\r
430                                         || doc.body.firstChild.getAttribute( 'type' ) == '_moz' ) )\r
431                         doc.body.replaceChild( node, doc.body.firstChild ) ;\r
432                 else\r
433                         doc.body.appendChild( node ) ;\r
434         },\r
435 \r
436         CheckAndRemovePaddingNode : function( doc, tagName, dontRemove )\r
437         {\r
438                 var paddingNode = this.PaddingNode ;\r
439                 if ( ! paddingNode )\r
440                         return ;\r
441 \r
442                 // If the padding node is changed, remove its status as a padding node.\r
443                 try\r
444                 {\r
445                         if ( paddingNode.parentNode != doc.body\r
446                                 || paddingNode.tagName.toLowerCase() != tagName\r
447                                 || ( paddingNode.childNodes.length > 1 )\r
448                                 || ( paddingNode.firstChild && paddingNode.firstChild.nodeValue != '\xa0'\r
449                                         && String(paddingNode.firstChild.tagName).toLowerCase() != 'br' ) )\r
450                         {\r
451                                 this.PaddingNode = null ;\r
452                                 return ;\r
453                         }\r
454                 }\r
455                 catch (e)\r
456                 {\r
457                                 this.PaddingNode = null ;\r
458                                 return ;\r
459                 }\r
460 \r
461                 // Now we're sure the padding node exists, and it is unchanged, and it\r
462                 // isn't the only node in doc.body, remove it.\r
463                 if ( !dontRemove )\r
464                 {\r
465                         if ( paddingNode.parentNode.childNodes.length > 1 )\r
466                                 paddingNode.parentNode.removeChild( paddingNode ) ;\r
467                         this.PaddingNode = null ;\r
468                 }\r
469         },\r
470 \r
471         HasAttribute : function( element, attributeName )\r
472         {\r
473                 if ( element.hasAttribute )\r
474                         return element.hasAttribute( attributeName ) ;\r
475                 else\r
476                 {\r
477                         var att = element.attributes[ attributeName ] ;\r
478                         return ( att != undefined && att.specified ) ;\r
479                 }\r
480         },\r
481 \r
482         /**\r
483          * Checks if an element has "specified" attributes.\r
484          */\r
485         HasAttributes : function( element )\r
486         {\r
487                 var attributes = element.attributes ;\r
488 \r
489                 for ( var i = 0 ; i < attributes.length ; i++ )\r
490                 {\r
491                         if ( FCKBrowserInfo.IsIE && attributes[i].nodeName == 'class' )\r
492                         {\r
493                                 // IE has a strange bug. If calling removeAttribute('className'),\r
494                                 // the attributes collection will still contain the "class"\r
495                                 // attribute, which will be marked as "specified", even if the\r
496                                 // outerHTML of the element is not displaying the class attribute.\r
497                                 // Note : I was not able to reproduce it outside the editor,\r
498                                 // but I've faced it while working on the TC of #1391.\r
499                                 if ( element.className.length > 0 )\r
500                                         return true ;\r
501                         }\r
502                         else if ( attributes[i].specified )\r
503                                 return true ;\r
504                 }\r
505 \r
506                 return false ;\r
507         },\r
508 \r
509         /**\r
510          * Remove an attribute from an element.\r
511          */\r
512         RemoveAttribute : function( element, attributeName )\r
513         {\r
514                 if ( FCKBrowserInfo.IsIE && attributeName.toLowerCase() == 'class' )\r
515                         attributeName = 'className' ;\r
516 \r
517                 return element.removeAttribute( attributeName, 0 ) ;\r
518         },\r
519 \r
520         /**\r
521          * Removes an array of attributes from an element\r
522          */\r
523         RemoveAttributes : function (element, aAttributes )\r
524         {\r
525                 for ( var i = 0 ; i < aAttributes.length ; i++ )\r
526                         this.RemoveAttribute( element, aAttributes[i] );\r
527         },\r
528 \r
529         GetAttributeValue : function( element, att )\r
530         {\r
531                 var attName = att ;\r
532 \r
533                 if ( typeof att == 'string' )\r
534                         att = element.attributes[ att ] ;\r
535                 else\r
536                         attName = att.nodeName ;\r
537 \r
538                 if ( att && att.specified )\r
539                 {\r
540                         // IE returns "null" for the nodeValue of a "style" attribute.\r
541                         if ( attName == 'style' )\r
542                                 return element.style.cssText ;\r
543                         // There are two cases when the nodeValue must be used:\r
544                         //              - for the "class" attribute (all browsers).\r
545                         //              - for events attributes (IE only).\r
546                         else if ( attName == 'class' || attName.indexOf('on') == 0 )\r
547                                 return att.nodeValue ;\r
548                         else\r
549                         {\r
550                                 // Use getAttribute to get its value  exactly as it is\r
551                                 // defined.\r
552                                 return element.getAttribute( attName, 2 ) ;\r
553                         }\r
554                 }\r
555                 return null ;\r
556         },\r
557 \r
558         /**\r
559          * Checks whether one element contains the other.\r
560          */\r
561         Contains : function( mainElement, otherElement )\r
562         {\r
563                 // IE supports contains, but only for element nodes.\r
564                 if ( mainElement.contains && otherElement.nodeType == 1 )\r
565                         return mainElement.contains( otherElement ) ;\r
566 \r
567                 while ( ( otherElement = otherElement.parentNode ) )    // Only one "="\r
568                 {\r
569                         if ( otherElement == mainElement )\r
570                                 return true ;\r
571                 }\r
572                 return false ;\r
573         },\r
574 \r
575         /**\r
576          * Breaks a parent element in the position of one of its contained elements.\r
577          * For example, in the following case:\r
578          *              <b>This <i>is some<span /> sample</i> test text</b>\r
579          * If element = <span />, we have these results:\r
580          *              <b>This <i>is some</i><span /><i> sample</i> test text</b>                      (If parent = <i>)\r
581          *              <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b>       (If parent = <b>)\r
582          */\r
583         BreakParent : function( element, parent, reusableRange )\r
584         {\r
585                 var range = reusableRange || new FCKDomRange( FCKTools.GetElementWindow( element ) ) ;\r
586 \r
587                 // We'll be extracting part of this element, so let's use our\r
588                 // range to get the correct piece.\r
589                 range.SetStart( element, 4 ) ;\r
590                 range.SetEnd( parent, 4 ) ;\r
591 \r
592                 // Extract it.\r
593                 var docFrag = range.ExtractContents() ;\r
594 \r
595                 // Move the element outside the broken element.\r
596                 range.InsertNode( element.parentNode.removeChild( element ) ) ;\r
597 \r
598                 // Re-insert the extracted piece after the element.\r
599                 docFrag.InsertAfterNode( element ) ;\r
600 \r
601                 range.Release( !!reusableRange ) ;\r
602         },\r
603 \r
604         /**\r
605          * Retrieves a uniquely identifiable tree address of a DOM tree node.\r
606          * The tree address returns is an array of integers, with each integer\r
607          * indicating a child index from a DOM tree node, starting from\r
608          * document.documentElement.\r
609          *\r
610          * For example, assuming <body> is the second child from <html> (<head>\r
611          * being the first), and we'd like to address the third child under the\r
612          * fourth child of body, the tree address returned would be:\r
613          * [1, 3, 2]\r
614          *\r
615          * The tree address cannot be used for finding back the DOM tree node once\r
616          * the DOM tree structure has been modified.\r
617          */\r
618         GetNodeAddress : function( node, normalized )\r
619         {\r
620                 var retval = [] ;\r
621                 while ( node && node != FCKTools.GetElementDocument( node ).documentElement )\r
622                 {\r
623                         var parentNode = node.parentNode ;\r
624                         var currentIndex = -1 ;\r
625                         for( var i = 0 ; i < parentNode.childNodes.length ; i++ )\r
626                         {\r
627                                 var candidate = parentNode.childNodes[i] ;\r
628                                 if ( normalized === true &&\r
629                                                 candidate.nodeType == 3 &&\r
630                                                 candidate.previousSibling &&\r
631                                                 candidate.previousSibling.nodeType == 3 )\r
632                                         continue;\r
633                                 currentIndex++ ;\r
634                                 if ( parentNode.childNodes[i] == node )\r
635                                         break ;\r
636                         }\r
637                         retval.unshift( currentIndex ) ;\r
638                         node = node.parentNode ;\r
639                 }\r
640                 return retval ;\r
641         },\r
642 \r
643         /**\r
644          * The reverse transformation of FCKDomTools.GetNodeAddress(). This\r
645          * function returns the DOM node pointed to by its index address.\r
646          */\r
647         GetNodeFromAddress : function( doc, addr, normalized )\r
648         {\r
649                 var cursor = doc.documentElement ;\r
650                 for ( var i = 0 ; i < addr.length ; i++ )\r
651                 {\r
652                         var target = addr[i] ;\r
653                         if ( ! normalized )\r
654                         {\r
655                                 cursor = cursor.childNodes[target] ;\r
656                                 continue ;\r
657                         }\r
658 \r
659                         var currentIndex = -1 ;\r
660                         for (var j = 0 ; j < cursor.childNodes.length ; j++ )\r
661                         {\r
662                                 var candidate = cursor.childNodes[j] ;\r
663                                 if ( normalized === true &&\r
664                                                 candidate.nodeType == 3 &&\r
665                                                 candidate.previousSibling &&\r
666                                                 candidate.previousSibling.nodeType == 3 )\r
667                                         continue ;\r
668                                 currentIndex++ ;\r
669                                 if ( currentIndex == target )\r
670                                 {\r
671                                         cursor = candidate ;\r
672                                         break ;\r
673                                 }\r
674                         }\r
675                 }\r
676                 return cursor ;\r
677         },\r
678 \r
679         CloneElement : function( element )\r
680         {\r
681                 element = element.cloneNode( false ) ;\r
682 \r
683                 // The "id" attribute should never be cloned to avoid duplication.\r
684                 element.removeAttribute( 'id', false ) ;\r
685 \r
686                 return element ;\r
687         },\r
688 \r
689         ClearElementJSProperty : function( element, attrName )\r
690         {\r
691                 if ( FCKBrowserInfo.IsIE )\r
692                         element.removeAttribute( attrName ) ;\r
693                 else\r
694                         delete element[attrName] ;\r
695         },\r
696 \r
697         SetElementMarker : function ( markerObj, element, attrName, value)\r
698         {\r
699                 var id = String( parseInt( Math.random() * 0xffffffff, 10 ) ) ;\r
700                 element._FCKMarkerId = id ;\r
701                 element[attrName] = value ;\r
702                 if ( ! markerObj[id] )\r
703                         markerObj[id] = { 'element' : element, 'markers' : {} } ;\r
704                 markerObj[id]['markers'][attrName] = value ;\r
705         },\r
706 \r
707         ClearElementMarkers : function( markerObj, element, clearMarkerObj )\r
708         {\r
709                 var id = element._FCKMarkerId ;\r
710                 if ( ! id )\r
711                         return ;\r
712                 this.ClearElementJSProperty( element, '_FCKMarkerId' ) ;\r
713                 for ( var j in markerObj[id]['markers'] )\r
714                         this.ClearElementJSProperty( element, j ) ;\r
715                 if ( clearMarkerObj )\r
716                         delete markerObj[id] ;\r
717         },\r
718 \r
719         ClearAllMarkers : function( markerObj )\r
720         {\r
721                 for ( var i in markerObj )\r
722                         this.ClearElementMarkers( markerObj, markerObj[i]['element'], true ) ;\r
723         },\r
724 \r
725         /**\r
726          * Convert a DOM list tree into a data structure that is easier to\r
727          * manipulate. This operation should be non-intrusive in the sense that it\r
728          * does not change the DOM tree, with the exception that it may add some\r
729          * markers to the list item nodes when markerObj is specified.\r
730          */\r
731         ListToArray : function( listNode, markerObj, baseArray, baseIndentLevel, grandparentNode )\r
732         {\r
733                 if ( ! listNode.nodeName.IEquals( ['ul', 'ol'] ) )\r
734                         return [] ;\r
735 \r
736                 if ( ! baseIndentLevel )\r
737                         baseIndentLevel = 0 ;\r
738                 if ( ! baseArray )\r
739                         baseArray = [] ;\r
740                 // Iterate over all list items to get their contents and look for inner lists.\r
741                 for ( var i = 0 ; i < listNode.childNodes.length ; i++ )\r
742                 {\r
743                         var listItem = listNode.childNodes[i] ;\r
744                         if ( ! listItem.nodeName.IEquals( 'li' ) )\r
745                                 continue ;\r
746                         var itemObj = { 'parent' : listNode, 'indent' : baseIndentLevel, 'contents' : [] } ;\r
747                         if ( ! grandparentNode )\r
748                         {\r
749                                 itemObj.grandparent = listNode.parentNode ;\r
750                                 if ( itemObj.grandparent && itemObj.grandparent.nodeName.IEquals( 'li' ) )\r
751                                         itemObj.grandparent = itemObj.grandparent.parentNode ;\r
752                         }\r
753                         else\r
754                                 itemObj.grandparent = grandparentNode ;\r
755                         if ( markerObj )\r
756                                 this.SetElementMarker( markerObj, listItem, '_FCK_ListArray_Index', baseArray.length ) ;\r
757                         baseArray.push( itemObj ) ;\r
758                         for ( var j = 0 ; j < listItem.childNodes.length ; j++ )\r
759                         {\r
760                                 var child = listItem.childNodes[j] ;\r
761                                 if ( child.nodeName.IEquals( ['ul', 'ol'] ) )\r
762                                         // Note the recursion here, it pushes inner list items with\r
763                                         // +1 indentation in the correct order.\r
764                                         this.ListToArray( child, markerObj, baseArray, baseIndentLevel + 1, itemObj.grandparent ) ;\r
765                                 else\r
766                                         itemObj.contents.push( child ) ;\r
767                         }\r
768                 }\r
769                 return baseArray ;\r
770         },\r
771 \r
772         // Convert our internal representation of a list back to a DOM forest.\r
773         ArrayToList : function( listArray, markerObj, baseIndex )\r
774         {\r
775                 if ( baseIndex == undefined )\r
776                         baseIndex = 0 ;\r
777                 if ( ! listArray || listArray.length < baseIndex + 1 )\r
778                         return null ;\r
779                 var doc = FCKTools.GetElementDocument( listArray[baseIndex].parent ) ;\r
780                 var retval = doc.createDocumentFragment() ;\r
781                 var rootNode = null ;\r
782                 var currentIndex = baseIndex ;\r
783                 var indentLevel = Math.max( listArray[baseIndex].indent, 0 ) ;\r
784                 var currentListItem = null ;\r
785                 while ( true )\r
786                 {\r
787                         var item = listArray[currentIndex] ;\r
788                         if ( item.indent == indentLevel )\r
789                         {\r
790                                 if ( ! rootNode || listArray[currentIndex].parent.nodeName != rootNode.nodeName )\r
791                                 {\r
792                                         rootNode = listArray[currentIndex].parent.cloneNode( false ) ;\r
793                                         retval.appendChild( rootNode ) ;\r
794                                 }\r
795                                 currentListItem = doc.createElement( 'li' ) ;\r
796                                 rootNode.appendChild( currentListItem ) ;\r
797                                 for ( var i = 0 ; i < item.contents.length ; i++ )\r
798                                         currentListItem.appendChild( item.contents[i].cloneNode( true ) ) ;\r
799                                 currentIndex++ ;\r
800                         }\r
801                         else if ( item.indent == Math.max( indentLevel, 0 ) + 1 )\r
802                         {\r
803                                 var listData = this.ArrayToList( listArray, null, currentIndex ) ;\r
804                                 currentListItem.appendChild( listData.listNode ) ;\r
805                                 currentIndex = listData.nextIndex ;\r
806                         }\r
807                         else if ( item.indent == -1 && baseIndex == 0 && item.grandparent )\r
808                         {\r
809                                 var currentListItem ;\r
810                                 if ( item.grandparent.nodeName.IEquals( ['ul', 'ol'] ) )\r
811                                         currentListItem = doc.createElement( 'li' ) ;\r
812                                 else\r
813                                 {\r
814                                         if ( FCKConfig.EnterMode.IEquals( ['div', 'p'] ) && ! item.grandparent.nodeName.IEquals( 'td' ) )\r
815                                                 currentListItem = doc.createElement( FCKConfig.EnterMode ) ;\r
816                                         else\r
817                                                 currentListItem = doc.createDocumentFragment() ;\r
818                                 }\r
819                                 for ( var i = 0 ; i < item.contents.length ; i++ )\r
820                                         currentListItem.appendChild( item.contents[i].cloneNode( true ) ) ;\r
821                                 if ( currentListItem.nodeType == 11 )\r
822                                 {\r
823                                         if ( currentListItem.lastChild &&\r
824                                                         currentListItem.lastChild.getAttribute &&\r
825                                                         currentListItem.lastChild.getAttribute( 'type' ) == '_moz' )\r
826                                                 currentListItem.removeChild( currentListItem.lastChild );\r
827                                         currentListItem.appendChild( doc.createElement( 'br' ) ) ;\r
828                                 }\r
829                                 if ( currentListItem.nodeName.IEquals( FCKConfig.EnterMode ) && currentListItem.firstChild )\r
830                                 {\r
831                                         this.TrimNode( currentListItem ) ;\r
832                                         if ( FCKListsLib.BlockBoundaries[currentListItem.firstChild.nodeName.toLowerCase()] )\r
833                                         {\r
834                                                 var tmp = doc.createDocumentFragment() ;\r
835                                                 while ( currentListItem.firstChild )\r
836                                                         tmp.appendChild( currentListItem.removeChild( currentListItem.firstChild ) ) ;\r
837                                                 currentListItem = tmp ;\r
838                                         }\r
839                                 }\r
840                                 if ( FCKBrowserInfo.IsGeckoLike && currentListItem.nodeName.IEquals( ['div', 'p'] ) )\r
841                                         FCKTools.AppendBogusBr( currentListItem ) ;\r
842                                 retval.appendChild( currentListItem ) ;\r
843                                 rootNode = null ;\r
844                                 currentIndex++ ;\r
845                         }\r
846                         else\r
847                                 return null ;\r
848 \r
849                         if ( listArray.length <= currentIndex || Math.max( listArray[currentIndex].indent, 0 ) < indentLevel )\r
850                         {\r
851                                 break ;\r
852                         }\r
853                 }\r
854 \r
855                 // Clear marker attributes for the new list tree made of cloned nodes, if any.\r
856                 if ( markerObj )\r
857                 {\r
858                         var currentNode = retval.firstChild ;\r
859                         while ( currentNode )\r
860                         {\r
861                                 if ( currentNode.nodeType == 1 )\r
862                                         this.ClearElementMarkers( markerObj, currentNode ) ;\r
863                                 currentNode = this.GetNextSourceNode( currentNode ) ;\r
864                         }\r
865                 }\r
866 \r
867                 return { 'listNode' : retval, 'nextIndex' : currentIndex } ;\r
868         },\r
869 \r
870         /**\r
871          * Get the next sibling node for a node. If "includeEmpties" is false,\r
872          * only element or non empty text nodes are returned.\r
873          */\r
874         GetNextSibling : function( node, includeEmpties )\r
875         {\r
876                 node = node.nextSibling ;\r
877 \r
878                 while ( node && !includeEmpties && node.nodeType != 1 && ( node.nodeType != 3 || node.nodeValue.length == 0 ) )\r
879                         node = node.nextSibling ;\r
880 \r
881                 return node ;\r
882         },\r
883 \r
884         /**\r
885          * Get the previous sibling node for a node. If "includeEmpties" is false,\r
886          * only element or non empty text nodes are returned.\r
887          */\r
888         GetPreviousSibling : function( node, includeEmpties )\r
889         {\r
890                 node = node.previousSibling ;\r
891 \r
892                 while ( node && !includeEmpties && node.nodeType != 1 && ( node.nodeType != 3 || node.nodeValue.length == 0 ) )\r
893                         node = node.previousSibling ;\r
894 \r
895                 return node ;\r
896         },\r
897 \r
898         /**\r
899          * Checks if an element has no "useful" content inside of it\r
900          * node tree. No "useful" content means empty text node or a signle empty\r
901          * inline node.\r
902          * elementCheckCallback may point to a function that returns a boolean\r
903          * indicating that a child element must be considered in the element check.\r
904          */\r
905         CheckIsEmptyElement : function( element, elementCheckCallback )\r
906         {\r
907                 var child = element.firstChild ;\r
908                 var elementChild ;\r
909 \r
910                 while ( child )\r
911                 {\r
912                         if ( child.nodeType == 1 )\r
913                         {\r
914                                 if ( elementChild || !FCKListsLib.InlineNonEmptyElements[ child.nodeName.toLowerCase() ] )\r
915                                         return false ;\r
916 \r
917                                 if ( !elementCheckCallback || elementCheckCallback( child ) === true )\r
918                                         elementChild = child ;\r
919                         }\r
920                         else if ( child.nodeType == 3 && child.nodeValue.length > 0 )\r
921                                 return false ;\r
922 \r
923                         child = child.nextSibling ;\r
924                 }\r
925 \r
926                 return elementChild ? this.CheckIsEmptyElement( elementChild, elementCheckCallback ) : true ;\r
927         },\r
928 \r
929         SetElementStyles : function( element, styleDict )\r
930         {\r
931                 var style = element.style ;\r
932                 for ( var styleName in styleDict )\r
933                         style[ styleName ] = styleDict[ styleName ] ;\r
934         },\r
935 \r
936         SetOpacity : function( element, opacity )\r
937         {\r
938                 if ( FCKBrowserInfo.IsIE )\r
939                 {\r
940                         opacity = Math.round( opacity * 100 ) ;\r
941                         element.style.filter = ( opacity > 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' ) ;\r
942                 }\r
943                 else\r
944                         element.style.opacity = opacity ;\r
945         },\r
946 \r
947         GetCurrentElementStyle : function( element, propertyName )\r
948         {\r
949                 if ( FCKBrowserInfo.IsIE )\r
950                         return element.currentStyle[ propertyName ] ;\r
951                 else\r
952                         return element.ownerDocument.defaultView.getComputedStyle( element, '' ).getPropertyValue( propertyName ) ;\r
953         },\r
954 \r
955         GetPositionedAncestor : function( element )\r
956         {\r
957                 var currentElement = element ;\r
958 \r
959                 while ( currentElement != FCKTools.GetElementDocument( currentElement ).documentElement )\r
960                 {\r
961                         if ( this.GetCurrentElementStyle( currentElement, 'position' ) != 'static' )\r
962                                 return currentElement ;\r
963 \r
964                         if ( currentElement == FCKTools.GetElementDocument( currentElement ).documentElement\r
965                                         && currentWindow != w )\r
966                                 currentElement = currentWindow.frameElement ;\r
967                         else\r
968                                 currentElement = currentElement.parentNode ;\r
969                 }\r
970 \r
971                 return null ;\r
972         },\r
973 \r
974         /**\r
975          * Current implementation for ScrollIntoView (due to #1462 and #2279). We\r
976          * don't have a complete implementation here, just the things that fit our\r
977          * needs.\r
978          */\r
979         ScrollIntoView : function( element, alignTop )\r
980         {\r
981                 // Get the element window.\r
982                 var window = FCKTools.GetElementWindow( element ) ;\r
983                 var windowHeight = FCKTools.GetViewPaneSize( window ).Height ;\r
984 \r
985                 // Starts the offset that will be scrolled with the negative value of\r
986                 // the visible window height.\r
987                 var offset = windowHeight * -1 ;\r
988 \r
989                 // Appends the height it we are about to align the bottoms.\r
990                 if ( alignTop === false )\r
991                 {\r
992                         offset += element.offsetHeight || 0 ;\r
993 \r
994                         // Consider the margin in the scroll, which is ok for our current\r
995                         // needs, but needs investigation if we will be using this function\r
996                         // in other places.\r
997                         offset += parseInt( this.GetCurrentElementStyle( element, 'marginBottom' ) || 0, 10 ) || 0 ;\r
998                 }\r
999 \r
1000                 // Appends the offsets for the entire element hierarchy.\r
1001                 var elementPosition = FCKTools.GetDocumentPosition( window, element ) ;\r
1002                 offset += elementPosition.y ;\r
1003 \r
1004                 // Scroll the window to the desired position, if not already visible.\r
1005                 var currentScroll = FCKTools.GetScrollPosition( window ).Y ;\r
1006                 if ( offset > 0 && ( offset > currentScroll || offset < currentScroll - windowHeight ) )\r
1007                         window.scrollTo( 0, offset ) ;\r
1008         },\r
1009 \r
1010         /**\r
1011          * Check if the element can be edited inside the browser.\r
1012          */\r
1013         CheckIsEditable : function( element )\r
1014         {\r
1015                 // Get the element name.\r
1016                 var nodeName = element.nodeName.toLowerCase() ;\r
1017 \r
1018                 // Get the element DTD (defaults to span for unknown elements).\r
1019                 var childDTD = FCK.DTD[ nodeName ] || FCK.DTD.span ;\r
1020 \r
1021                 // In the DTD # == text node.\r
1022                 return ( childDTD['#'] && !FCKListsLib.NonEditableElements[ nodeName ] ) ;\r
1023         },\r
1024 \r
1025         GetSelectedDivContainers : function()\r
1026         {\r
1027                 var currentBlocks = [] ;\r
1028                 var range = new FCKDomRange( FCK.EditorWindow ) ;\r
1029                 range.MoveToSelection() ;\r
1030 \r
1031                 var startNode = range.GetTouchedStartNode() ;\r
1032                 var endNode = range.GetTouchedEndNode() ;\r
1033                 var currentNode = startNode ;\r
1034 \r
1035                 if ( startNode == endNode )\r
1036                 {\r
1037                         while ( endNode.nodeType == 1 && endNode.lastChild )\r
1038                                 endNode = endNode.lastChild ;\r
1039                         endNode = FCKDomTools.GetNextSourceNode( endNode ) ;\r
1040                 }\r
1041 \r
1042                 while ( currentNode && currentNode != endNode )\r
1043                 {\r
1044                         if ( currentNode.nodeType != 3 || !/^[ \t\n]*$/.test( currentNode.nodeValue ) )\r
1045                         {\r
1046                                 var path = new FCKElementPath( currentNode ) ;\r
1047                                 var blockLimit = path.BlockLimit ;\r
1048                                 if ( blockLimit && blockLimit.nodeName.IEquals( 'div' ) && currentBlocks.IndexOf( blockLimit ) == -1 )\r
1049                                         currentBlocks.push( blockLimit ) ;\r
1050                         }\r
1051 \r
1052                         currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;\r
1053                 }\r
1054 \r
1055                 return currentBlocks ;\r
1056         }\r
1057 } ;\r