~ubuntu-branches/ubuntu/oneiric/moin/oneiric-security

« back to all changes in this revision

Viewing changes to MoinMoin/web/static/htdocs/applets/FCKeditor/editor/_source/classes/fckstyle.js

  • Committer: Bazaar Package Importer
  • Author(s): Jamie Strandboge
  • Date: 2010-03-30 12:55:34 UTC
  • mfrom: (0.1.17 sid)
  • Revision ID: james.westby@ubuntu.com-20100330125534-4c2ufc1rok24447l
Tags: 1.9.2-2ubuntu1
* Merge from Debian testing (LP: #521834). Based on work by Stefan Ebner.
  Remaining changes:
 - Remove python-xml from Suggests field, the package isn't anymore in
   sys.path.
 - Demote fckeditor from Recommends to Suggests; the code was previously
   embedded in moin, but it was also disabled, so there's no reason for us
   to pull this in by default currently. Note: This isn't necessary anymore
   but needs a MIR for fckeditor, so postpone dropping this change until
   lucid+1
* debian/rules:
  - Replace hardcoded python2.5 with python* and hardcore python2.6 for ln
* debian/control.in: drop versioned depends on cdbs

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-2009 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
 * FCKStyle Class: contains a style definition, and all methods to work with
 
22
 * the style in a document.
 
23
 */
 
24
 
 
25
/**
 
26
 * @param {Object} styleDesc A "style descriptor" object, containing the raw
 
27
 * style definition in the following format:
 
28
 *              '<style name>' : {
 
29
 *                      Element : '<element name>',
 
30
 *                      Attributes : {
 
31
 *                              '<att name>' : '<att value>',
 
32
 *                              ...
 
33
 *                      },
 
34
 *                      Styles : {
 
35
 *                              '<style name>' : '<style value>',
 
36
 *                              ...
 
37
 *                      },
 
38
 *                      Overrides : '<element name>'|{
 
39
 *                              Element : '<element name>',
 
40
 *                              Attributes : {
 
41
 *                                      '<att name>' : '<att value>'|/<att regex>/
 
42
 *                              },
 
43
 *                              Styles : {
 
44
 *                                      '<style name>' : '<style value>'|/<style regex>/
 
45
 *                              },
 
46
 *                      }
 
47
 *              }
 
48
 */
 
49
var FCKStyle = function( styleDesc )
 
50
{
 
51
        this.Element = ( styleDesc.Element || 'span' ).toLowerCase() ;
 
52
        this._StyleDesc = styleDesc ;
 
53
}
 
54
 
 
55
FCKStyle.prototype =
 
56
{
 
57
        /**
 
58
         * Get the style type, based on its element name:
 
59
         *              - FCK_STYLE_BLOCK  (0): Block Style
 
60
         *              - FCK_STYLE_INLINE (1): Inline Style
 
61
         *              - FCK_STYLE_OBJECT (2): Object Style
 
62
         */
 
63
        GetType : function()
 
64
        {
 
65
                var type = this.GetType_$ ;
 
66
 
 
67
                if ( type != undefined )
 
68
                        return type ;
 
69
 
 
70
                var elementName = this.Element ;
 
71
 
 
72
                if ( elementName == '#' || FCKListsLib.StyleBlockElements[ elementName ] )
 
73
                        type = FCK_STYLE_BLOCK ;
 
74
                else if ( FCKListsLib.StyleObjectElements[ elementName ] )
 
75
                        type = FCK_STYLE_OBJECT ;
 
76
                else
 
77
                        type = FCK_STYLE_INLINE ;
 
78
 
 
79
                return ( this.GetType_$ = type ) ;
 
80
        },
 
81
 
 
82
        /**
 
83
         * Apply the style to the current selection.
 
84
         */
 
85
        ApplyToSelection : function( targetWindow )
 
86
        {
 
87
                // Create a range for the current selection.
 
88
                var range = new FCKDomRange( targetWindow ) ;
 
89
                range.MoveToSelection() ;
 
90
 
 
91
                this.ApplyToRange( range, true ) ;
 
92
        },
 
93
 
 
94
        /**
 
95
         * Apply the style to a FCKDomRange.
 
96
         */
 
97
        ApplyToRange : function( range, selectIt, updateRange )
 
98
        {
 
99
                // ApplyToRange is not valid for FCK_STYLE_OBJECT types.
 
100
                // Use ApplyToObject instead.
 
101
 
 
102
                switch ( this.GetType() )
 
103
                {
 
104
                        case FCK_STYLE_BLOCK :
 
105
                                this.ApplyToRange = this._ApplyBlockStyle ;
 
106
                                break ;
 
107
                        case FCK_STYLE_INLINE :
 
108
                                this.ApplyToRange = this._ApplyInlineStyle ;
 
109
                                break ;
 
110
                        default :
 
111
                                return ;
 
112
                }
 
113
 
 
114
                this.ApplyToRange( range, selectIt, updateRange ) ;
 
115
        },
 
116
 
 
117
        /**
 
118
         * Apply the style to an object. Valid for FCK_STYLE_BLOCK types only.
 
119
         */
 
120
        ApplyToObject : function( objectElement )
 
121
        {
 
122
                if ( !objectElement )
 
123
                        return ;
 
124
 
 
125
                this.BuildElement( null, objectElement ) ;
 
126
        },
 
127
 
 
128
        /**
 
129
         * Remove the style from the current selection.
 
130
         */
 
131
        RemoveFromSelection : function( targetWindow )
 
132
        {
 
133
                // Create a range for the current selection.
 
134
                var range = new FCKDomRange( targetWindow ) ;
 
135
                range.MoveToSelection() ;
 
136
 
 
137
                this.RemoveFromRange( range, true ) ;
 
138
        },
 
139
 
 
140
        /**
 
141
         * Remove the style from a FCKDomRange. Block type styles will have no
 
142
         * effect.
 
143
         */
 
144
        RemoveFromRange : function( range, selectIt, updateRange )
 
145
        {
 
146
                var bookmark ;
 
147
 
 
148
                // Create the attribute list to be used later for element comparisons.
 
149
                var styleAttribs = this._GetAttribsForComparison() ;
 
150
                var styleOverrides = this._GetOverridesForComparison() ;
 
151
 
 
152
                // If collapsed, we are removing all conflicting styles from the range
 
153
                // parent tree.
 
154
                if ( range.CheckIsCollapsed() )
 
155
                {
 
156
                        // Bookmark the range so we can re-select it after processing.
 
157
                        var bookmark = range.CreateBookmark( true ) ;
 
158
 
 
159
                        // Let's start from the bookmark <span> parent.
 
160
                        var bookmarkStart = range.GetBookmarkNode( bookmark, true ) ;
 
161
 
 
162
                        var path = new FCKElementPath( bookmarkStart.parentNode ) ;
 
163
 
 
164
                        // While looping through the path, we'll be saving references to
 
165
                        // parent elements if the range is in one of their boundaries. In
 
166
                        // this way, we are able to create a copy of those elements when
 
167
                        // removing a style if the range is in a boundary limit (see #1270).
 
168
                        var boundaryElements = [] ;
 
169
 
 
170
                        // Check if the range is in the boundary limits of an element
 
171
                        // (related to #1270).
 
172
                        var isBoundaryRight = !FCKDomTools.GetNextSibling( bookmarkStart ) ;
 
173
                        var isBoundary = isBoundaryRight || !FCKDomTools.GetPreviousSibling( bookmarkStart ) ;
 
174
 
 
175
                        // This is the last element to be removed in the boundary situation
 
176
                        // described at #1270.
 
177
                        var lastBoundaryElement ;
 
178
                        var boundaryLimitIndex = -1 ;
 
179
 
 
180
                        for ( var i = 0 ; i < path.Elements.length ; i++ )
 
181
                        {
 
182
                                var pathElement = path.Elements[i] ;
 
183
                                if ( this.CheckElementRemovable( pathElement ) )
 
184
                                {
 
185
                                        if ( isBoundary
 
186
                                                && !FCKDomTools.CheckIsEmptyElement( pathElement,
 
187
                                                                function( el )
 
188
                                                                {
 
189
                                                                        return ( el != bookmarkStart ) ;
 
190
                                                                } )
 
191
                                                )
 
192
                                        {
 
193
                                                lastBoundaryElement = pathElement ;
 
194
 
 
195
                                                // We'll be continuously including elements in the
 
196
                                                // boundaryElements array, but only those added before
 
197
                                                // setting lastBoundaryElement must be used later, so
 
198
                                                // let's mark the current index here.
 
199
                                                boundaryLimitIndex = boundaryElements.length - 1 ;
 
200
                                        }
 
201
                                        else
 
202
                                        {
 
203
                                                var pathElementName = pathElement.nodeName.toLowerCase() ;
 
204
 
 
205
                                                if ( pathElementName == this.Element )
 
206
                                                {
 
207
                                                        // Remove any attribute that conflict with this style, no
 
208
                                                        // matter their values.
 
209
                                                        for ( var att in styleAttribs )
 
210
                                                        {
 
211
                                                                if ( FCKDomTools.HasAttribute( pathElement, att ) )
 
212
                                                                {
 
213
                                                                        switch ( att )
 
214
                                                                        {
 
215
                                                                                case 'style' :
 
216
                                                                                        this._RemoveStylesFromElement( pathElement ) ;
 
217
                                                                                        break ;
 
218
 
 
219
                                                                                case 'class' :
 
220
                                                                                        // The 'class' element value must match (#1318).
 
221
                                                                                        if ( FCKDomTools.GetAttributeValue( pathElement, att ) != this.GetFinalAttributeValue( att ) )
 
222
                                                                                                continue ;
 
223
 
 
224
                                                                                        /*jsl:fallthru*/
 
225
 
 
226
                                                                                default :
 
227
                                                                                        FCKDomTools.RemoveAttribute( pathElement, att ) ;
 
228
                                                                        }
 
229
                                                                }
 
230
                                                        }
 
231
                                                }
 
232
 
 
233
                                                // Remove overrides defined to the same element name.
 
234
                                                this._RemoveOverrides( pathElement, styleOverrides[ pathElementName ] ) ;
 
235
 
 
236
                                                // Remove the element if no more attributes are available and it's an inline style element
 
237
                                                if ( this.GetType() == FCK_STYLE_INLINE)
 
238
                                                        this._RemoveNoAttribElement( pathElement ) ;
 
239
                                        }
 
240
                                }
 
241
                                else if ( isBoundary )
 
242
                                        boundaryElements.push( pathElement ) ;
 
243
 
 
244
                                // Check if we are still in a boundary (at the same side).
 
245
                                isBoundary = isBoundary && ( ( isBoundaryRight && !FCKDomTools.GetNextSibling( pathElement ) ) || ( !isBoundaryRight && !FCKDomTools.GetPreviousSibling( pathElement ) ) ) ;
 
246
 
 
247
                                // If we are in an element that is not anymore a boundary, or
 
248
                                // we are at the last element, let's move things outside the
 
249
                                // boundary (if available).
 
250
                                if ( lastBoundaryElement && ( !isBoundary || ( i == path.Elements.length - 1 ) ) )
 
251
                                {
 
252
                                        // Remove the bookmark node from the DOM.
 
253
                                        var currentElement = FCKDomTools.RemoveNode( bookmarkStart ) ;
 
254
 
 
255
                                        // Build the collapsed group of elements that are not
 
256
                                        // removed by this style, but share the boundary.
 
257
                                        // (see comment 1 and 2 at #1270)
 
258
                                        for ( var j = 0 ; j <= boundaryLimitIndex ; j++ )
 
259
                                        {
 
260
                                                var newElement = FCKDomTools.CloneElement( boundaryElements[j] ) ;
 
261
                                                newElement.appendChild( currentElement ) ;
 
262
                                                currentElement = newElement ;
 
263
                                        }
 
264
 
 
265
                                        // Re-insert the bookmark node (and the collapsed elements)
 
266
                                        // in the DOM, in the new position next to the styled element.
 
267
                                        if ( isBoundaryRight )
 
268
                                                FCKDomTools.InsertAfterNode( lastBoundaryElement, currentElement ) ;
 
269
                                        else
 
270
                                                lastBoundaryElement.parentNode.insertBefore( currentElement, lastBoundaryElement ) ;
 
271
 
 
272
                                        isBoundary = false ;
 
273
                                        lastBoundaryElement = null ;
 
274
                                }
 
275
                        }
 
276
 
 
277
                                // Re-select the original range.
 
278
                        if ( selectIt )
 
279
                                range.SelectBookmark( bookmark ) ;
 
280
 
 
281
                        if ( updateRange )
 
282
                                range.MoveToBookmark( bookmark ) ;
 
283
 
 
284
                        return ;
 
285
                }
 
286
 
 
287
                // Expand the range, if inside inline element boundaries.
 
288
                range.Expand( 'inline_elements' ) ;
 
289
 
 
290
                // Bookmark the range so we can re-select it after processing.
 
291
                bookmark = range.CreateBookmark( true ) ;
 
292
 
 
293
                // The style will be applied within the bookmark boundaries.
 
294
                var startNode   = range.GetBookmarkNode( bookmark, true ) ;
 
295
                var endNode             = range.GetBookmarkNode( bookmark, false ) ;
 
296
 
 
297
                range.Release( true ) ;
 
298
 
 
299
                // We need to check the selection boundaries (bookmark spans) to break
 
300
                // the code in a way that we can properly remove partially selected nodes.
 
301
                // For example, removing a <b> style from
 
302
                //              <b>This is [some text</b> to show <b>the] problem</b>
 
303
                // ... where [ and ] represent the selection, must result:
 
304
                //              <b>This is </b>[some text to show the]<b> problem</b>
 
305
                // The strategy is simple, we just break the partial nodes before the
 
306
                // removal logic, having something that could be represented this way:
 
307
                //              <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
 
308
 
 
309
                // Let's start checking the start boundary.
 
310
                var path = new FCKElementPath( startNode ) ;
 
311
                var pathElements = path.Elements ;
 
312
                var pathElement ;
 
313
 
 
314
                for ( var i = 1 ; i < pathElements.length ; i++ )
 
315
                {
 
316
                        pathElement = pathElements[i] ;
 
317
 
 
318
                        if ( pathElement == path.Block || pathElement == path.BlockLimit )
 
319
                                break ;
 
320
 
 
321
                        // If this element can be removed (even partially).
 
322
                        if ( this.CheckElementRemovable( pathElement ) )
 
323
                                FCKDomTools.BreakParent( startNode, pathElement, range ) ;
 
324
                }
 
325
 
 
326
                // Now the end boundary.
 
327
                path = new FCKElementPath( endNode ) ;
 
328
                pathElements = path.Elements ;
 
329
 
 
330
                for ( var i = 1 ; i < pathElements.length ; i++ )
 
331
                {
 
332
                        pathElement = pathElements[i] ;
 
333
 
 
334
                        if ( pathElement == path.Block || pathElement == path.BlockLimit )
 
335
                                break ;
 
336
 
 
337
                        elementName = pathElement.nodeName.toLowerCase() ;
 
338
 
 
339
                        // If this element can be removed (even partially).
 
340
                        if ( this.CheckElementRemovable( pathElement ) )
 
341
                                FCKDomTools.BreakParent( endNode, pathElement, range ) ;
 
342
                }
 
343
 
 
344
                // Navigate through all nodes between the bookmarks.
 
345
                var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
 
346
 
 
347
                while ( currentNode )
 
348
                {
 
349
                        // Cache the next node to be processed. Do it now, because
 
350
                        // currentNode may be removed.
 
351
                        var nextNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
 
352
 
 
353
                        // Remove elements nodes that match with this style rules.
 
354
                        if ( currentNode.nodeType == 1 )
 
355
                        {
 
356
                                var elementName = currentNode.nodeName.toLowerCase() ;
 
357
 
 
358
                                var mayRemove = ( elementName == this.Element ) ;
 
359
                                if ( mayRemove )
 
360
                                {
 
361
                                        // Remove any attribute that conflict with this style, no matter
 
362
                                        // their values.
 
363
                                        for ( var att in styleAttribs )
 
364
                                        {
 
365
                                                if ( FCKDomTools.HasAttribute( currentNode, att ) )
 
366
                                                {
 
367
                                                        switch ( att )
 
368
                                                        {
 
369
                                                                case 'style' :
 
370
                                                                        this._RemoveStylesFromElement( currentNode ) ;
 
371
                                                                        break ;
 
372
 
 
373
                                                                case 'class' :
 
374
                                                                        // The 'class' element value must match (#1318).
 
375
                                                                        if ( FCKDomTools.GetAttributeValue( currentNode, att ) != this.GetFinalAttributeValue( att ) )
 
376
                                                                                continue ;
 
377
 
 
378
                                                                        /*jsl:fallthru*/
 
379
 
 
380
                                                                default :
 
381
                                                                        FCKDomTools.RemoveAttribute( currentNode, att ) ;
 
382
                                                        }
 
383
                                                }
 
384
                                        }
 
385
                                }
 
386
                                else
 
387
                                        mayRemove = !!styleOverrides[ elementName ] ;
 
388
 
 
389
                                if ( mayRemove )
 
390
                                {
 
391
                                        // Remove overrides defined to the same element name.
 
392
                                        this._RemoveOverrides( currentNode, styleOverrides[ elementName ] ) ;
 
393
 
 
394
                                        // Remove the element if no more attributes are available.
 
395
                                        this._RemoveNoAttribElement( currentNode ) ;
 
396
                                }
 
397
                        }
 
398
 
 
399
                        // If we have reached the end of the selection, stop looping.
 
400
                        if ( nextNode == endNode )
 
401
                                break ;
 
402
 
 
403
                        currentNode = nextNode ;
 
404
                }
 
405
 
 
406
                this._FixBookmarkStart( startNode ) ;
 
407
 
 
408
                // Re-select the original range.
 
409
                if ( selectIt )
 
410
                        range.SelectBookmark( bookmark ) ;
 
411
 
 
412
                if ( updateRange )
 
413
                        range.MoveToBookmark( bookmark ) ;
 
414
        },
 
415
 
 
416
        /**
 
417
         * Checks if an element, or any of its attributes, is removable by the
 
418
         * current style definition.
 
419
         */
 
420
        CheckElementRemovable : function( element, fullMatch )
 
421
        {
 
422
                if ( !element )
 
423
                        return false ;
 
424
 
 
425
                var elementName = element.nodeName.toLowerCase() ;
 
426
 
 
427
                // If the element name is the same as the style name.
 
428
                if ( elementName == this.Element )
 
429
                {
 
430
                        // If no attributes are defined in the element.
 
431
                        if ( !fullMatch && !FCKDomTools.HasAttributes( element ) )
 
432
                                return true ;
 
433
 
 
434
                        // If any attribute conflicts with the style attributes.
 
435
                        var attribs = this._GetAttribsForComparison() ;
 
436
                        var allMatched = ( attribs._length == 0 ) ;
 
437
                        for ( var att in attribs )
 
438
                        {
 
439
                                if ( att == '_length' )
 
440
                                        continue ;
 
441
 
 
442
                                if ( this._CompareAttributeValues( att, FCKDomTools.GetAttributeValue( element, att ), ( this.GetFinalAttributeValue( att ) || '' ) ) )
 
443
                                {
 
444
                                        allMatched = true ;
 
445
                                        if ( !fullMatch )
 
446
                                                break ;
 
447
                                }
 
448
                                else
 
449
                                {
 
450
                                        allMatched = false ;
 
451
                                        if ( fullMatch )
 
452
                                                return false ;
 
453
                                }
 
454
                        }
 
455
                        if ( allMatched )
 
456
                                return true ;
 
457
                }
 
458
 
 
459
                // Check if the element can be somehow overriden.
 
460
                var override = this._GetOverridesForComparison()[ elementName ] ;
 
461
                if ( override )
 
462
                {
 
463
                        // If no attributes have been defined, remove the element.
 
464
                        if ( !( attribs = override.Attributes ) ) // Only one "="
 
465
                                return true ;
 
466
 
 
467
                        for ( var i = 0 ; i < attribs.length ; i++ )
 
468
                        {
 
469
                                var attName = attribs[i][0] ;
 
470
                                if ( FCKDomTools.HasAttribute( element, attName ) )
 
471
                                {
 
472
                                        var attValue = attribs[i][1] ;
 
473
 
 
474
                                        // Remove the attribute if:
 
475
                                        //    - The override definition value is null ;
 
476
                                        //    - The override definition valie is a string that
 
477
                                        //      matches the attribute value exactly.
 
478
                                        //    - The override definition value is a regex that
 
479
                                        //      has matches in the attribute value.
 
480
                                        if ( attValue == null ||
 
481
                                                        ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) ||
 
482
                                                        attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) )
 
483
                                                return true ;
 
484
                                }
 
485
                        }
 
486
                }
 
487
 
 
488
                return false ;
 
489
        },
 
490
 
 
491
        /**
 
492
         * Get the style state for an element path. Returns "true" if the element
 
493
         * is active in the path.
 
494
         */
 
495
        CheckActive : function( elementPath )
 
496
        {
 
497
                switch ( this.GetType() )
 
498
                {
 
499
                        case FCK_STYLE_BLOCK :
 
500
                                return this.CheckElementRemovable( elementPath.Block || elementPath.BlockLimit, true ) ;
 
501
 
 
502
                        case FCK_STYLE_INLINE :
 
503
 
 
504
                                var elements = elementPath.Elements ;
 
505
 
 
506
                                for ( var i = 0 ; i < elements.length ; i++ )
 
507
                                {
 
508
                                        var element = elements[i] ;
 
509
 
 
510
                                        if ( element == elementPath.Block || element == elementPath.BlockLimit )
 
511
                                                continue ;
 
512
 
 
513
                                        if ( this.CheckElementRemovable( element, true ) )
 
514
                                                return true ;
 
515
                                }
 
516
                }
 
517
                return false ;
 
518
        },
 
519
 
 
520
        /**
 
521
         * Removes an inline style from inside an element tree. The element node
 
522
         * itself is not checked or removed, only the child tree inside of it.
 
523
         */
 
524
        RemoveFromElement : function( element )
 
525
        {
 
526
                var attribs = this._GetAttribsForComparison() ;
 
527
                var overrides = this._GetOverridesForComparison() ;
 
528
 
 
529
                // Get all elements with the same name.
 
530
                var innerElements = element.getElementsByTagName( this.Element ) ;
 
531
 
 
532
                for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
 
533
                {
 
534
                        var innerElement = innerElements[i] ;
 
535
 
 
536
                        // Remove any attribute that conflict with this style, no matter
 
537
                        // their values.
 
538
                        for ( var att in attribs )
 
539
                        {
 
540
                                if ( FCKDomTools.HasAttribute( innerElement, att ) )
 
541
                                {
 
542
                                        switch ( att )
 
543
                                        {
 
544
                                                case 'style' :
 
545
                                                        this._RemoveStylesFromElement( innerElement ) ;
 
546
                                                        break ;
 
547
 
 
548
                                                case 'class' :
 
549
                                                        // The 'class' element value must match (#1318).
 
550
                                                        if ( FCKDomTools.GetAttributeValue( innerElement, att ) != this.GetFinalAttributeValue( att ) )
 
551
                                                                continue ;
 
552
 
 
553
                                                        /*jsl:fallthru*/
 
554
 
 
555
                                                default :
 
556
                                                        FCKDomTools.RemoveAttribute( innerElement, att ) ;
 
557
                                        }
 
558
                                }
 
559
                        }
 
560
 
 
561
                        // Remove overrides defined to the same element name.
 
562
                        this._RemoveOverrides( innerElement, overrides[ this.Element ] ) ;
 
563
 
 
564
                        // Remove the element if no more attributes are available.
 
565
                        this._RemoveNoAttribElement( innerElement ) ;
 
566
                }
 
567
 
 
568
                // Now remove any other element with different name that is
 
569
                // defined to be overriden.
 
570
                for ( var overrideElement in overrides )
 
571
                {
 
572
                        if ( overrideElement != this.Element )
 
573
                        {
 
574
                                // Get all elements.
 
575
                                innerElements = element.getElementsByTagName( overrideElement ) ;
 
576
 
 
577
                                for ( var i = innerElements.length - 1 ; i >= 0 ; i-- )
 
578
                                {
 
579
                                        var innerElement = innerElements[i] ;
 
580
                                        this._RemoveOverrides( innerElement, overrides[ overrideElement ] ) ;
 
581
                                        this._RemoveNoAttribElement( innerElement ) ;
 
582
                                }
 
583
                        }
 
584
                }
 
585
        },
 
586
 
 
587
        _RemoveStylesFromElement : function( element )
 
588
        {
 
589
                var elementStyle = element.style.cssText ;
 
590
                var pattern = this.GetFinalStyleValue() ;
 
591
 
 
592
                if ( elementStyle.length > 0 && pattern.length == 0 )
 
593
                        return ;
 
594
 
 
595
                pattern = '(^|;)\\s*(' +
 
596
                        pattern.replace( /\s*([^ ]+):.*?(;|$)/g, '$1|' ).replace( /\|$/, '' ) +
 
597
                        '):[^;]+' ;
 
598
 
 
599
                var regex = new RegExp( pattern, 'gi' ) ;
 
600
 
 
601
                elementStyle = elementStyle.replace( regex, '' ).Trim() ;
 
602
 
 
603
                if ( elementStyle.length == 0 || elementStyle == ';' )
 
604
                        FCKDomTools.RemoveAttribute( element, 'style' ) ;
 
605
                else
 
606
                        element.style.cssText = elementStyle.replace( regex, '' ) ;
 
607
        },
 
608
 
 
609
        /**
 
610
         * Remove all attributes that are defined to be overriden,
 
611
         */
 
612
        _RemoveOverrides : function( element, override )
 
613
        {
 
614
                var attributes = override && override.Attributes ;
 
615
 
 
616
                if ( attributes )
 
617
                {
 
618
                        for ( var i = 0 ; i < attributes.length ; i++ )
 
619
                        {
 
620
                                var attName = attributes[i][0] ;
 
621
 
 
622
                                if ( FCKDomTools.HasAttribute( element, attName ) )
 
623
                                {
 
624
                                        var attValue    = attributes[i][1] ;
 
625
 
 
626
                                        // Remove the attribute if:
 
627
                                        //    - The override definition value is null ;
 
628
                                        //    - The override definition valie is a string that
 
629
                                        //      matches the attribute value exactly.
 
630
                                        //    - The override definition value is a regex that
 
631
                                        //      has matches in the attribute value.
 
632
                                        if ( attValue == null ||
 
633
                                                        ( attValue.test && attValue.test( FCKDomTools.GetAttributeValue( element, attName ) ) ) ||
 
634
                                                        ( typeof attValue == 'string' && FCKDomTools.GetAttributeValue( element, attName ) == attValue ) )
 
635
                                                FCKDomTools.RemoveAttribute( element, attName ) ;
 
636
                                }
 
637
                        }
 
638
                }
 
639
        },
 
640
 
 
641
        /**
 
642
         * If the element has no more attributes, remove it.
 
643
         */
 
644
        _RemoveNoAttribElement : function( element )
 
645
        {
 
646
                // If no more attributes remained in the element, remove it,
 
647
                // leaving its children.
 
648
                if ( !FCKDomTools.HasAttributes( element ) )
 
649
                {
 
650
                        // Removing elements may open points where merging is possible,
 
651
                        // so let's cache the first and last nodes for later checking.
 
652
                        var firstChild  = element.firstChild ;
 
653
                        var lastChild   = element.lastChild ;
 
654
 
 
655
                        FCKDomTools.RemoveNode( element, true ) ;
 
656
 
 
657
                        // Check the cached nodes for merging.
 
658
                        this._MergeSiblings( firstChild ) ;
 
659
 
 
660
                        if ( firstChild != lastChild )
 
661
                                this._MergeSiblings( lastChild ) ;
 
662
                }
 
663
        },
 
664
 
 
665
        /**
 
666
         * Creates a DOM element for this style object.
 
667
         */
 
668
        BuildElement : function( targetDoc, element )
 
669
        {
 
670
                // Create the element.
 
671
                var el = element || targetDoc.createElement( this.Element ) ;
 
672
 
 
673
                // Assign all defined attributes.
 
674
                var attribs     = this._StyleDesc.Attributes ;
 
675
                var attValue ;
 
676
                if ( attribs )
 
677
                {
 
678
                        for ( var att in attribs )
 
679
                        {
 
680
                                attValue = this.GetFinalAttributeValue( att ) ;
 
681
 
 
682
                                if ( att.toLowerCase() == 'class' )
 
683
                                        el.className = attValue ;
 
684
                                else
 
685
                                        el.setAttribute( att, attValue ) ;
 
686
                        }
 
687
                }
 
688
 
 
689
                // Assign the style attribute.
 
690
                if ( this._GetStyleText().length > 0 )
 
691
                        el.style.cssText = this.GetFinalStyleValue() ;
 
692
 
 
693
                return el ;
 
694
        },
 
695
 
 
696
        _CompareAttributeValues : function( attName, valueA, valueB )
 
697
        {
 
698
                if ( attName == 'style' && valueA && valueB )
 
699
                {
 
700
                        valueA = valueA.replace( /;$/, '' ).toLowerCase() ;
 
701
                        valueB = valueB.replace( /;$/, '' ).toLowerCase() ;
 
702
                }
 
703
 
 
704
                // Return true if they match or if valueA is null and valueB is an empty string
 
705
                return ( valueA == valueB || ( ( valueA === null || valueA === '' ) && ( valueB === null || valueB === '' ) ) )
 
706
        },
 
707
 
 
708
        GetFinalAttributeValue : function( attName )
 
709
        {
 
710
                var attValue = this._StyleDesc.Attributes ;
 
711
                var attValue = attValue ? attValue[ attName ] : null ;
 
712
 
 
713
                if ( !attValue && attName == 'style' )
 
714
                        return this.GetFinalStyleValue() ;
 
715
 
 
716
                if ( attValue && this._Variables )
 
717
                        // Using custom Replace() to guarantee the correct scope.
 
718
                        attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
 
719
 
 
720
                return attValue ;
 
721
        },
 
722
 
 
723
        GetFinalStyleValue : function()
 
724
        {
 
725
                var attValue = this._GetStyleText() ;
 
726
 
 
727
                if ( attValue.length > 0 && this._Variables )
 
728
                {
 
729
                        // Using custom Replace() to guarantee the correct scope.
 
730
                        attValue = attValue.Replace( FCKRegexLib.StyleVariableAttName, this._GetVariableReplace, this ) ;
 
731
                        attValue = FCKTools.NormalizeCssText( attValue ) ;
 
732
                }
 
733
 
 
734
                return attValue ;
 
735
        },
 
736
 
 
737
        _GetVariableReplace : function()
 
738
        {
 
739
                // The second group in the regex is the variable name.
 
740
                return this._Variables[ arguments[2] ] || arguments[0] ;
 
741
        },
 
742
 
 
743
        /**
 
744
         * Set the value of a variable attribute or style, to be used when
 
745
         * appliying the style.
 
746
         */
 
747
        SetVariable : function( name, value )
 
748
        {
 
749
                var variables = this._Variables ;
 
750
 
 
751
                if ( !variables )
 
752
                        variables = this._Variables = {} ;
 
753
 
 
754
                this._Variables[ name ] = value ;
 
755
        },
 
756
 
 
757
        /**
 
758
         * Converting from a PRE block to a non-PRE block in formatting operations.
 
759
         */
 
760
        _FromPre : function( doc, block, newBlock )
 
761
        {
 
762
                var innerHTML = block.innerHTML ;
 
763
 
 
764
                // Trim the first and last linebreaks immediately after and before <pre>, </pre>,
 
765
                // if they exist.
 
766
                // This is done because the linebreaks are not rendered.
 
767
                innerHTML = innerHTML.replace( /(\r\n|\r)/g, '\n' ) ;
 
768
                innerHTML = innerHTML.replace( /^[ \t]*\n/, '' ) ;
 
769
                innerHTML = innerHTML.replace( /\n$/, '' ) ;
 
770
 
 
771
                // 1. Convert spaces or tabs at the beginning or at the end to &nbsp;
 
772
                innerHTML = innerHTML.replace( /^[ \t]+|[ \t]+$/g, function( match, offset, s )
 
773
                                {
 
774
                                        if ( match.length == 1 )        // one space, preserve it
 
775
                                                return '&nbsp;' ;
 
776
                                        else if ( offset == 0 )         // beginning of block
 
777
                                                return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
 
778
                                        else                            // end of block
 
779
                                                return ' ' + new Array( match.length ).join( '&nbsp;' ) ;
 
780
                                } ) ;
 
781
 
 
782
                // 2. Convert \n to <BR>.
 
783
                // 3. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
 
784
                var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
 
785
                var results = [] ;
 
786
                htmlIterator.Each( function( isTag, value )
 
787
                        {
 
788
                                if ( !isTag )
 
789
                                {
 
790
                                        value = value.replace( /\n/g, '<br>' ) ;
 
791
                                        value = value.replace( /[ \t]{2,}/g,
 
792
                                                        function ( match )
 
793
                                                        {
 
794
                                                                return new Array( match.length ).join( '&nbsp;' ) + ' ' ;
 
795
                                                        } ) ;
 
796
                                }
 
797
                                results.push( value ) ;
 
798
                        } ) ;
 
799
                newBlock.innerHTML = results.join( '' ) ;
 
800
                return newBlock ;
 
801
        },
 
802
 
 
803
        /**
 
804
         * Converting from a non-PRE block to a PRE block in formatting operations.
 
805
         */
 
806
        _ToPre : function( doc, block, newBlock )
 
807
        {
 
808
                // Handle converting from a regular block to a <pre> block.
 
809
                var innerHTML = block.innerHTML.Trim() ;
 
810
 
 
811
                // 1. Delete ANSI whitespaces immediately before and after <BR> because
 
812
                //    they are not visible.
 
813
                // 2. Mark down any <BR /> nodes here so they can be turned into \n in
 
814
                //    the next step and avoid being compressed.
 
815
                innerHTML = innerHTML.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '<br />' ) ;
 
816
 
 
817
                // 3. Compress other ANSI whitespaces since they're only visible as one
 
818
                //    single space previously.
 
819
                // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
 
820
                // 5. Convert any <BR /> to \n. This must not be done earlier because
 
821
                //    the \n would then get compressed.
 
822
                var htmlIterator = new FCKHtmlIterator( innerHTML ) ;
 
823
                var results = [] ;
 
824
                htmlIterator.Each( function( isTag, value )
 
825
                        {
 
826
                                if ( !isTag )
 
827
                                        value = value.replace( /([ \t\n\r]+|&nbsp;)/g, ' ' ) ;
 
828
                                else if ( isTag && value == '<br />' )
 
829
                                        value = '\n' ;
 
830
                                results.push( value ) ;
 
831
                        } ) ;
 
832
 
 
833
                // Assigning innerHTML to <PRE> in IE causes all linebreaks to be
 
834
                // reduced to spaces.
 
835
                // Assigning outerHTML to <PRE> in IE doesn't work if the <PRE> isn't
 
836
                // contained in another node since the node reference is changed after
 
837
                // outerHTML assignment.
 
838
                // So, we need some hacks to workaround IE bugs here.
 
839
                if ( FCKBrowserInfo.IsIE )
 
840
                {
 
841
                        var temp = doc.createElement( 'div' ) ;
 
842
                        temp.appendChild( newBlock ) ;
 
843
                        newBlock.outerHTML = '<pre>\n' + results.join( '' ) + '</pre>' ;
 
844
                        newBlock = temp.removeChild( temp.firstChild ) ;
 
845
                }
 
846
                else
 
847
                        newBlock.innerHTML = results.join( '' ) ;
 
848
 
 
849
                return newBlock ;
 
850
        },
 
851
 
 
852
        /**
 
853
         * Merge a <pre> block with a previous <pre> block, if available.
 
854
         */
 
855
        _CheckAndMergePre : function( previousBlock, preBlock )
 
856
        {
 
857
                // Check if the previous block and the current block are next
 
858
                // to each other.
 
859
                if ( previousBlock != FCKDomTools.GetPreviousSourceElement( preBlock, true ) )
 
860
                        return ;
 
861
 
 
862
                // Merge the previous <pre> block contents into the current <pre>
 
863
                // block.
 
864
                //
 
865
                // Another thing to be careful here is that currentBlock might contain
 
866
                // a '\n' at the beginning, and previousBlock might contain a '\n'
 
867
                // towards the end. These new lines are not normally displayed but they
 
868
                // become visible after merging.
 
869
                var innerHTML = previousBlock.innerHTML.replace( /\n$/, '' ) + '\n\n' +
 
870
                                preBlock.innerHTML.replace( /^\n/, '' ) ;
 
871
 
 
872
                // Buggy IE normalizes innerHTML from <pre>, breaking whitespaces.
 
873
                if ( FCKBrowserInfo.IsIE )
 
874
                        preBlock.outerHTML = '<pre>' + innerHTML + '</pre>' ;
 
875
                else
 
876
                        preBlock.innerHTML = innerHTML ;
 
877
 
 
878
                // Remove the previous <pre> block.
 
879
                //
 
880
                // The preBlock must not be moved or deleted from the DOM tree. This
 
881
                // guarantees the FCKDomRangeIterator in _ApplyBlockStyle would not
 
882
                // get lost at the next iteration.
 
883
                FCKDomTools.RemoveNode( previousBlock ) ;
 
884
        },
 
885
 
 
886
        _CheckAndSplitPre : function( newBlock )
 
887
        {
 
888
                var lastNewBlock ;
 
889
 
 
890
                var cursor = newBlock.firstChild ;
 
891
 
 
892
                // We are not splitting <br><br> at the beginning of the block, so
 
893
                // we'll start from the second child.
 
894
                cursor = cursor && cursor.nextSibling ;
 
895
 
 
896
                while ( cursor )
 
897
                {
 
898
                        var next = cursor.nextSibling ;
 
899
 
 
900
                        // If we have two <BR>s, and they're not at the beginning or the end,
 
901
                        // then we'll split up the contents following them into another block.
 
902
                        // Stop processing if we are at the last child couple.
 
903
                        if ( next && next.nextSibling && cursor.nodeName.IEquals( 'br' ) && next.nodeName.IEquals( 'br' ) )
 
904
                        {
 
905
                                // Remove the first <br>.
 
906
                                FCKDomTools.RemoveNode( cursor ) ;
 
907
 
 
908
                                // Move to the node after the second <br>.
 
909
                                cursor = next.nextSibling ;
 
910
 
 
911
                                // Remove the second <br>.
 
912
                                FCKDomTools.RemoveNode( next ) ;
 
913
 
 
914
                                // Create the block that will hold the child nodes from now on.
 
915
                                lastNewBlock = FCKDomTools.InsertAfterNode( lastNewBlock || newBlock, FCKDomTools.CloneElement( newBlock ) ) ;
 
916
 
 
917
                                continue ;
 
918
                        }
 
919
 
 
920
                        // If we split it, then start moving the nodes to the new block.
 
921
                        if ( lastNewBlock )
 
922
                        {
 
923
                                cursor = cursor.previousSibling ;
 
924
                                FCKDomTools.MoveNode(cursor.nextSibling, lastNewBlock ) ;
 
925
                        }
 
926
 
 
927
                        cursor = cursor.nextSibling ;
 
928
                }
 
929
        },
 
930
 
 
931
        /**
 
932
         * Apply an inline style to a FCKDomRange.
 
933
         *
 
934
         * TODO
 
935
         *      - Implement the "#" style handling.
 
936
         *      - Properly handle block containers like <div> and <blockquote>.
 
937
         */
 
938
        _ApplyBlockStyle : function( range, selectIt, updateRange )
 
939
        {
 
940
                // Bookmark the range so we can re-select it after processing.
 
941
                var bookmark ;
 
942
 
 
943
                if ( selectIt )
 
944
                        bookmark = range.CreateBookmark() ;
 
945
 
 
946
                var iterator = new FCKDomRangeIterator( range ) ;
 
947
                iterator.EnforceRealBlocks = true ;
 
948
 
 
949
                var block ;
 
950
                var doc = range.Window.document ;
 
951
                var previousPreBlock ;
 
952
 
 
953
                while( ( block = iterator.GetNextParagraph() ) )                // Only one =
 
954
                {
 
955
                        // Create the new node right before the current one.
 
956
                        var newBlock = this.BuildElement( doc ) ;
 
957
 
 
958
                        // Check if we are changing from/to <pre>.
 
959
                        var newBlockIsPre       = newBlock.nodeName.IEquals( 'pre' ) ;
 
960
                        var blockIsPre          = block.nodeName.IEquals( 'pre' ) ;
 
961
 
 
962
                        var toPre       = newBlockIsPre && !blockIsPre ;
 
963
                        var fromPre     = !newBlockIsPre && blockIsPre ;
 
964
 
 
965
                        // Move everything from the current node to the new one.
 
966
                        if ( toPre )
 
967
                                newBlock = this._ToPre( doc, block, newBlock ) ;
 
968
                        else if ( fromPre )
 
969
                                newBlock = this._FromPre( doc, block, newBlock ) ;
 
970
                        else    // Convering from a regular block to another regular block.
 
971
                                FCKDomTools.MoveChildren( block, newBlock ) ;
 
972
 
 
973
                        // Replace the current block.
 
974
                        block.parentNode.insertBefore( newBlock, block ) ;
 
975
                        FCKDomTools.RemoveNode( block ) ;
 
976
 
 
977
                        // Complete other tasks after inserting the node in the DOM.
 
978
                        if ( newBlockIsPre )
 
979
                        {
 
980
                                if ( previousPreBlock )
 
981
                                        this._CheckAndMergePre( previousPreBlock, newBlock ) ;  // Merge successive <pre> blocks.
 
982
                                previousPreBlock = newBlock ;
 
983
                        }
 
984
                        else if ( fromPre )
 
985
                                this._CheckAndSplitPre( newBlock ) ;    // Split <br><br> in successive <pre>s.
 
986
                }
 
987
 
 
988
                // Re-select the original range.
 
989
                if ( selectIt )
 
990
                        range.SelectBookmark( bookmark ) ;
 
991
 
 
992
                if ( updateRange )
 
993
                        range.MoveToBookmark( bookmark ) ;
 
994
        },
 
995
 
 
996
        /**
 
997
         * Apply an inline style to a FCKDomRange.
 
998
         *
 
999
         * TODO
 
1000
         *      - Merge elements, when applying styles to similar elements that enclose
 
1001
         *    the entire selection, outputing:
 
1002
         *        <span style="color: #ff0000; background-color: #ffffff">XYZ</span>
 
1003
         *    instead of:
 
1004
         *        <span style="color: #ff0000;"><span style="background-color: #ffffff">XYZ</span></span>
 
1005
         */
 
1006
        _ApplyInlineStyle : function( range, selectIt, updateRange )
 
1007
        {
 
1008
                var doc = range.Window.document ;
 
1009
 
 
1010
                if ( range.CheckIsCollapsed() )
 
1011
                {
 
1012
                        // Create the element to be inserted in the DOM.
 
1013
                        var collapsedElement = this.BuildElement( doc ) ;
 
1014
                        range.InsertNode( collapsedElement ) ;
 
1015
                        range.MoveToPosition( collapsedElement, 2 ) ;
 
1016
                        range.Select() ;
 
1017
 
 
1018
                        return ;
 
1019
                }
 
1020
 
 
1021
                // The general idea here is navigating through all nodes inside the
 
1022
                // current selection, working on distinct range blocks, defined by the
 
1023
                // DTD compatibility between the style element and the nodes inside the
 
1024
                // ranges.
 
1025
                //
 
1026
                // For example, suppose we have the following selection (where [ and ]
 
1027
                // are the boundaries), and we apply a <b> style there:
 
1028
                //
 
1029
                //              <p>Here we [have <b>some</b> text.<p>
 
1030
                //              <p>And some here] here.</p>
 
1031
                //
 
1032
                // Two different ranges will be detected:
 
1033
                //
 
1034
                //              "have <b>some</b> text."
 
1035
                //              "And some here"
 
1036
                //
 
1037
                // Both ranges will be extracted, moved to a <b> element, and
 
1038
                // re-inserted, resulting in the following output:
 
1039
                //
 
1040
                //              <p>Here we [<b>have some text.</b><p>
 
1041
                //              <p><b>And some here</b>] here.</p>
 
1042
                //
 
1043
                // Note that the <b> element at <b>some</b> is also removed because it
 
1044
                // is not needed anymore.
 
1045
 
 
1046
                var elementName = this.Element ;
 
1047
 
 
1048
                // Get the DTD definition for the element. Defaults to "span".
 
1049
                var elementDTD = FCK.DTD[ elementName ] || FCK.DTD.span ;
 
1050
 
 
1051
                // Create the attribute list to be used later for element comparisons.
 
1052
                var styleAttribs = this._GetAttribsForComparison() ;
 
1053
                var styleNode ;
 
1054
 
 
1055
                // Expand the range, if inside inline element boundaries.
 
1056
                range.Expand( 'inline_elements' ) ;
 
1057
 
 
1058
                // Bookmark the range so we can re-select it after processing.
 
1059
                var bookmark = range.CreateBookmark( true ) ;
 
1060
 
 
1061
                // The style will be applied within the bookmark boundaries.
 
1062
                var startNode   = range.GetBookmarkNode( bookmark, true ) ;
 
1063
                var endNode             = range.GetBookmarkNode( bookmark, false ) ;
 
1064
 
 
1065
                // We'll be reusing the range to apply the styles. So, release it here
 
1066
                // to indicate that it has not been initialized.
 
1067
                range.Release( true ) ;
 
1068
 
 
1069
                // Let's start the nodes lookup from the node right after the bookmark
 
1070
                // span.
 
1071
                var currentNode = FCKDomTools.GetNextSourceNode( startNode, true ) ;
 
1072
 
 
1073
                while ( currentNode )
 
1074
                {
 
1075
                        var applyStyle = false ;
 
1076
 
 
1077
                        var nodeType = currentNode.nodeType ;
 
1078
                        var nodeName = nodeType == 1 ? currentNode.nodeName.toLowerCase() : null ;
 
1079
 
 
1080
                        // Check if the current node can be a child of the style element.
 
1081
                        if ( !nodeName || elementDTD[ nodeName ] )
 
1082
                        {
 
1083
                                // Check if the style element can be a child of the current
 
1084
                                // node parent or if the element is not defined in the DTD.
 
1085
                                if ( ( FCK.DTD[ currentNode.parentNode.nodeName.toLowerCase() ] || FCK.DTD.span )[ elementName ] || !FCK.DTD[ elementName ] )
 
1086
                                {
 
1087
                                        // This node will be part of our range, so if it has not
 
1088
                                        // been started, place its start right before the node.
 
1089
                                        if ( !range.CheckHasRange() )
 
1090
                                                range.SetStart( currentNode, 3 ) ;
 
1091
 
 
1092
                                        // Non element nodes, or empty elements can be added
 
1093
                                        // completely to the range.
 
1094
                                        if ( nodeType != 1 || currentNode.childNodes.length == 0 )
 
1095
                                        {
 
1096
                                                var includedNode = currentNode ;
 
1097
                                                var parentNode = includedNode.parentNode ;
 
1098
 
 
1099
                                                // This node is about to be included completelly, but,
 
1100
                                                // if this is the last node in its parent, we must also
 
1101
                                                // check if the parent itself can be added completelly
 
1102
                                                // to the range.
 
1103
                                                while ( includedNode == parentNode.lastChild
 
1104
                                                        && elementDTD[ parentNode.nodeName.toLowerCase() ] )
 
1105
                                                {
 
1106
                                                        includedNode = parentNode ;
 
1107
                                                }
 
1108
 
 
1109
                                                range.SetEnd( includedNode, 4 ) ;
 
1110
 
 
1111
                                                // If the included node is the last node in its parent
 
1112
                                                // and its parent can't be inside the style node, apply
 
1113
                                                // the style immediately.
 
1114
                                                if ( includedNode == includedNode.parentNode.lastChild && !elementDTD[ includedNode.parentNode.nodeName.toLowerCase() ] )
 
1115
                                                        applyStyle = true ;
 
1116
                                        }
 
1117
                                        else
 
1118
                                        {
 
1119
                                                // Element nodes will not be added directly. We need to
 
1120
                                                // check their children because the selection could end
 
1121
                                                // inside the node, so let's place the range end right
 
1122
                                                // before the element.
 
1123
                                                range.SetEnd( currentNode, 3 ) ;
 
1124
                                        }
 
1125
                                }
 
1126
                                else
 
1127
                                        applyStyle = true ;
 
1128
                        }
 
1129
                        else
 
1130
                                applyStyle = true ;
 
1131
 
 
1132
                        // Get the next node to be processed.
 
1133
                        currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
 
1134
 
 
1135
                        // If we have reached the end of the selection, just apply the
 
1136
                        // style ot the range, and stop looping.
 
1137
                        if ( currentNode == endNode )
 
1138
                        {
 
1139
                                currentNode = null ;
 
1140
                                applyStyle = true ;
 
1141
                        }
 
1142
 
 
1143
                        // Apply the style if we have something to which apply it.
 
1144
                        if ( applyStyle && range.CheckHasRange() && !range.CheckIsCollapsed() )
 
1145
                        {
 
1146
                                // Build the style element, based on the style object definition.
 
1147
                                styleNode = this.BuildElement( doc ) ;
 
1148
 
 
1149
                                // Move the contents of the range to the style element.
 
1150
                                range.ExtractContents().AppendTo( styleNode ) ;
 
1151
 
 
1152
                                // If it is not empty.
 
1153
                                if ( styleNode.innerHTML.RTrim().length > 0 )
 
1154
                                {
 
1155
                                        // Insert it in the range position (it is collapsed after
 
1156
                                        // ExtractContents.
 
1157
                                        range.InsertNode( styleNode ) ;
 
1158
 
 
1159
                                        // Here we do some cleanup, removing all duplicated
 
1160
                                        // elements from the style element.
 
1161
                                        this.RemoveFromElement( styleNode ) ;
 
1162
 
 
1163
                                        // Let's merge our new style with its neighbors, if possible.
 
1164
                                        this._MergeSiblings( styleNode, this._GetAttribsForComparison() ) ;
 
1165
 
 
1166
                                        // As the style system breaks text nodes constantly, let's normalize
 
1167
                                        // things for performance.
 
1168
                                        // With IE, some paragraphs get broken when calling normalize()
 
1169
                                        // repeatedly. Also, for IE, we must normalize body, not documentElement.
 
1170
                                        // IE is also known for having a "crash effect" with normalize().
 
1171
                                        // We should try to normalize with IE too in some way, somewhere.
 
1172
                                        if ( !FCKBrowserInfo.IsIE )
 
1173
                                                styleNode.normalize() ;
 
1174
                                }
 
1175
 
 
1176
                                // Style applied, let's release the range, so it gets marked to
 
1177
                                // re-initialization in the next loop.
 
1178
                                range.Release( true ) ;
 
1179
                        }
 
1180
                }
 
1181
 
 
1182
                this._FixBookmarkStart( startNode ) ;
 
1183
 
 
1184
                // Re-select the original range.
 
1185
                if ( selectIt )
 
1186
                        range.SelectBookmark( bookmark ) ;
 
1187
 
 
1188
                if ( updateRange )
 
1189
                        range.MoveToBookmark( bookmark ) ;
 
1190
        },
 
1191
 
 
1192
        _FixBookmarkStart : function( startNode )
 
1193
        {
 
1194
                // After appliying or removing an inline style, the start boundary of
 
1195
                // the selection must be placed inside all inline elements it is
 
1196
                // bordering.
 
1197
                var startSibling ;
 
1198
                while ( ( startSibling = startNode.nextSibling ) )      // Only one "=".
 
1199
                {
 
1200
                        if ( startSibling.nodeType == 1
 
1201
                                && FCKListsLib.InlineNonEmptyElements[ startSibling.nodeName.toLowerCase() ] )
 
1202
                        {
 
1203
                                // If it is an empty inline element, we can safely remove it.
 
1204
                                if ( !startSibling.firstChild )
 
1205
                                        FCKDomTools.RemoveNode( startSibling ) ;
 
1206
                                else
 
1207
                                        FCKDomTools.MoveNode( startNode, startSibling, true ) ;
 
1208
                                continue ;
 
1209
                        }
 
1210
 
 
1211
                        // Empty text nodes can be safely removed to not disturb.
 
1212
                        if ( startSibling.nodeType == 3 && startSibling.length == 0 )
 
1213
                        {
 
1214
                                FCKDomTools.RemoveNode( startSibling ) ;
 
1215
                                continue ;
 
1216
                        }
 
1217
 
 
1218
                        break ;
 
1219
                }
 
1220
        },
 
1221
 
 
1222
        /**
 
1223
         * Merge an element with its similar siblings.
 
1224
         * "attribs" is and object computed with _CreateAttribsForComparison.
 
1225
         */
 
1226
        _MergeSiblings : function( element, attribs )
 
1227
        {
 
1228
                if ( !element || element.nodeType != 1 || !FCKListsLib.InlineNonEmptyElements[ element.nodeName.toLowerCase() ] )
 
1229
                        return ;
 
1230
 
 
1231
                this._MergeNextSibling( element, attribs ) ;
 
1232
                this._MergePreviousSibling( element, attribs ) ;
 
1233
        },
 
1234
 
 
1235
        /**
 
1236
         * Merge an element with its similar siblings after it.
 
1237
         * "attribs" is and object computed with _CreateAttribsForComparison.
 
1238
         */
 
1239
        _MergeNextSibling : function( element, attribs )
 
1240
        {
 
1241
                // Check the next sibling.
 
1242
                var sibling = element.nextSibling ;
 
1243
 
 
1244
                // Check if the next sibling is a bookmark element. In this case, jump it.
 
1245
                var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
 
1246
                if ( hasBookmark )
 
1247
                        sibling = sibling.nextSibling ;
 
1248
 
 
1249
                if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
 
1250
                {
 
1251
                        if ( !attribs )
 
1252
                                attribs = this._CreateElementAttribsForComparison( element ) ;
 
1253
 
 
1254
                        if ( this._CheckAttributesMatch( sibling, attribs ) )
 
1255
                        {
 
1256
                                // Save the last child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
 
1257
                                var innerSibling = element.lastChild ;
 
1258
 
 
1259
                                if ( hasBookmark )
 
1260
                                        FCKDomTools.MoveNode( element.nextSibling, element ) ;
 
1261
 
 
1262
                                // Move contents from the sibling.
 
1263
                                FCKDomTools.MoveChildren( sibling, element ) ;
 
1264
                                FCKDomTools.RemoveNode( sibling ) ;
 
1265
 
 
1266
                                // Now check the last inner child (see two comments above).
 
1267
                                if ( innerSibling )
 
1268
                                        this._MergeNextSibling( innerSibling ) ;
 
1269
                        }
 
1270
                }
 
1271
        },
 
1272
 
 
1273
        /**
 
1274
         * Merge an element with its similar siblings before it.
 
1275
         * "attribs" is and object computed with _CreateAttribsForComparison.
 
1276
         */
 
1277
        _MergePreviousSibling : function( element, attribs )
 
1278
        {
 
1279
                // Check the previous sibling.
 
1280
                var sibling = element.previousSibling ;
 
1281
 
 
1282
                // Check if the previous sibling is a bookmark element. In this case, jump it.
 
1283
                var hasBookmark = ( sibling && sibling.nodeType == 1 && sibling.getAttribute( '_fck_bookmark' ) ) ;
 
1284
                if ( hasBookmark )
 
1285
                        sibling = sibling.previousSibling ;
 
1286
 
 
1287
                if ( sibling && sibling.nodeType == 1 && sibling.nodeName == element.nodeName )
 
1288
                {
 
1289
                        if ( !attribs )
 
1290
                                attribs = this._CreateElementAttribsForComparison( element ) ;
 
1291
 
 
1292
                        if ( this._CheckAttributesMatch( sibling, attribs ) )
 
1293
                        {
 
1294
                                // Save the first child to be checked too (to merge things like <b><i></i></b><b><i></i></b>).
 
1295
                                var innerSibling = element.firstChild ;
 
1296
 
 
1297
                                if ( hasBookmark )
 
1298
                                        FCKDomTools.MoveNode( element.previousSibling, element, true ) ;
 
1299
 
 
1300
                                // Move contents to the sibling.
 
1301
                                FCKDomTools.MoveChildren( sibling, element, true ) ;
 
1302
                                FCKDomTools.RemoveNode( sibling ) ;
 
1303
 
 
1304
                                // Now check the first inner child (see two comments above).
 
1305
                                if ( innerSibling )
 
1306
                                        this._MergePreviousSibling( innerSibling ) ;
 
1307
                        }
 
1308
                }
 
1309
        },
 
1310
 
 
1311
        /**
 
1312
         * Build the cssText based on the styles definition.
 
1313
         */
 
1314
        _GetStyleText : function()
 
1315
        {
 
1316
                var stylesDef = this._StyleDesc.Styles ;
 
1317
 
 
1318
                // Builds the StyleText.
 
1319
                var stylesText = ( this._StyleDesc.Attributes ? this._StyleDesc.Attributes['style'] || '' : '' ) ;
 
1320
 
 
1321
                if ( stylesText.length > 0 )
 
1322
                        stylesText += ';' ;
 
1323
 
 
1324
                for ( var style in stylesDef )
 
1325
                        stylesText += style + ':' + stylesDef[style] + ';' ;
 
1326
 
 
1327
                // Browsers make some changes to the style when applying them. So, here
 
1328
                // we normalize it to the browser format. We'll not do that if there
 
1329
                // are variables inside the style.
 
1330
                if ( stylesText.length > 0 && !( /#\(/.test( stylesText ) ) )
 
1331
                {
 
1332
                        stylesText = FCKTools.NormalizeCssText( stylesText ) ;
 
1333
                }
 
1334
 
 
1335
                return (this._GetStyleText = function() { return stylesText ; })() ;
 
1336
        },
 
1337
 
 
1338
        /**
 
1339
         * Get the the collection used to compare the attributes defined in this
 
1340
         * style with attributes in an element. All information in it is lowercased.
 
1341
         */
 
1342
        _GetAttribsForComparison : function()
 
1343
        {
 
1344
                // If we have already computed it, just return it.
 
1345
                var attribs = this._GetAttribsForComparison_$ ;
 
1346
                if ( attribs )
 
1347
                        return attribs ;
 
1348
 
 
1349
                attribs = new Object() ;
 
1350
 
 
1351
                // Loop through all defined attributes.
 
1352
                var styleAttribs = this._StyleDesc.Attributes ;
 
1353
                if ( styleAttribs )
 
1354
                {
 
1355
                        for ( var styleAtt in styleAttribs )
 
1356
                        {
 
1357
                                attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase() ;
 
1358
                        }
 
1359
                }
 
1360
 
 
1361
                // Includes the style definitions.
 
1362
                if ( this._GetStyleText().length > 0 )
 
1363
                {
 
1364
                        attribs['style'] = this._GetStyleText().toLowerCase() ;
 
1365
                }
 
1366
 
 
1367
                // Appends the "length" information to the object.
 
1368
                FCKTools.AppendLengthProperty( attribs, '_length' ) ;
 
1369
 
 
1370
                // Return it, saving it to the next request.
 
1371
                return ( this._GetAttribsForComparison_$ = attribs ) ;
 
1372
        },
 
1373
 
 
1374
        /**
 
1375
         * Get the the collection used to compare the elements and attributes,
 
1376
         * defined in this style overrides, with other element. All information in
 
1377
         * it is lowercased.
 
1378
         */
 
1379
        _GetOverridesForComparison : function()
 
1380
        {
 
1381
                // If we have already computed it, just return it.
 
1382
                var overrides = this._GetOverridesForComparison_$ ;
 
1383
                if ( overrides )
 
1384
                        return overrides ;
 
1385
 
 
1386
                overrides = new Object() ;
 
1387
 
 
1388
                var overridesDesc = this._StyleDesc.Overrides ;
 
1389
 
 
1390
                if ( overridesDesc )
 
1391
                {
 
1392
                        // The override description can be a string, object or array.
 
1393
                        // Internally, well handle arrays only, so transform it if needed.
 
1394
                        if ( !FCKTools.IsArray( overridesDesc ) )
 
1395
                                overridesDesc = [ overridesDesc ] ;
 
1396
 
 
1397
                        // Loop through all override definitions.
 
1398
                        for ( var i = 0 ; i < overridesDesc.length ; i++ )
 
1399
                        {
 
1400
                                var override = overridesDesc[i] ;
 
1401
                                var elementName ;
 
1402
                                var overrideEl ;
 
1403
                                var attrs ;
 
1404
 
 
1405
                                // If can be a string with the element name.
 
1406
                                if ( typeof override == 'string' )
 
1407
                                        elementName = override.toLowerCase() ;
 
1408
                                // Or an object.
 
1409
                                else
 
1410
                                {
 
1411
                                        elementName = override.Element ? override.Element.toLowerCase() : this.Element ;
 
1412
                                        attrs = override.Attributes ;
 
1413
                                }
 
1414
 
 
1415
                                // We can have more than one override definition for the same
 
1416
                                // element name, so we attempt to simply append information to
 
1417
                                // it if it already exists.
 
1418
                                overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} ) ;
 
1419
 
 
1420
                                if ( attrs )
 
1421
                                {
 
1422
                                        // The returning attributes list is an array, because we
 
1423
                                        // could have different override definitions for the same
 
1424
                                        // attribute name.
 
1425
                                        var overrideAttrs = ( overrideEl.Attributes = overrideEl.Attributes || new Array() ) ;
 
1426
                                        for ( var attName in attrs )
 
1427
                                        {
 
1428
                                                // Each item in the attributes array is also an array,
 
1429
                                                // where [0] is the attribute name and [1] is the
 
1430
                                                // override value.
 
1431
                                                overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] ) ;
 
1432
                                        }
 
1433
                                }
 
1434
                        }
 
1435
                }
 
1436
 
 
1437
                return ( this._GetOverridesForComparison_$ = overrides ) ;
 
1438
        },
 
1439
 
 
1440
        /*
 
1441
         * Create and object containing all attributes specified in an element,
 
1442
         * added by a "_length" property. All values are lowercased.
 
1443
         */
 
1444
        _CreateElementAttribsForComparison : function( element )
 
1445
        {
 
1446
                var attribs = new Object() ;
 
1447
                var attribsCount = 0 ;
 
1448
 
 
1449
                for ( var i = 0 ; i < element.attributes.length ; i++ )
 
1450
                {
 
1451
                        var att = element.attributes[i] ;
 
1452
 
 
1453
                        if ( att.specified )
 
1454
                        {
 
1455
                                attribs[ att.nodeName.toLowerCase() ] = FCKDomTools.GetAttributeValue( element, att ).toLowerCase() ;
 
1456
                                attribsCount++ ;
 
1457
                        }
 
1458
                }
 
1459
 
 
1460
                attribs._length = attribsCount ;
 
1461
 
 
1462
                return attribs ;
 
1463
        },
 
1464
 
 
1465
        /**
 
1466
         * Checks is the element attributes have a perfect match with the style
 
1467
         * attributes.
 
1468
         */
 
1469
        _CheckAttributesMatch : function( element, styleAttribs )
 
1470
        {
 
1471
                // Loop through all specified attributes. The same number of
 
1472
                // attributes must be found and their values must match to
 
1473
                // declare them as equal.
 
1474
 
 
1475
                var elementAttrbs = element.attributes ;
 
1476
                var matchCount = 0 ;
 
1477
 
 
1478
                for ( var i = 0 ; i < elementAttrbs.length ; i++ )
 
1479
                {
 
1480
                        var att = elementAttrbs[i] ;
 
1481
                        if ( att.specified )
 
1482
                        {
 
1483
                                var attName = att.nodeName.toLowerCase() ;
 
1484
                                var styleAtt = styleAttribs[ attName ] ;
 
1485
 
 
1486
                                // The attribute is not defined in the style.
 
1487
                                if ( !styleAtt )
 
1488
                                        break ;
 
1489
 
 
1490
                                // The values are different.
 
1491
                                if ( styleAtt != FCKDomTools.GetAttributeValue( element, att ).toLowerCase() )
 
1492
                                        break ;
 
1493
 
 
1494
                                matchCount++ ;
 
1495
                        }
 
1496
                }
 
1497
 
 
1498
                return ( matchCount == styleAttribs._length ) ;
 
1499
        }
 
1500
} ;