PluginProbe
Wp-Insert / 1.7.2
Wp-Insert v1.7.2
trunk 1.0 1.1 1.2 1.2.1 1.2.2 1.3.0 1.3.1 1.3.3 1.4 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 2.0 All 63 releases
wp-insert / fckeditor / editor / _source / classes / fckstyle.js

fckstyle.js in Wp-Insert 1.7.2, at fckeditor/editor/_source/classes/fckstyle.js

1,501 lines 45.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 } ;
1501