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