~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/web/static/htdocs/applets/FCKeditor/editor/_source/internals/fckdomtools.js

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

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