LBF custom template (nation notes) fancybox replace.
[openemr.git] / library / custom_template / ckeditor / _source / plugins / styles / plugin.js
blob7c5e19af9143b55052defd349126c786d0843455
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
6 CKEDITOR.plugins.add( 'styles',
8         requires : [ 'selection' ],
9         init : function( editor )
10         {
11                 // This doesn't look like correct, but it's the safest way to proper
12                 // pass the disableReadonlyStyling configuration to the style system
13                 // without having to change any method signature in the API. (#6103)
14                 editor.on( 'contentDom', function()
15                         {
16                                 editor.document.setCustomData( 'cke_includeReadonly', !editor.config.disableReadonlyStyling );
17                         });
18         }
19 });
21 /**
22  * Registers a function to be called whenever a style changes its state in the
23  * editing area. The current state is passed to the function. The possible
24  * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.
25  * @param {CKEDITOR.style} style The style to be watched.
26  * @param {Function} callback The function to be called when the style state changes.
27  * @example
28  * // Create a style object for the <b> element.
29  * var style = new CKEDITOR.style( { element : 'b' } );
30  * var editor = CKEDITOR.instances.editor1;
31  * editor.attachStyleStateChange( style, function( state )
32  *     {
33  *         if ( state == CKEDITOR.TRISTATE_ON )
34  *             alert( 'The current state for the B element is ON' );
35  *         else
36  *             alert( 'The current state for the B element is OFF' );
37  *     });
38  */
39 CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback )
41         // Try to get the list of attached callbacks.
42         var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
44         // If it doesn't exist, it means this is the first call. So, let's create
45         // all the structure to manage the style checks and the callback calls.
46         if ( !styleStateChangeCallbacks )
47         {
48                 // Create the callbacks array.
49                 styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
51                 // Attach to the selectionChange event, so we can check the styles at
52                 // that point.
53                 this.on( 'selectionChange', function( ev )
54                         {
55                                 // Loop throw all registered callbacks.
56                                 for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
57                                 {
58                                         var callback = styleStateChangeCallbacks[ i ];
60                                         // Check the current state for the style defined for that
61                                         // callback.
62                                         var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
64                                         // If the state changed since the last check.
65                                         if ( callback.state !== currentState )
66                                         {
67                                                 // Call the callback function, passing the current
68                                                 // state to it.
69                                                 callback.fn.call( this, currentState );
71                                                 // Save the current state, so it can be compared next
72                                                 // time.
73                                                 callback.state = currentState;
74                                         }
75                                 }
76                         });
77         }
79         // Save the callback info, so it can be checked on the next occurrence of
80         // selectionChange.
81         styleStateChangeCallbacks.push( { style : style, fn : callback } );
84 CKEDITOR.STYLE_BLOCK = 1;
85 CKEDITOR.STYLE_INLINE = 2;
86 CKEDITOR.STYLE_OBJECT = 3;
88 (function()
90         var blockElements       = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
91         var objectElements      = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,th:1,ul:1,dl:1,dt:1,dd:1,form:1};
93         var semicolonFixRegex = /\s*(?:;\s*|$)/;
95         var notBookmark = CKEDITOR.dom.walker.bookmark( 0, 1 );
97         CKEDITOR.style = function( styleDefinition, variablesValues )
98         {
99                 if ( variablesValues )
100                 {
101                         styleDefinition = CKEDITOR.tools.clone( styleDefinition );
103                         replaceVariables( styleDefinition.attributes, variablesValues );
104                         replaceVariables( styleDefinition.styles, variablesValues );
105                 }
107                 var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
109                 this.type =
110                         ( element == '#' || blockElements[ element ] ) ?
111                                 CKEDITOR.STYLE_BLOCK
112                         : objectElements[ element ] ?
113                                 CKEDITOR.STYLE_OBJECT
114                         :
115                                 CKEDITOR.STYLE_INLINE;
117                 this._ =
118                 {
119                         definition : styleDefinition
120                 };
121         };
123         CKEDITOR.style.prototype =
124         {
125                 apply : function( document )
126                 {
127                         applyStyle.call( this, document, false );
128                 },
130                 remove : function( document )
131                 {
132                         applyStyle.call( this, document, true );
133                 },
135                 applyToRange : function( range )
136                 {
137                         return ( this.applyToRange =
138                                                 this.type == CKEDITOR.STYLE_INLINE ?
139                                                         applyInlineStyle
140                                                 : this.type == CKEDITOR.STYLE_BLOCK ?
141                                                         applyBlockStyle
142                                                 : this.type == CKEDITOR.STYLE_OBJECT ?
143                                                         applyObjectStyle
144                                                 : null ).call( this, range );
145                 },
147                 removeFromRange : function( range )
148                 {
149                         return ( this.removeFromRange =
150                                                 this.type == CKEDITOR.STYLE_INLINE ?
151                                                         removeInlineStyle
152                                                 : this.type == CKEDITOR.STYLE_BLOCK ?
153                                                         removeBlockStyle
154                                                 : this.type == CKEDITOR.STYLE_OBJECT ?
155                                                         removeObjectStyle
156                                                 : null ).call( this, range );
157                 },
159                 applyToObject : function( element )
160                 {
161                         setupElement( element, this );
162                 },
164                 /**
165                  * Get the style state inside an element path. Returns "true" if the
166                  * element is active in the path.
167                  */
168                 checkActive : function( elementPath )
169                 {
170                         switch ( this.type )
171                         {
172                                 case CKEDITOR.STYLE_BLOCK :
173                                         return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
175                                 case CKEDITOR.STYLE_OBJECT :
176                                 case CKEDITOR.STYLE_INLINE :
178                                         var elements = elementPath.elements;
180                                         for ( var i = 0, element ; i < elements.length ; i++ )
181                                         {
182                                                 element = elements[ i ];
184                                                 if ( this.type == CKEDITOR.STYLE_INLINE
185                                                           && ( element == elementPath.block || element == elementPath.blockLimit ) )
186                                                         continue;
188                                                 if( this.type == CKEDITOR.STYLE_OBJECT
189                                                          && !( element.getName() in objectElements ) )
190                                                                 continue;
192                                                 if ( this.checkElementRemovable( element, true ) )
193                                                         return true;
194                                         }
195                         }
196                         return false;
197                 },
199                 /**
200                  * Whether this style can be applied at the element path.
201                  * @param elementPath
202                  */
203                 checkApplicable : function( elementPath )
204                 {
205                         switch ( this.type )
206                         {
207                                 case CKEDITOR.STYLE_INLINE :
208                                 case CKEDITOR.STYLE_BLOCK :
209                                         break;
211                                 case CKEDITOR.STYLE_OBJECT :
212                                         return elementPath.lastElement.getAscendant( this.element, true );
213                         }
215                         return true;
216                 },
218                 // Checks if an element, or any of its attributes, is removable by the
219                 // current style definition.
220                 checkElementRemovable : function( element, fullMatch )
221                 {
222                         if ( !element )
223                                 return false;
225                         var def = this._.definition,
226                                 attribs;
228                         // If the element name is the same as the style name.
229                         if ( element.getName() == this.element )
230                         {
231                                 // If no attributes are defined in the element.
232                                 if ( !fullMatch && !element.hasAttributes() )
233                                         return true;
235                                 attribs = getAttributesForComparison( def );
237                                 if ( attribs._length )
238                                 {
239                                         for ( var attName in attribs )
240                                         {
241                                                 if ( attName == '_length' )
242                                                         continue;
244                                                 var elementAttr = element.getAttribute( attName ) || '';
246                                                 // Special treatment for 'style' attribute is required.
247                                                 if ( attName == 'style' ?
248                                                         compareCssText( attribs[ attName ], normalizeCssText( elementAttr, false ) )
249                                                         : attribs[ attName ] == elementAttr  )
250                                                 {
251                                                         if ( !fullMatch )
252                                                                 return true;
253                                                 }
254                                                 else if ( fullMatch )
255                                                                 return false;
256                                         }
257                                         if ( fullMatch )
258                                                 return true;
259                                 }
260                                 else
261                                         return true;
262                         }
264                         // Check if the element can be somehow overriden.
265                         var override = getOverrides( this )[ element.getName() ] ;
266                         if ( override )
267                         {
268                                 // If no attributes have been defined, remove the element.
269                                 if ( !( attribs = override.attributes ) )
270                                         return true;
272                                 for ( var i = 0 ; i < attribs.length ; i++ )
273                                 {
274                                         attName = attribs[i][0];
275                                         var actualAttrValue = element.getAttribute( attName );
276                                         if ( actualAttrValue )
277                                         {
278                                                 var attValue = attribs[i][1];
280                                                 // Remove the attribute if:
281                                                 //    - The override definition value is null;
282                                                 //    - The override definition value is a string that
283                                                 //      matches the attribute value exactly.
284                                                 //    - The override definition value is a regex that
285                                                 //      has matches in the attribute value.
286                                                 if ( attValue === null ||
287                                                                 ( typeof attValue == 'string' && actualAttrValue == attValue ) ||
288                                                                 attValue.test( actualAttrValue ) )
289                                                         return true;
290                                         }
291                                 }
292                         }
293                         return false;
294                 },
296                 // Builds the preview HTML based on the styles definition.
297                 buildPreview : function()
298                 {
299                         var styleDefinition = this._.definition,
300                                 html = [],
301                                 elementName = styleDefinition.element;
303                         // Avoid <bdo> in the preview.
304                         if ( elementName == 'bdo' )
305                                 elementName = 'span';
307                         html = [ '<', elementName ];
309                         // Assign all defined attributes.
310                         var attribs     = styleDefinition.attributes;
311                         if ( attribs )
312                         {
313                                 for ( var att in attribs )
314                                 {
315                                         html.push( ' ', att, '="', attribs[ att ], '"' );
316                                 }
317                         }
319                         // Assign the style attribute.
320                         var cssStyle = CKEDITOR.style.getStyleText( styleDefinition );
321                         if ( cssStyle )
322                                 html.push( ' style="', cssStyle, '"' );
324                         html.push( '>', styleDefinition.name, '</', elementName, '>' );
326                         return html.join( '' );
327                 }
328         };
330         // Build the cssText based on the styles definition.
331         CKEDITOR.style.getStyleText = function( styleDefinition )
332         {
333                 // If we have already computed it, just return it.
334                 var stylesDef = styleDefinition._ST;
335                 if ( stylesDef )
336                         return stylesDef;
338                 stylesDef = styleDefinition.styles;
340                 // Builds the StyleText.
341                 var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '',
342                                 specialStylesText = '';
344                 if ( stylesText.length )
345                         stylesText = stylesText.replace( semicolonFixRegex, ';' );
347                 for ( var style in stylesDef )
348                 {
349                         var styleVal = stylesDef[ style ],
350                                         text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
352                         // Some browsers don't support 'inherit' property value, leave them intact. (#5242)
353                         if ( styleVal == 'inherit' )
354                                 specialStylesText += text;
355                         else
356                                 stylesText += text;
357                 }
359                 // Browsers make some changes to the style when applying them. So, here
360                 // we normalize it to the browser format.
361                 if ( stylesText.length )
362                         stylesText = normalizeCssText( stylesText );
364                 stylesText += specialStylesText;
366                 // Return it, saving it to the next request.
367                 return ( styleDefinition._ST = stylesText );
368         };
370         // Gets the parent element which blocks the styling for an element. This
371         // can be done through read-only elements (contenteditable=false) or
372         // elements with the "data-nostyle" attribute.
373         function getUnstylableParent( element )
374         {
375                 var unstylable,
376                         editable;
378                 while ( ( element = element.getParent() ) )
379                 {
380                         if ( element.getName() == 'body' )
381                                 break;
383                         if ( element.getAttribute( 'data-nostyle' ) )
384                                 unstylable = element;
385                         else if ( !editable )
386                         {
387                                 var contentEditable = element.getAttribute( 'contentEditable' );
389                                 if ( contentEditable == 'false' )
390                                         unstylable = element;
391                                 else if ( contentEditable == 'true' )
392                                         editable = 1;
393                         }
394                 }
396                 return unstylable;
397         }
399         function applyInlineStyle( range )
400         {
401                 var document = range.document;
403                 if ( range.collapsed )
404                 {
405                         // Create the element to be inserted in the DOM.
406                         var collapsedElement = getElement( this, document );
408                         // Insert the empty element into the DOM at the range position.
409                         range.insertNode( collapsedElement );
411                         // Place the selection right inside the empty element.
412                         range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
414                         return;
415                 }
417                 var elementName = this.element;
418                 var def = this._.definition;
419                 var isUnknownElement;
421                 // Indicates that fully selected read-only elements are to be included in the styling range.
422                 var includeReadonly = def.includeReadonly;
424                 // If the read-only inclusion is not available in the definition, try
425                 // to get it from the document data.
426                 if ( includeReadonly == undefined )
427                         includeReadonly = document.getCustomData( 'cke_includeReadonly' );
429                 // Get the DTD definition for the element. Defaults to "span".
430                 var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
432                 // Expand the range.
433                 range.enlarge( CKEDITOR.ENLARGE_ELEMENT, 1 );
434                 range.trim();
436                 // Get the first node to be processed and the last, which concludes the
437                 // processing.
438                 var boundaryNodes = range.createBookmark(),
439                         firstNode = boundaryNodes.startNode,
440                         lastNode = boundaryNodes.endNode;
442                 var currentNode = firstNode;
444                 var styleRange;
446                 // Check if the boundaries are inside non stylable elements.
447                 var firstUnstylable = getUnstylableParent( firstNode ),
448                         lastUnstylable = getUnstylableParent( lastNode );
450                 // If the first element can't be styled, we'll start processing right
451                 // after its unstylable root.
452                 if ( firstUnstylable )
453                         currentNode = firstUnstylable.getNextSourceNode( true );
455                 // If the last element can't be styled, we'll stop processing on its
456                 // unstylable root.
457                 if ( lastUnstylable )
458                         lastNode = lastUnstylable;
460                 // Do nothing if the current node now follows the last node to be processed.
461                 if ( currentNode.getPosition( lastNode ) == CKEDITOR.POSITION_FOLLOWING )
462                         currentNode = 0;
464                 while ( currentNode )
465                 {
466                         var applyStyle = false;
468                         if ( currentNode.equals( lastNode ) )
469                         {
470                                 currentNode = null;
471                                 applyStyle = true;
472                         }
473                         else
474                         {
475                                 var nodeType = currentNode.type;
476                                 var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
477                                 var nodeIsReadonly = nodeName && ( currentNode.getAttribute( 'contentEditable' ) == 'false' );
478                                 var nodeIsNoStyle = nodeName && currentNode.getAttribute( 'data-nostyle' );
480                                 if ( nodeName && currentNode.data( 'cke-bookmark' ) )
481                                 {
482                                         currentNode = currentNode.getNextSourceNode( true );
483                                         continue;
484                                 }
486                                 // Check if the current node can be a child of the style element.
487                                 if ( !nodeName || ( dtd[ nodeName ]
488                                         && !nodeIsNoStyle
489                                         && ( !nodeIsReadonly || includeReadonly )
490                                         && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED )
491                                         && ( !def.childRule || def.childRule( currentNode ) ) ) )
492                                 {
493                                         var currentParent = currentNode.getParent();
495                                         // Check if the style element can be a child of the current
496                                         // node parent or if the element is not defined in the DTD.
497                                         if ( currentParent
498                                                 && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement )
499                                                 && ( !def.parentRule || def.parentRule( currentParent ) ) )
500                                         {
501                                                 // This node will be part of our range, so if it has not
502                                                 // been started, place its start right before the node.
503                                                 // In the case of an element node, it will be included
504                                                 // only if it is entirely inside the range.
505                                                 if ( !styleRange && ( !nodeName || !CKEDITOR.dtd.$removeEmpty[ nodeName ] || ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) )
506                                                 {
507                                                         styleRange = new CKEDITOR.dom.range( document );
508                                                         styleRange.setStartBefore( currentNode );
509                                                 }
511                                                 // Non element nodes, readonly elements, or empty
512                                                 // elements can be added completely to the range.
513                                                 if ( nodeType == CKEDITOR.NODE_TEXT || nodeIsReadonly || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) )
514                                                 {
515                                                         var includedNode = currentNode;
516                                                         var parentNode;
518                                                         // This node is about to be included completelly, but,
519                                                         // if this is the last node in its parent, we must also
520                                                         // check if the parent itself can be added completelly
521                                                         // to the range, otherwise apply the style immediately.
522                                                         while ( ( applyStyle = !includedNode.getNext( notBookmark ) )
523                                                                 && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
524                                                                 && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED )
525                                                                 && ( !def.childRule || def.childRule( parentNode ) ) )
526                                                         {
527                                                                 includedNode = parentNode;
528                                                         }
530                                                         styleRange.setEndAfter( includedNode );
532                                                 }
533                                         }
534                                         else
535                                                 applyStyle = true;
536                                 }
537                                 else
538                                         applyStyle = true;
540                                 // Get the next node to be processed.
541                                 currentNode = currentNode.getNextSourceNode( nodeIsNoStyle || nodeIsReadonly );
542                         }
544                         // Apply the style if we have something to which apply it.
545                         if ( applyStyle && styleRange && !styleRange.collapsed )
546                         {
547                                 // Build the style element, based on the style object definition.
548                                 var styleNode = getElement( this, document ),
549                                         styleHasAttrs = styleNode.hasAttributes();
551                                 // Get the element that holds the entire range.
552                                 var parent = styleRange.getCommonAncestor();
554                                 var removeList = {
555                                         styles : {},
556                                         attrs : {},
557                                         // Styles cannot be removed.
558                                         blockedStyles : {},
559                                         // Attrs cannot be removed.
560                                         blockedAttrs : {}
561                                 };
563                                 var attName, styleName, value;
565                                 // Loop through the parents, removing the redundant attributes
566                                 // from the element to be applied.
567                                 while ( styleNode && parent )
568                                 {
569                                         if ( parent.getName() == elementName )
570                                         {
571                                                 for ( attName in def.attributes )
572                                                 {
573                                                         if ( removeList.blockedAttrs[ attName ] || !( value = parent.getAttribute( styleName ) ) )
574                                                                 continue;
576                                                         if ( styleNode.getAttribute( attName ) == value )
577                                                                 removeList.attrs[ attName ] = 1;
578                                                         else
579                                                                 removeList.blockedAttrs[ attName ] = 1;
580                                                 }
582                                                 for ( styleName in def.styles )
583                                                 {
584                                                         if ( removeList.blockedStyles[ styleName ] || !( value = parent.getStyle( styleName ) ) )
585                                                                 continue;
587                                                         if ( styleNode.getStyle( styleName ) == value )
588                                                                 removeList.styles[ styleName ] = 1;
589                                                         else
590                                                                 removeList.blockedStyles[ styleName ] = 1;
591                                                 }
592                                         }
594                                         parent = parent.getParent();
595                                 }
597                                 for ( attName in removeList.attrs )
598                                         styleNode.removeAttribute( attName );
600                                 for ( styleName in removeList.styles )
601                                         styleNode.removeStyle( styleName );
603                                 if ( styleHasAttrs && !styleNode.hasAttributes() )
604                                         styleNode = null;
606                                 if ( styleNode )
607                                 {
608                                         // Move the contents of the range to the style element.
609                                         styleRange.extractContents().appendTo( styleNode );
611                                         // Here we do some cleanup, removing all duplicated
612                                         // elements from the style element.
613                                         removeFromInsideElement( this, styleNode );
615                                         // Insert it into the range position (it is collapsed after
616                                         // extractContents.
617                                         styleRange.insertNode( styleNode );
619                                         // Let's merge our new style with its neighbors, if possible.
620                                         styleNode.mergeSiblings();
622                                         // As the style system breaks text nodes constantly, let's normalize
623                                         // things for performance.
624                                         // With IE, some paragraphs get broken when calling normalize()
625                                         // repeatedly. Also, for IE, we must normalize body, not documentElement.
626                                         // IE is also known for having a "crash effect" with normalize().
627                                         // We should try to normalize with IE too in some way, somewhere.
628                                         if ( !CKEDITOR.env.ie )
629                                                 styleNode.$.normalize();
630                                 }
631                                 // Style already inherit from parents, left just to clear up any internal overrides. (#5931)
632                                 else
633                                 {
634                                         styleNode = new CKEDITOR.dom.element( 'span' );
635                                         styleRange.extractContents().appendTo( styleNode );
636                                         styleRange.insertNode( styleNode );
637                                         removeFromInsideElement( this, styleNode );
638                                         styleNode.remove( true );
639                                 }
641                                 // Style applied, let's release the range, so it gets
642                                 // re-initialization in the next loop.
643                                 styleRange = null;
644                         }
645                 }
647                 // Remove the bookmark nodes.
648                 range.moveToBookmark( boundaryNodes );
650                 // Minimize the result range to exclude empty text nodes. (#5374)
651                 range.shrink( CKEDITOR.SHRINK_TEXT );
652         }
654         function removeInlineStyle( range )
655         {
656                 /*
657                  * Make sure our range has included all "collpased" parent inline nodes so
658                  * that our operation logic can be simpler.
659                  */
660                 range.enlarge( CKEDITOR.ENLARGE_ELEMENT, 1 );
662                 var bookmark = range.createBookmark(),
663                         startNode = bookmark.startNode;
665                 if ( range.collapsed )
666                 {
668                         var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
669                                 // The topmost element in elementspatch which we should jump out of.
670                                 boundaryElement;
673                         for ( var i = 0, element ; i < startPath.elements.length
674                                         && ( element = startPath.elements[i] ) ; i++ )
675                         {
676                                 /*
677                                  * 1. If it's collaped inside text nodes, try to remove the style from the whole element.
678                                  *
679                                  * 2. Otherwise if it's collapsed on element boundaries, moving the selection
680                                  *  outside the styles instead of removing the whole tag,
681                                  *  also make sure other inner styles were well preserverd.(#3309)
682                                  */
683                                 if ( element == startPath.block || element == startPath.blockLimit )
684                                         break;
686                                 if ( this.checkElementRemovable( element ) )
687                                 {
688                                         var isStart;
690                                         if ( range.collapsed && (
691                                                  range.checkBoundaryOfElement( element, CKEDITOR.END ) ||
692                                                  ( isStart = range.checkBoundaryOfElement( element, CKEDITOR.START ) ) ) )
693                                         {
694                                                 boundaryElement = element;
695                                                 boundaryElement.match = isStart ? 'start' : 'end';
696                                         }
697                                         else
698                                         {
699                                                 /*
700                                                  * Before removing the style node, there may be a sibling to the style node
701                                                  * that's exactly the same to the one to be removed. To the user, it makes
702                                                  * no difference that they're separate entities in the DOM tree. So, merge
703                                                  * them before removal.
704                                                  */
705                                                 element.mergeSiblings();
706                                                 removeFromElement( this, element );
708                                         }
709                                 }
710                         }
712                         // Re-create the style tree after/before the boundary element,
713                         // the replication start from bookmark start node to define the
714                         // new range.
715                         if ( boundaryElement )
716                         {
717                                 var clonedElement = startNode;
718                                 for ( i = 0 ;; i++ )
719                                 {
720                                         var newElement = startPath.elements[ i ];
721                                         if ( newElement.equals( boundaryElement ) )
722                                                 break;
723                                         // Avoid copying any matched element.
724                                         else if ( newElement.match )
725                                                 continue;
726                                         else
727                                                 newElement = newElement.clone();
728                                         newElement.append( clonedElement );
729                                         clonedElement = newElement;
730                                 }
731                                 clonedElement[ boundaryElement.match == 'start' ?
732                                                         'insertBefore' : 'insertAfter' ]( boundaryElement );
733                         }
734                 }
735                 else
736                 {
737                         /*
738                          * Now our range isn't collapsed. Lets walk from the start node to the end
739                          * node via DFS and remove the styles one-by-one.
740                          */
741                         var endNode = bookmark.endNode,
742                                 me = this;
744                         /*
745                          * Find out the style ancestor that needs to be broken down at startNode
746                          * and endNode.
747                          */
748                         function breakNodes()
749                         {
750                                 var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
751                                         endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ),
752                                         breakStart = null,
753                                         breakEnd = null;
754                                 for ( var i = 0 ; i < startPath.elements.length ; i++ )
755                                 {
756                                         var element = startPath.elements[ i ];
758                                         if ( element == startPath.block || element == startPath.blockLimit )
759                                                 break;
761                                         if ( me.checkElementRemovable( element ) )
762                                                 breakStart = element;
763                                 }
764                                 for ( i = 0 ; i < endPath.elements.length ; i++ )
765                                 {
766                                         element = endPath.elements[ i ];
768                                         if ( element == endPath.block || element == endPath.blockLimit )
769                                                 break;
771                                         if ( me.checkElementRemovable( element ) )
772                                                 breakEnd = element;
773                                 }
775                                 if ( breakEnd )
776                                         endNode.breakParent( breakEnd );
777                                 if ( breakStart )
778                                         startNode.breakParent( breakStart );
779                         }
780                         breakNodes();
782                         // Now, do the DFS walk.
783                         var currentNode = startNode.getNext();
784                         while ( !currentNode.equals( endNode ) )
785                         {
786                                 /*
787                                  * Need to get the next node first because removeFromElement() can remove
788                                  * the current node from DOM tree.
789                                  */
790                                 var nextNode = currentNode.getNextSourceNode();
791                                 if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) )
792                                 {
793                                         // Remove style from element or overriding element.
794                                         if ( currentNode.getName() == this.element )
795                                                 removeFromElement( this, currentNode );
796                                         else
797                                                 removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] );
799                                         /*
800                                          * removeFromElement() may have merged the next node with something before
801                                          * the startNode via mergeSiblings(). In that case, the nextNode would
802                                          * contain startNode and we'll have to call breakNodes() again and also
803                                          * reassign the nextNode to something after startNode.
804                                          */
805                                         if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) )
806                                         {
807                                                 breakNodes();
808                                                 nextNode = startNode.getNext();
809                                         }
810                                 }
811                                 currentNode = nextNode;
812                         }
813                 }
815                 range.moveToBookmark( bookmark );
818         function applyObjectStyle( range )
819         {
820                 var root = range.getCommonAncestor( true, true ),
821                                 element = root.getAscendant( this.element, true );
822                 element && setupElement( element, this );
823         }
825         function removeObjectStyle( range )
826         {
827                 var root = range.getCommonAncestor( true, true ),
828                                 element = root.getAscendant( this.element, true );
830                 if ( !element )
831                         return;
833                 var style = this;
834                 var def = style._.definition;
835                 var attributes = def.attributes;
836                 var styles = CKEDITOR.style.getStyleText( def );
838                 // Remove all defined attributes.
839                 if ( attributes )
840                 {
841                         for ( var att in attributes )
842                         {
843                                 element.removeAttribute( att, attributes[ att ] );
844                         }
845                 }
847                 // Assign all defined styles.
848                 if ( def.styles )
849                 {
850                         for ( var i in def.styles )
851                         {
852                                 if ( !def.styles.hasOwnProperty( i ) )
853                                         continue;
855                                 element.removeStyle( i );
856                         }
857                 }
858         }
860         function applyBlockStyle( range )
861         {
862                 // Serializible bookmarks is needed here since
863                 // elements may be merged.
864                 var bookmark = range.createBookmark( true );
866                 var iterator = range.createIterator();
867                 iterator.enforceRealBlocks = true;
869                 // make recognize <br /> tag as a separator in ENTER_BR mode (#5121)
870                 if ( this._.enterMode )
871                         iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR );
873                 var block;
874                 var doc = range.document;
875                 var previousPreBlock;
877                 while ( ( block = iterator.getNextParagraph() ) )               // Only one =
878                 {
879                         var newBlock = getElement( this, doc, block );
880                         replaceBlock( block, newBlock );
881                 }
883                 range.moveToBookmark( bookmark );
884         }
886         function removeBlockStyle( range )
887         {
888                 // Serializible bookmarks is needed here since
889                 // elements may be merged.
890                 var bookmark = range.createBookmark( 1 );
892                 var iterator = range.createIterator();
893                 iterator.enforceRealBlocks = true;
894                 iterator.enlargeBr = this._.enterMode != CKEDITOR.ENTER_BR;
896                 var block;
897                 while ( ( block = iterator.getNextParagraph() ) )
898                 {
899                         if ( this.checkElementRemovable( block ) )
900                         {
901                                 // <pre> get special treatment.
902                                 if ( block.is( 'pre' ) )
903                                 {
904                                         var newBlock = this._.enterMode == CKEDITOR.ENTER_BR ?
905                                                                 null : range.document.createElement(
906                                                                         this._.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
908                                         newBlock && block.copyAttributes( newBlock );
909                                         replaceBlock( block, newBlock );
910                                 }
911                                 else
912                                          removeFromElement( this, block, 1 );
913                         }
914                 }
916                 range.moveToBookmark( bookmark );
917         }
919         // Replace the original block with new one, with special treatment
920         // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
921         // when necessary.(#3188)
922         function replaceBlock( block, newBlock )
923         {
924                 // Block is to be removed, create a temp element to
925                 // save contents.
926                 var removeBlock = !newBlock;
927                 if ( removeBlock )
928                 {
929                         newBlock = block.getDocument().createElement( 'div' );
930                         block.copyAttributes( newBlock );
931                 }
933                 var newBlockIsPre       = newBlock && newBlock.is( 'pre' );
934                 var blockIsPre  = block.is( 'pre' );
936                 var isToPre     = newBlockIsPre && !blockIsPre;
937                 var isFromPre   = !newBlockIsPre && blockIsPre;
939                 if ( isToPre )
940                         newBlock = toPre( block, newBlock );
941                 else if ( isFromPre )
942                         // Split big <pre> into pieces before start to convert.
943                         newBlock = fromPres( removeBlock ?
944                                                 [ block.getHtml() ] : splitIntoPres( block ), newBlock );
945                 else
946                         block.moveChildren( newBlock );
948                 newBlock.replace( block );
950                 if ( newBlockIsPre )
951                 {
952                         // Merge previous <pre> blocks.
953                         mergePre( newBlock );
954                 }
955                 else if ( removeBlock )
956                         removeNoAttribsElement( newBlock );
957         }
959         var nonWhitespaces = CKEDITOR.dom.walker.whitespaces( 1 );
960         /**
961          * Merge a <pre> block with a previous sibling if available.
962          */
963         function mergePre( preBlock )
964         {
965                 var previousBlock;
966                 if ( !( ( previousBlock = preBlock.getPrevious( nonWhitespaces ) )
967                                  && previousBlock.is
968                                  && previousBlock.is( 'pre') ) )
969                         return;
971                 // Merge the previous <pre> block contents into the current <pre>
972                 // block.
973                 //
974                 // Another thing to be careful here is that currentBlock might contain
975                 // a '\n' at the beginning, and previousBlock might contain a '\n'
976                 // towards the end. These new lines are not normally displayed but they
977                 // become visible after merging.
978                 var mergedHtml = replace( previousBlock.getHtml(), /\n$/, '' ) + '\n\n' +
979                                 replace( preBlock.getHtml(), /^\n/, '' ) ;
981                 // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces.
982                 if ( CKEDITOR.env.ie )
983                         preBlock.$.outerHTML = '<pre>' + mergedHtml + '</pre>';
984                 else
985                         preBlock.setHtml( mergedHtml );
987                 previousBlock.remove();
988         }
990         /**
991          * Split into multiple <pre> blocks separated by double line-break.
992          * @param preBlock
993          */
994         function splitIntoPres( preBlock )
995         {
996                 // Exclude the ones at header OR at tail,
997                 // and ignore bookmark content between them.
998                 var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,
999                         blockName = preBlock.getName(),
1000                         splitedHtml = replace( preBlock.getOuterHtml(),
1001                                 duoBrRegex,
1002                                 function( match, charBefore, bookmark )
1003                                 {
1004                                   return charBefore + '</pre>' + bookmark + '<pre>';
1005                                 } );
1007                 var pres = [];
1008                 splitedHtml.replace( /<pre\b.*?>([\s\S]*?)<\/pre>/gi, function( match, preContent ){
1009                         pres.push( preContent );
1010                 } );
1011                 return pres;
1012         }
1014         // Wrapper function of String::replace without considering of head/tail bookmarks nodes.
1015         function replace( str, regexp, replacement )
1016         {
1017                 var headBookmark = '',
1018                         tailBookmark = '';
1020                 str = str.replace( /(^<span[^>]+data-cke-bookmark.*?\/span>)|(<span[^>]+data-cke-bookmark.*?\/span>$)/gi,
1021                         function( str, m1, m2 ){
1022                                         m1 && ( headBookmark = m1 );
1023                                         m2 && ( tailBookmark = m2 );
1024                                 return '';
1025                         } );
1026                 return headBookmark + str.replace( regexp, replacement ) + tailBookmark;
1027         }
1028         /**
1029          * Converting a list of <pre> into blocks with format well preserved.
1030          */
1031         function fromPres( preHtmls, newBlock )
1032         {
1033                 var docFrag;
1034                 if ( preHtmls.length > 1 )
1035                         docFrag = new CKEDITOR.dom.documentFragment( newBlock.getDocument() );
1037                 for ( var i = 0 ; i < preHtmls.length ; i++ )
1038                 {
1039                         var blockHtml = preHtmls[ i ];
1041                         // 1. Trim the first and last line-breaks immediately after and before <pre>,
1042                         // they're not visible.
1043                          blockHtml =  blockHtml.replace( /(\r\n|\r)/g, '\n' ) ;
1044                          blockHtml = replace(  blockHtml, /^[ \t]*\n/, '' ) ;
1045                          blockHtml = replace(  blockHtml, /\n$/, '' ) ;
1046                         // 2. Convert spaces or tabs at the beginning or at the end to &nbsp;
1047                          blockHtml = replace(  blockHtml, /^[ \t]+|[ \t]+$/g, function( match, offset, s )
1048                                         {
1049                                                 if ( match.length == 1 )        // one space, preserve it
1050                                                         return '&nbsp;' ;
1051                                                 else if ( !offset )             // beginning of block
1052                                                         return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ';
1053                                                 else                            // end of block
1054                                                         return ' ' + CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 );
1055                                         } ) ;
1057                         // 3. Convert \n to <BR>.
1058                         // 4. Convert contiguous (i.e. non-singular) spaces or tabs to &nbsp;
1059                          blockHtml =  blockHtml.replace( /\n/g, '<br>' ) ;
1060                          blockHtml =  blockHtml.replace( /[ \t]{2,}/g,
1061                                         function ( match )
1062                                         {
1063                                                 return CKEDITOR.tools.repeat( '&nbsp;', match.length - 1 ) + ' ' ;
1064                                         } ) ;
1066                         if ( docFrag )
1067                         {
1068                                 var newBlockClone = newBlock.clone();
1069                                 newBlockClone.setHtml(  blockHtml );
1070                                 docFrag.append( newBlockClone );
1071                         }
1072                         else
1073                                 newBlock.setHtml( blockHtml );
1074                 }
1076                 return docFrag || newBlock;
1077         }
1079         /**
1080          * Converting from a non-PRE block to a PRE block in formatting operations.
1081          */
1082         function toPre( block, newBlock )
1083         {
1084                 var bogus = block.getBogus();
1085                 bogus && bogus.remove();
1087                 // First trim the block content.
1088                 var preHtml = block.getHtml();
1090                 // 1. Trim head/tail spaces, they're not visible.
1091                 preHtml = replace( preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, '' );
1092                 // 2. Delete ANSI whitespaces immediately before and after <BR> because
1093                 //    they are not visible.
1094                 preHtml = preHtml.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1' );
1095                 // 3. Compress other ANSI whitespaces since they're only visible as one
1096                 //    single space previously.
1097                 // 4. Convert &nbsp; to spaces since &nbsp; is no longer needed in <PRE>.
1098                 preHtml = preHtml.replace( /([ \t\n\r]+|&nbsp;)/g, ' ' );
1099                 // 5. Convert any <BR /> to \n. This must not be done earlier because
1100                 //    the \n would then get compressed.
1101                 preHtml = preHtml.replace( /<br\b[^>]*>/gi, '\n' );
1103                 // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces.
1104                 if ( CKEDITOR.env.ie )
1105                 {
1106                         var temp = block.getDocument().createElement( 'div' );
1107                         temp.append( newBlock );
1108                         newBlock.$.outerHTML =  '<pre>' + preHtml + '</pre>';
1109                         newBlock.copyAttributes( temp.getFirst() );
1110                         newBlock = temp.getFirst().remove();
1111                 }
1112                 else
1113                         newBlock.setHtml( preHtml );
1115                 return newBlock;
1116         }
1118         // Removes a style from an element itself, don't care about its subtree.
1119         function removeFromElement( style, element )
1120         {
1121                 var def = style._.definition,
1122                         attributes = CKEDITOR.tools.extend( {}, def.attributes, getOverrides( style )[ element.getName() ] ),
1123                         styles = def.styles,
1124                         // If the style is only about the element itself, we have to remove the element.
1125                         removeEmpty = CKEDITOR.tools.isEmpty( attributes ) && CKEDITOR.tools.isEmpty( styles );
1127                 // Remove definition attributes/style from the elemnt.
1128                 for ( var attName in attributes )
1129                 {
1130                         // The 'class' element value must match (#1318).
1131                         if ( ( attName == 'class' || style._.definition.fullMatch )
1132                                 && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) )
1133                                 continue;
1134                         removeEmpty = element.hasAttribute( attName );
1135                         element.removeAttribute( attName );
1136                 }
1138                 for ( var styleName in styles )
1139                 {
1140                         // Full match style insist on having fully equivalence. (#5018)
1141                         if ( style._.definition.fullMatch
1142                                 && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) )
1143                                 continue;
1145                         removeEmpty = removeEmpty || !!element.getStyle( styleName );
1146                         element.removeStyle( styleName );
1147                 }
1149                 if ( removeEmpty )
1150                 {
1151                         !CKEDITOR.dtd.$block[ element.getName() ] || style._.enterMode == CKEDITOR.ENTER_BR && !element.hasAttributes() ?
1152                                 removeNoAttribsElement( element ) :
1153                                 element.renameNode( style._.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
1154                 }
1155         }
1157         // Removes a style from inside an element.
1158         function removeFromInsideElement( style, element )
1159         {
1160                 var def = style._.definition,
1161                         attribs = def.attributes,
1162                         styles = def.styles,
1163                         overrides = getOverrides( style );
1165                 var innerElements = element.getElementsByTag( style.element );
1167                 for ( var i = innerElements.count(); --i >= 0 ; )
1168                         removeFromElement( style,  innerElements.getItem( i ) );
1170                 // Now remove any other element with different name that is
1171                 // defined to be overriden.
1172                 for ( var overrideElement in overrides )
1173                 {
1174                         if ( overrideElement != style.element )
1175                         {
1176                                 innerElements = element.getElementsByTag( overrideElement ) ;
1177                                 for ( i = innerElements.count() - 1 ; i >= 0 ; i-- )
1178                                 {
1179                                         var innerElement = innerElements.getItem( i );
1180                                         removeOverrides( innerElement, overrides[ overrideElement ] ) ;
1181                                 }
1182                         }
1183                 }
1185         }
1187         /**
1188          *  Remove overriding styles/attributes from the specific element.
1189          *  Note: Remove the element if no attributes remain.
1190          * @param {Object} element
1191          * @param {Object} overrides
1192          */
1193         function removeOverrides( element, overrides )
1194         {
1195                 var attributes = overrides && overrides.attributes ;
1197                 if ( attributes )
1198                 {
1199                         for ( var i = 0 ; i < attributes.length ; i++ )
1200                         {
1201                                 var attName = attributes[i][0], actualAttrValue ;
1203                                 if ( ( actualAttrValue = element.getAttribute( attName ) ) )
1204                                 {
1205                                         var attValue = attributes[i][1] ;
1207                                         // Remove the attribute if:
1208                                         //    - The override definition value is null ;
1209                                         //    - The override definition valie is a string that
1210                                         //      matches the attribute value exactly.
1211                                         //    - The override definition value is a regex that
1212                                         //      has matches in the attribute value.
1213                                         if ( attValue === null ||
1214                                                         ( attValue.test && attValue.test( actualAttrValue ) ) ||
1215                                                         ( typeof attValue == 'string' && actualAttrValue == attValue ) )
1216                                                 element.removeAttribute( attName ) ;
1217                                 }
1218                         }
1219                 }
1221                 removeNoAttribsElement( element );
1222         }
1224         // If the element has no more attributes, remove it.
1225         function removeNoAttribsElement( element )
1226         {
1227                 // If no more attributes remained in the element, remove it,
1228                 // leaving its children.
1229                 if ( !element.hasAttributes() )
1230                 {
1231                         if ( CKEDITOR.dtd.$block[ element.getName() ] )
1232                         {
1233                                 var previous = element.getPrevious( nonWhitespaces ),
1234                                                 next = element.getNext( nonWhitespaces );
1236                                 if ( previous && ( previous.type == CKEDITOR.NODE_TEXT || !previous.isBlockBoundary( { br : 1 } ) ) )
1237                                         element.append( 'br', 1 );
1238                                 if ( next && ( next.type == CKEDITOR.NODE_TEXT || !next.isBlockBoundary( { br : 1 } ) ) )
1239                                         element.append( 'br' );
1241                                 element.remove( true );
1242                         }
1243                         else
1244                         {
1245                                 // Removing elements may open points where merging is possible,
1246                                 // so let's cache the first and last nodes for later checking.
1247                                 var firstChild = element.getFirst();
1248                                 var lastChild = element.getLast();
1250                                 element.remove( true );
1252                                 if ( firstChild )
1253                                 {
1254                                         // Check the cached nodes for merging.
1255                                         firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.mergeSiblings();
1257                                         if ( lastChild && !firstChild.equals( lastChild )
1258                                                         && lastChild.type == CKEDITOR.NODE_ELEMENT )
1259                                                 lastChild.mergeSiblings();
1260                                 }
1262                         }
1263                 }
1264         }
1266         function getElement( style, targetDocument, element )
1267         {
1268                 var el;
1270                 var def = style._.definition;
1272                 var elementName = style.element;
1274                 // The "*" element name will always be a span for this function.
1275                 if ( elementName == '*' )
1276                         elementName = 'span';
1278                 // Create the element.
1279                 el = new CKEDITOR.dom.element( elementName, targetDocument );
1281                 // #6226: attributes should be copied before the new ones are applied
1282                 if ( element )
1283                         element.copyAttributes( el );
1285                 el = setupElement( el, style );
1287                 // Avoid ID duplication.
1288                 if ( targetDocument.getCustomData( 'doc_processing_style' ) && el.hasAttribute( 'id' ) )
1289                         el.removeAttribute( 'id' );
1290                 else
1291                         targetDocument.setCustomData( 'doc_processing_style', 1 );
1293                 return el;
1294         }
1296         function setupElement( el, style )
1297         {
1298                 var def = style._.definition;
1299                 var attributes = def.attributes;
1300                 var styles = CKEDITOR.style.getStyleText( def );
1302                 // Assign all defined attributes.
1303                 if ( attributes )
1304                 {
1305                         for ( var att in attributes )
1306                         {
1307                                 el.setAttribute( att, attributes[ att ] );
1308                         }
1309                 }
1311                 // Assign all defined styles.
1312                 if( styles )
1313                         el.setAttribute( 'style', styles );
1315                 return el;
1316         }
1318         var varRegex = /#\((.+?)\)/g;
1319         function replaceVariables( list, variablesValues )
1320         {
1321                 for ( var item in list )
1322                 {
1323                         list[ item ] = list[ item ].replace( varRegex, function( match, varName )
1324                                 {
1325                                         return variablesValues[ varName ];
1326                                 });
1327                 }
1328         }
1331         // Returns an object that can be used for style matching comparison.
1332         // Attributes names and values are all lowercased, and the styles get
1333         // merged with the style attribute.
1334         function getAttributesForComparison( styleDefinition )
1335         {
1336                 // If we have already computed it, just return it.
1337                 var attribs = styleDefinition._AC;
1338                 if ( attribs )
1339                         return attribs;
1341                 attribs = {};
1343                 var length = 0;
1345                 // Loop through all defined attributes.
1346                 var styleAttribs = styleDefinition.attributes;
1347                 if ( styleAttribs )
1348                 {
1349                         for ( var styleAtt in styleAttribs )
1350                         {
1351                                 length++;
1352                                 attribs[ styleAtt ] = styleAttribs[ styleAtt ];
1353                         }
1354                 }
1356                 // Includes the style definitions.
1357                 var styleText = CKEDITOR.style.getStyleText( styleDefinition );
1358                 if ( styleText )
1359                 {
1360                         if ( !attribs[ 'style' ] )
1361                                 length++;
1362                         attribs[ 'style' ] = styleText;
1363                 }
1365                 // Appends the "length" information to the object.
1366                 attribs._length = length;
1368                 // Return it, saving it to the next request.
1369                 return ( styleDefinition._AC = attribs );
1370         }
1372         /**
1373          * Get the the collection used to compare the elements and attributes,
1374          * defined in this style overrides, with other element. All information in
1375          * it is lowercased.
1376          * @param {CKEDITOR.style} style
1377          */
1378         function getOverrides( style )
1379         {
1380                 if ( style._.overrides )
1381                         return style._.overrides;
1383                 var overrides = ( style._.overrides = {} ),
1384                         definition = style._.definition.overrides;
1386                 if ( definition )
1387                 {
1388                         // The override description can be a string, object or array.
1389                         // Internally, well handle arrays only, so transform it if needed.
1390                         if ( !CKEDITOR.tools.isArray( definition ) )
1391                                 definition = [ definition ];
1393                         // Loop through all override definitions.
1394                         for ( var i = 0 ; i < definition.length ; i++ )
1395                         {
1396                                 var override = definition[i];
1397                                 var elementName;
1398                                 var overrideEl;
1399                                 var attrs;
1401                                 // If can be a string with the element name.
1402                                 if ( typeof override == 'string' )
1403                                         elementName = override.toLowerCase();
1404                                 // Or an object.
1405                                 else
1406                                 {
1407                                         elementName = override.element ? override.element.toLowerCase() : style.element;
1408                                         attrs = override.attributes;
1409                                 }
1411                                 // We can have more than one override definition for the same
1412                                 // element name, so we attempt to simply append information to
1413                                 // it if it already exists.
1414                                 overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} );
1416                                 if ( attrs )
1417                                 {
1418                                         // The returning attributes list is an array, because we
1419                                         // could have different override definitions for the same
1420                                         // attribute name.
1421                                         var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || new Array() );
1422                                         for ( var attName in attrs )
1423                                         {
1424                                                 // Each item in the attributes array is also an array,
1425                                                 // where [0] is the attribute name and [1] is the
1426                                                 // override value.
1427                                                 overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] );
1428                                         }
1429                                 }
1430                         }
1431                 }
1433                 return overrides;
1434         }
1436         // Make the comparison of attribute value easier by standardizing it.
1437         function normalizeProperty( name, value, isStyle )
1438         {
1439                 var temp = new CKEDITOR.dom.element( 'span' );
1440                 temp [ isStyle ? 'setStyle' : 'setAttribute' ]( name, value );
1441                 return temp[ isStyle ? 'getStyle' : 'getAttribute' ]( name );
1442         }
1444         // Make the comparison of style text easier by standardizing it.
1445         function normalizeCssText( unparsedCssText, nativeNormalize )
1446         {
1447                 var styleText;
1448                 if ( nativeNormalize !== false )
1449                 {
1450                         // Injects the style in a temporary span object, so the browser parses it,
1451                         // retrieving its final format.
1452                         var temp = new CKEDITOR.dom.element( 'span' );
1453                         temp.setAttribute( 'style', unparsedCssText );
1454                         styleText = temp.getAttribute( 'style' ) || '';
1455                 }
1456                 else
1457                         styleText = unparsedCssText;
1459                 // Shrinking white-spaces around colon and semi-colon (#4147).
1460                 // Compensate tail semi-colon.
1461                 return styleText.replace( /\s*([;:])\s*/, '$1' )
1462                                                          .replace( /([^\s;])$/, '$1;')
1463                                                         // Trimming spaces after comma(#4107),
1464                                                         // remove quotations(#6403),
1465                                                         // mostly for differences on "font-family".
1466                                                          .replace( /,\s+/g, ',' )
1467                                                          .replace( /\"/g,'' )
1468                                                          .toLowerCase();
1469         }
1471         // Turn inline style text properties into one hash.
1472         function parseStyleText( styleText )
1473         {
1474                 var retval = {};
1475                 styleText
1476                    .replace( /&quot;/g, '"' )
1477                    .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value )
1478                 {
1479                         retval[ name ] = value;
1480                 } );
1481                 return retval;
1482         }
1484         /**
1485          * Compare two bunch of styles, with the speciality that value 'inherit'
1486          * is treated as a wildcard which will match any value.
1487          * @param {Object|String} source
1488          * @param {Object|String} target
1489          */
1490         function compareCssText( source, target )
1491         {
1492                 typeof source == 'string' && ( source = parseStyleText( source ) );
1493                 typeof target == 'string' && ( target = parseStyleText( target ) );
1494                 for( var name in source )
1495                 {
1496                         if ( !( name in target &&
1497                                         ( target[ name ] == source[ name ]
1498                                                 || source[ name ] == 'inherit'
1499                                                 || target[ name ] == 'inherit' ) ) )
1500                         {
1501                                 return false;
1502                         }
1503                 }
1504                 return true;
1505         }
1507         function applyStyle( document, remove )
1508         {
1509                 var selection = document.getSelection(),
1510                         ranges = selection.getRanges(),
1511                         func = remove ? this.removeFromRange : this.applyToRange,
1512                         range;
1514                 var iterator = ranges.createIterator();
1515                 while ( ( range = iterator.getNextRange() ) )
1516                         func.call( this, range );
1518                 selection.selectRanges( ranges );
1520                 document.removeCustomData( 'doc_processing_style' );
1521         }
1522 })();
1524 CKEDITOR.styleCommand = function( style )
1526         this.style = style;
1529 CKEDITOR.styleCommand.prototype.exec = function( editor )
1531         editor.focus();
1533         var doc = editor.document;
1535         if ( doc )
1536         {
1537                 if ( this.state == CKEDITOR.TRISTATE_OFF )
1538                         this.style.apply( doc );
1539                 else if ( this.state == CKEDITOR.TRISTATE_ON )
1540                         this.style.remove( doc );
1541         }
1543         return !!doc;
1546 CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' );
1548 // Backward compatibility (#5025).
1549 CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
1550 CKEDITOR.loadStylesSet = function( name, url, callback )
1551         {
1552                 CKEDITOR.stylesSet.addExternal( name, url, '' );
1553                 CKEDITOR.stylesSet.load( name, callback );
1554         };
1558  * Gets the current styleSet for this instance
1559  * @param {Function} callback The function to be called with the styles data.
1560  * @example
1561  * editor.getStylesSet( function( stylesDefinitions ) {} );
1562  */
1563 CKEDITOR.editor.prototype.getStylesSet = function( callback )
1565         if ( !this._.stylesDefinitions )
1566         {
1567                 var editor = this,
1568                         // Respect the backwards compatible definition entry
1569                         configStyleSet = editor.config.stylesCombo_stylesSet || editor.config.stylesSet || 'default';
1571                 // #5352 Allow to define the styles directly in the config object
1572                 if ( configStyleSet instanceof Array )
1573                 {
1574                         editor._.stylesDefinitions = configStyleSet;
1575                         callback( configStyleSet );
1576                         return;
1577                 }
1579                 var     partsStylesSet = configStyleSet.split( ':' ),
1580                         styleSetName = partsStylesSet[ 0 ],
1581                         externalPath = partsStylesSet[ 1 ],
1582                         pluginPath = CKEDITOR.plugins.registered.styles.path;
1584                 CKEDITOR.stylesSet.addExternal( styleSetName,
1585                                 externalPath ?
1586                                         partsStylesSet.slice( 1 ).join( ':' ) :
1587                                         pluginPath + 'styles/' + styleSetName + '.js', '' );
1589                 CKEDITOR.stylesSet.load( styleSetName, function( stylesSet )
1590                         {
1591                                 editor._.stylesDefinitions = stylesSet[ styleSetName ];
1592                                 callback( editor._.stylesDefinitions );
1593                         } ) ;
1594         }
1595         else
1596                 callback( this._.stylesDefinitions );
1600  * Indicates that fully selected read-only elements will be included when
1601  * applying the style (for inline styles only).
1602  * @name CKEDITOR.style.includeReadonly
1603  * @type Boolean
1604  * @default false
1605  * @since 3.5
1606  */
1608  /**
1609   * Disables inline styling on read-only elements.
1610   * @name CKEDITOR.config.disableReadonlyStyling
1611   * @type Boolean
1612   * @default false
1613   * @since 3.5
1614   */
1617  * The "styles definition set" to use in the editor. They will be used in the
1618  * styles combo and the Style selector of the div container. <br>
1619  * The styles may be defined in the page containing the editor, or can be
1620  * loaded on demand from an external file. In the second case, if this setting
1621  * contains only a name, the styles definition file will be loaded from the
1622  * "styles" folder inside the styles plugin folder.
1623  * Otherwise, this setting has the "name:url" syntax, making it
1624  * possible to set the URL from which loading the styles file.<br>
1625  * Previously this setting was available as config.stylesCombo_stylesSet<br>
1626  * @name CKEDITOR.config.stylesSet
1627  * @type String|Array
1628  * @default 'default'
1629  * @since 3.3
1630  * @example
1631  * // Load from the styles' styles folder (mystyles.js file).
1632  * config.stylesSet = 'mystyles';
1633  * @example
1634  * // Load from a relative URL.
1635  * config.stylesSet = 'mystyles:/editorstyles/styles.js';
1636  * @example
1637  * // Load from a full URL.
1638  * config.stylesSet = 'mystyles:http://www.example.com/editorstyles/styles.js';
1639  * @example
1640  * // Load from a list of definitions.
1641  * config.stylesSet = [
1642  *  { name : 'Strong Emphasis', element : 'strong' },
1643  * { name : 'Emphasis', element : 'em' }, ... ];
1644  */