~ubuntu-it-wiki/wiki-ubuntu-it/wiki-repo

« back to all changes in this revision

Viewing changes to applets/FCKeditor/editor/_source/internals/fckdomtools.js

  • Committer: Leo Iannacone
  • Date: 2011-06-02 15:05:37 UTC
  • Revision ID: l3on@ubuntu.com-20110602150537-ycrnf58qf67uf593
Added applets for gui editor

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