      1 /*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 CKEDITOR.plugins.add( 'styles',
      7 {
      8 	requires : [ 'selection' ]
      9 });
     10
     11 /**
     12  * Registers a function to be called whenever a style changes its state in the
     13  * editing area. The current state is passed to the function. The possible
     14  * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.
     15  * @param {CKEDITOR.style} The style to be watched.
     16  * @param {Function} The function to be called when the style state changes.
     17  * @example
     18  * // Create a style object for the <b> element.
     19  * var style = new CKEDITOR.style( { element : 'b' } );
     20  * var editor = CKEDITOR.instances.editor1;
     21  * editor.attachStyleStateChange( style, function( state )
     22  *     {
     23  *         if ( state == CKEDITOR.TRISTATE_ON )
     24  *             alert( 'The current state for the B element is ON' );
     25  *         else
     26  *             alert( 'The current state for the B element is OFF' );
     27  *     });
     28  */
     29 CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback )
     30 {
     31 	// Try to get the list of attached callbacks.
     32 	var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
     33
     34 	// If it doesn't exist, it means this is the first call. So, let's create
     35 	// all the structure to manage the style checks and the callback calls.
     36 	if ( !styleStateChangeCallbacks )
     37 	{
     38 		// Create the callbacks array.
     39 		styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
     40
     41 		// Attach to the selectionChange event, so we can check the styles at
     42 		// that point.
     43 		this.on( 'selectionChange', function( ev )
     44 			{
     45 				// Loop throw all registered callbacks.
     46 				for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
     47 				{
     48 					var callback = styleStateChangeCallbacks[ i ];
     49
     50 					// Check the current state for the style defined for that
     51 					// callback.
     52 					var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
     53
     54 					// If the state changed since the last check.
     55 					if ( callback.state !== currentState )
     56 					{
     57 						// Call the callback function, passing the current
     58 						// state to it.
     59 						callback.fn.call( this, currentState );
     60
     61 						// Save the current state, so it can be compared next
     62 						// time.
     63 						callback.state !== currentState;
     64 					}
     65 				}
     66 			});
     67 	}
     68
     69 	// Save the callback info, so it can be checked on the next occurence of
     70 	// selectionChange.
     71 	styleStateChangeCallbacks.push( { style : style, fn : callback } );
     72 };
     73
     74 CKEDITOR.STYLE_BLOCK = 1;
     75 CKEDITOR.STYLE_INLINE = 2;
     76 CKEDITOR.STYLE_OBJECT = 3;
     77
     78 (function()
     79 {
     80 	var blockElements	= { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
     81 	var objectElements	= { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
     82
     83 	var semicolonFixRegex = /\s*(?:;\s*|$)/;
     84
     85 	CKEDITOR.style = function( styleDefinition, variablesValues )
     86 	{
     87 		if ( variablesValues )
     88 		{
     89 			styleDefinition = CKEDITOR.tools.clone( styleDefinition );
     90
     91 			replaceVariables( styleDefinition.attributes, variablesValues );
     92 			replaceVariables( styleDefinition.styles, variablesValues );
     93 		}
     94
     95 		var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
     96
     97 		this.type =
     98 			( element == '#' || blockElements[ element ] ) ?
     99 				CKEDITOR.STYLE_BLOCK
    100 			: objectElements[ element ] ?
    101 				CKEDITOR.STYLE_OBJECT
    102 			:
    103 				CKEDITOR.STYLE_INLINE;
    104
    105 		this._ =
    106 		{
    107 			definition : styleDefinition
    108 		};
    109 	};
    110
    111 	CKEDITOR.style.prototype =
    112 	{
    113 		apply : function( document )
    114 		{
    115 			applyStyle.call( this, document, false );
    116 		},
    117
    118 		remove : function( document )
    119 		{
    120 			applyStyle.call( this, document, true );
    121 		},
    122
    123 		applyToRange : function( range )
    124 		{
    125 			return ( this.applyToRange =
    126 						this.type == CKEDITOR.STYLE_INLINE ?
    127 							applyInlineStyle
    128 						: this.type == CKEDITOR.STYLE_BLOCK ?
    129 							applyBlockStyle
    130 						: null ).call( this, range );
    131 		},
    132
    133 		removeFromRange : function( range )
    134 		{
    135 			return ( this.removeFromRange =
    136 						this.type == CKEDITOR.STYLE_INLINE ?
    137 							removeInlineStyle
    138 						: null ).call( this, range );
    139 		},
    140
    141 		applyToObject : function( element )
    142 		{
    143 			setupElement( element, this );
    144 		},
    145
    146 		/**
    147 		 * Get the style state inside an element path. Returns "true" if the
    148 		 * element is active in the path.
    149 		 */
    150 		checkActive : function( elementPath )
    151 		{
    152 			switch ( this.type )
    153 			{
    154 				case CKEDITOR.STYLE_BLOCK :
    155 					return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
    156
    157 				case CKEDITOR.STYLE_INLINE :
    158
    159 					var elements = elementPath.elements;
    160
    161 					for ( var i = 0, element ; i < elements.length ; i++ )
    162 					{
    163 						element = elements[i];
    164
    165 						if ( element == elementPath.block || element == elementPath.blockLimit )
    166 							continue;
    167
    168 						if ( this.checkElementRemovable( element, true ) )
    169 							return true;
    170 					}
    171 			}
    172 			return false;
    173 		},
    174
    175 		// Checks if an element, or any of its attributes, is removable by the
    176 		// current style definition.
    177 		checkElementRemovable : function( element, fullMatch )
    178 		{
    179 			if ( !element )
    180 				return false;
    181
    182 			var def = this._.definition,
    183 				attribs;
    184
    185 			// If the element name is the same as the style name.
    186 			if ( element.getName() == this.element )
    187 			{
    188 				// If no attributes are defined in the element.
    189 				if ( !fullMatch && !element.hasAttributes() )
    190 					return true;
    191
    192 				attribs = getAttributesForComparison( def );
    193
    194 				if ( attribs._length )
    195 				{
    196 					for ( var attName in attribs )
    197 					{
    198 						if ( attName == '_length' )
    199 							continue;
    200 						if ( attribs[attName] == element.getAttribute( attName ) )
    201 						{
    202 							if ( !fullMatch )
    203 								return true;
    204 						}
    205 						else if ( fullMatch )
    206 								return false;
    207 					}
    208 					if( fullMatch )
    209 						return true;
    210 				}
    211 				else
    212 					return true;
    213 			}
    214
    215 			// Check if the element can be somehow overriden.
    216 			var override = getOverrides( this )[ element.getName() ] ;
    217 			if ( override )
    218 			{
    219 				// If no attributes have been defined, remove the element.
    220 				if ( !( attribs = override.attributes ) )
    221 					return true;
    222
    223 				for ( var i = 0 ; i < attribs.length ; i++ )
    224 				{
    225 					attName = attribs[i][0];
    226 					var actualAttrValue = element.getAttribute( attName );
    227 					if ( actualAttrValue )
    228 					{
    229 						var attValue = attribs[i][1];
    230
    231 						// Remove the attribute if:
    232 						//    - The override definition value is null;
    233 						//    - The override definition value is a string that
    234 						//      matches the attribute value exactly.
    235 						//    - The override definition value is a regex that
    236 						//      has matches in the attribute value.
    237 						if ( attValue === null ||
    238 								( typeof attValue == 'string' && actualAttrValue == attValue ) ||
    239 								attValue.test( actualAttrValue ) )
    240 							return true;
    241 					}
    242 				}
    243 			}
    244 			return false;
    245 		}
    246 	};
    247
    248 	// Build the cssText based on the styles definition.
    249 	CKEDITOR.style.getStyleText = function( styleDefinition )
    250 	{
    251 		// If we have already computed it, just return it.
    252 		var stylesDef = styleDefinition._ST;
    253 		if ( stylesDef )
    254 			return stylesDef;
    255
    256 		stylesDef = styleDefinition.styles;
    257
    258 		// Builds the StyleText.
    259
    260 		var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '';
    261
    262 		if ( stylesText.length )
    263 			stylesText = stylesText.replace( semicolonFixRegex, ';' );
    264
    265 		for ( var style in stylesDef )
    266 			stylesText += style + ':' + stylesDef[ style ] + ';';
    267
    268 		// Browsers make some changes to the style when applying them. So, here
    269 		// we normalize it to the browser format.
    270 		if ( stylesText.length )
    271 			stylesText = normalizeCssText( stylesText );
    272
    273 		// Return it, saving it to the next request.
    274 		return ( styleDefinition._ST = stylesText );
    275 	};
    276
    277 	function applyInlineStyle( range )
    278 	{
    279 		var document = range.document;
    280
    281 		if ( range.collapsed )
    282 		{
    283 			// Create the element to be inserted in the DOM.
    284 			var collapsedElement = getElement( this, document );
    285
    286 			// Insert the empty element into the DOM at the range position.
    287 			range.insertNode( collapsedElement );
    288
    289 			// Place the selection right inside the empty element.
    290 			range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
    291
    292 			return;
    293 		}
    294
    295 		var elementName = this.element;
    296 		var def = this._.definition;
    297 		var isUnknownElement;
    298
    299 		// Get the DTD definition for the element. Defaults to "span".
    300 		var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
    301
    302 		// Bookmark the range so we can re-select it after processing.
    303 		var bookmark = range.createBookmark();
    304
    305 		// Expand the range.
    306 		range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
    307 		range.trim();
    308
    309 		// Get the first node to be processed and the last, which concludes the
    310 		// processing.
    311 		var boundaryNodes = range.getBoundaryNodes();
    312 		var firstNode = boundaryNodes.startNode;
    313 		var lastNode = boundaryNodes.endNode.getNextSourceNode( true );
    314
    315 		// Probably the document end is reached, we need a marker node.
    316 		if ( !lastNode )
    317 		{
    318 				lastNode = document.createText( '' );
    319 				lastNode.insertAfter( range.endContainer );
    320 		}
    321 		// The detection algorithm below skips the contents inside bookmark nodes, so
    322 		// we'll need to make sure lastNode isn't the   inside a bookmark node.
    323 		var lastParent = lastNode.getParent();
    324 		if ( lastParent && lastParent.getAttribute( '_fck_bookmark' ) )
    325 			lastNode = lastParent;
    326
    327 		if ( lastNode.equals( firstNode ) )
    328 		{
    329 			// If the last node is the same as the the first one, we must move
    330 			// it to the next one, otherwise the first one will not be
    331 			// processed.
    332 			lastNode = lastNode.getNextSourceNode( true );
    333
    334 			// It may happen that there are no more nodes after it (the end of
    335 			// the document), so we must add something there to make our code
    336 			// simpler.
    337 			if ( !lastNode )
    338 			{
    339 				lastNode = document.createText( '' );
    340 				lastNode.insertAfter( firstNode );
    341 			}
    342 		}
    343
    344 		var currentNode = firstNode;
    345
    346 		var styleRange;
    347
    348 		// Indicates that that some useful inline content has been found, so
    349 		// the style should be applied.
    350 		var hasContents;
    351
    352 		while ( currentNode )
    353 		{
    354 			var applyStyle = false;
    355
    356 			if ( currentNode.equals( lastNode ) )
    357 			{
    358 				currentNode = null;
    359 				applyStyle = true;
    360 			}
    361 			else
    362 			{
    363 				var nodeType = currentNode.type;
    364 				var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
    365
    366 				if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
    367 				{
    368 					currentNode = currentNode.getNextSourceNode( true );
    369 					continue;
    370 				}
    371
    372 				// Check if the current node can be a child of the style element.
    373 				if ( !nodeName || ( dtd[ nodeName ] && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) )
    374 				{
    375 					var currentParent = currentNode.getParent();
    376
    377 					// Check if the style element can be a child of the current
    378 					// node parent or if the element is not defined in the DTD.
    379 					if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
    380 					{
    381 						// This node will be part of our range, so if it has not
    382 						// been started, place its start right before the node.
    383 						// In the case of an element node, it will be included
    384 						// only if it is entirely inside the range.
    385 						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 ) ) )
    386 						{
    387 							styleRange = new CKEDITOR.dom.range( document );
    388 							styleRange.setStartBefore( currentNode );
    389 						}
    390
    391 						// Non element nodes, or empty elements can be added
    392 						// completely to the range.
    393 						if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) )
    394 						{
    395 							var includedNode = currentNode;
    396 							var parentNode;
    397
    398 							// This node is about to be included completelly, but,
    399 							// if this is the last node in its parent, we must also
    400 							// check if the parent itself can be added completelly
    401 							// to the range.
    402 							while ( !includedNode.$.nextSibling
    403 								&& ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
    404 								&& ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
    405 							{
    406 								includedNode = parentNode;
    407 							}
    408
    409 							styleRange.setEndAfter( includedNode );
    410
    411 							// If the included node still is the last node in its
    412 							// parent, it means that the parent can't be included
    413 							// in this style DTD, so apply the style immediately.
    414 							if ( !includedNode.$.nextSibling )
    415 								applyStyle = true;
    416
    417 							if ( !hasContents )
    418 								hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
    419 						}
    420 					}
    421 					else
    422 						applyStyle = true;
    423 				}
    424 				else
    425 					applyStyle = true;
    426
    427 				// Get the next node to be processed.
    428 				currentNode = currentNode.getNextSourceNode();
    429 			}
    430
    431 			// Apply the style if we have something to which apply it.
    432 			if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
    433 			{
    434 				// Build the style element, based on the style object definition.
    435 				var styleNode = getElement( this, document );
    436
    437 				// Get the element that holds the entire range.
    438 				var parent = styleRange.getCommonAncestor();
    439
    440 				// Loop through the parents, removing the redundant attributes
    441 				// from the element to be applied.
    442 				while ( styleNode && parent )
    443 				{
    444 					if ( parent.getName() == elementName )
    445 					{
    446 						for ( var attName in def.attribs )
    447 						{
    448 							if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
    449 								styleNode.removeAttribute( attName );
    450 						}
    451
    452 						for ( var styleName in def.styles )
    453 						{
    454 							if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
    455 								styleNode.removeStyle( styleName );
    456 						}
    457
    458 						if ( !styleNode.hasAttributes() )
    459 						{
    460 							styleNode = null;
    461 							break;
    462 						}
    463 					}
    464
    465 					parent = parent.getParent();
    466 				}
    467
    468 				if ( styleNode )
    469 				{
    470 					// Move the contents of the range to the style element.
    471 					styleRange.extractContents().appendTo( styleNode );
    472
    473 					// Here we do some cleanup, removing all duplicated
    474 					// elements from the style element.
    475 					removeFromInsideElement( this, styleNode );
    476
    477 					// Insert it into the range position (it is collapsed after
    478 					// extractContents.
    479 					styleRange.insertNode( styleNode );
    480
    481 					// Let's merge our new style with its neighbors, if possible.
    482 					mergeSiblings( styleNode );
    483
    484 					// As the style system breaks text nodes constantly, let's normalize
    485 					// things for performance.
    486 					// With IE, some paragraphs get broken when calling normalize()
    487 					// repeatedly. Also, for IE, we must normalize body, not documentElement.
    488 					// IE is also known for having a "crash effect" with normalize().
    489 					// We should try to normalize with IE too in some way, somewhere.
    490 					if ( !CKEDITOR.env.ie )
    491 						styleNode.$.normalize();
    492 				}
    493
    494 				// Style applied, let's release the range, so it gets
    495 				// re-initialization in the next loop.
    496 				styleRange = null;
    497 			}
    498 		}
    499
    500 //		this._FixBookmarkStart( startNode );
    501
    502 		range.moveToBookmark( bookmark );
    503 	}
    504
    505 	function removeInlineStyle( range )
    506 	{
    507 		/*
    508 		 * Make sure our range has included all "collpased" parent inline nodes so
    509 		 * that our operation logic can be simpler.
    510 		 */
    511 		range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
    512
    513 		var bookmark = range.createBookmark(),
    514 			startNode = bookmark.startNode;
    515
    516 		if ( range.collapsed )
    517 		{
    518
    519 			var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
    520 				// The topmost element in elementspatch which we should jump out of.
    521 				boundaryElement;
    522
    523
    524 			for ( var i = 0, element ; i < startPath.elements.length
    525 					&& ( element = startPath.elements[i] ) ; i++ )
    526 			{
    527 				/*
    528 				 * 1. If it's collaped inside text nodes, try to remove the style from the whole element.
    529 				 *
    530 				 * 2. Otherwise if it's collapsed on element boundaries, moving the selection
    531 				 *  outside the styles instead of removing the whole tag,
    532 				 *  also make sure other inner styles were well preserverd.(#3309)
    533 				 */
    534 				if ( element == startPath.block || element == startPath.blockLimit )
    535 					break;
    536
    537 				if ( this.checkElementRemovable( element ) )
    538 				{
    539 					var endOfElement = range.checkBoundaryOfElement( element, CKEDITOR.END ),
    540 							startOfElement = !endOfElement && range.checkBoundaryOfElement( element, CKEDITOR.START );
    541 					if ( startOfElement || endOfElement )
    542 					{
    543 						boundaryElement = element;
    544 						boundaryElement.match = startOfElement ? 'start' : 'end';
    545 					}
    546 					else
    547 					{
    548 						/*
    549 						 * Before removing the style node, there may be a sibling to the style node
    550 						 * that's exactly the same to the one to be removed. To the user, it makes
    551 						 * no difference that they're separate entities in the DOM tree. So, merge
    552 						 * them before removal.
    553 						 */
    554 						mergeSiblings( element );
    555 						removeFromElement( this, element );
    556
    557 					}
    558 				}
    559 			}
    560
    561 			// Re-create the style tree after/before the boundary element,
    562 			// the replication start from bookmark start node to define the
    563 			// new range.
    564 			if ( boundaryElement )
    565 			{
    566 				var clonedElement = startNode;
    567 				for ( i = 0 ;; i++ )
    568 				{
    569 					var newElement = startPath.elements[ i ];
    570 					if ( newElement.equals( boundaryElement ) )
    571 						break;
    572 					// Avoid copying any matched element.
    573 					else if( newElement.match )
    574 						continue;
    575 					else
    576 						newElement = newElement.clone();
    577 					newElement.append( clonedElement );
    578 					clonedElement = newElement;
    579 				}
    580 				clonedElement[ boundaryElement.match == 'start' ?
    581 							'insertBefore' : 'insertAfter' ]( boundaryElement );
    582 			}
    583 		}
    584 		else
    585 		{
    586 			/*
    587 			 * Now our range isn't collapsed. Lets walk from the start node to the end
    588 			 * node via DFS and remove the styles one-by-one.
    589 			 */
    590 			var endNode = bookmark.endNode,
    591 				me = this;
    592
    593 			/*
    594 			 * Find out the style ancestor that needs to be broken down at startNode
    595 			 * and endNode.
    596 			 */
    597 			function breakNodes()
    598 			{
    599 				var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
    600 					endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ),
    601 					breakStart = null,
    602 					breakEnd = null;
    603 				for ( var i = 0 ; i < startPath.elements.length ; i++ )
    604 				{
    605 					var element = startPath.elements[ i ];
    606
    607 					if ( element == startPath.block || element == startPath.blockLimit )
    608 						break;
    609
    610 					if ( me.checkElementRemovable( element ) )
    611 						breakStart = element;
    612 				}
    613 				for ( i = 0 ; i < endPath.elements.length ; i++ )
    614 				{
    615 					element = endPath.elements[ i ];
    616
    617 					if ( element == endPath.block || element == endPath.blockLimit )
    618 						break;
    619
    620 					if ( me.checkElementRemovable( element ) )
    621 						breakEnd = element;
    622 				}
    623
    624 				if ( breakEnd )
    625 					endNode.breakParent( breakEnd );
    626 				if ( breakStart )
    627 					startNode.breakParent( breakStart );
    628 			}
    629 			breakNodes();
    630
    631 			// Now, do the DFS walk.
    632 			var currentNode = startNode.getNext();
    633 			while ( !currentNode.equals( endNode ) )
    634 			{
    635 				/*
    636 				 * Need to get the next node first because removeFromElement() can remove
    637 				 * the current node from DOM tree.
    638 				 */
    639 				var nextNode = currentNode.getNextSourceNode();
    640 				if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) )
    641 				{
    642 					// Remove style from element or overriding element.
    643 					if( currentNode.getName() == this.element )
    644 						removeFromElement( this, currentNode );
    645 					else
    646 						removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] );
    647
    648 					/*
    649 					 * removeFromElement() may have merged the next node with something before
    650 					 * the startNode via mergeSiblings(). In that case, the nextNode would
    651 					 * contain startNode and we'll have to call breakNodes() again and also
    652 					 * reassign the nextNode to something after startNode.
    653 					 */
    654 					if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) )
    655 					{
    656 						breakNodes();
    657 						nextNode = startNode.getNext();
    658 					}
    659 				}
    660 				currentNode = nextNode;
    661 			}
    662 		}
    663
    664 		range.moveToBookmark( bookmark );
    665 	}
    666
    667 	function applyBlockStyle( range )
    668 	{
    669 		// Bookmark the range so we can re-select it after processing.
    670 		var bookmark = range.createBookmark();
    671
    672 		var iterator = range.createIterator();
    673 		iterator.enforceRealBlocks = true;
    674
    675 		var block;
    676 		var doc = range.document;
    677 		var previousPreBlock;
    678
    679 		while( ( block = iterator.getNextParagraph() ) )		// Only one =
    680 		{
    681 			// Create the new node right before the current one.
    682 			var newBlock = getElement( this, doc );
    683
    684 			// Check if we are changing from/to <pre>.
    685 //			var newBlockIsPre	= newBlock.nodeName.IEquals( 'pre' );
    686 //			var blockIsPre		= block.nodeName.IEquals( 'pre' );
    687
    688 //			var toPre	= newBlockIsPre && !blockIsPre;
    689 //			var fromPre	= !newBlockIsPre && blockIsPre;
    690
    691 			// Move everything from the current node to the new one.
    692 //			if ( toPre )
    693 //				newBlock = this._ToPre( doc, block, newBlock );
    694 //			else if ( fromPre )
    695 //				newBlock = this._FromPre( doc, block, newBlock );
    696 //			else	// Convering from a regular block to another regular block.
    697 				block.moveChildren( newBlock );
    698
    699 			// Replace the current block.
    700 			newBlock.insertBefore( block );
    701 			block.remove();
    702
    703 			// Complete other tasks after inserting the node in the DOM.
    704 //			if ( newBlockIsPre )
    705 //			{
    706 //				if ( previousPreBlock )
    707 //					this._CheckAndMergePre( previousPreBlock, newBlock ) ;	// Merge successive <pre> blocks.
    708 //				previousPreBlock = newBlock;
    709 //			}
    710 //			else if ( fromPre )
    711 //				this._CheckAndSplitPre( newBlock ) ;	// Split <br><br> in successive <pre>s.
    712 		}
    713
    714 		range.moveToBookmark( bookmark );
    715 	}
    716
    717 	// Removes a style from an element itself, don't care about its subtree.
    718 	function removeFromElement( style, element )
    719 	{
    720 		var def = style._.definition,
    721 			attributes = def.attributes,
    722 			styles = def.styles,
    723 			overrides = getOverrides( style );
    724
    725 		function removeAttrs()
    726 		{
    727 			for ( var attName in attributes )
    728 			{
    729 				// The 'class' element value must match (#1318).
    730 				if ( attName == 'class' && element.getAttribute( attName ) != attributes[ attName ] )
    731 					continue;
    732 				element.removeAttribute( attName );
    733 			}
    734 		}
    735
    736 		// Remove definition attributes/style from the elemnt.
    737 		removeAttrs();
    738 		for ( var styleName in styles )
    739 			element.removeStyle( styleName );
    740
    741 		// Now remove override styles on the element.
    742 		attributes = overrides[ element.getName() ];
    743 		if( attributes )
    744 			removeAttrs();
    745 		removeNoAttribsElement( element );
    746 	}
    747
    748 	// Removes a style from inside an element.
    749 	function removeFromInsideElement( style, element )
    750 	{
    751 		var def = style._.definition,
    752 			attribs = def.attributes,
    753 			styles = def.styles,
    754 			overrides = getOverrides( style );
    755
    756 		var innerElements = element.getElementsByTag( style.element );
    757
    758 		for ( var i = innerElements.count(); --i >= 0 ; )
    759 			removeFromElement( style,  innerElements.getItem( i ) );
    760
    761 		// Now remove any other element with different name that is
    762 		// defined to be overriden.
    763 		for ( var overrideElement in overrides )
    764 		{
    765 			if ( overrideElement != style.element )
    766 			{
    767 				innerElements = element.getElementsByTag( overrideElement ) ;
    768 				for ( i = innerElements.count() - 1 ; i >= 0 ; i-- )
    769 				{
    770 					var innerElement = innerElements.getItem( i );
    771 					removeOverrides( innerElement, overrides[ overrideElement ] ) ;
    772 				}
    773 			}
    774 		}
    775
    776 	}
    777
    778 	/**
    779 	 *  Remove overriding styles/attributes from the specific element.
    780 	 *  Note: Remove the element if no attributes remain.
    781 	 * @param {Object} element
    782 	 * @param {Object} overrides
    783 	 */
    784 	function removeOverrides( element, overrides )
    785 	{
    786 		var attributes = overrides && overrides.attributes ;
    787
    788 		if ( attributes )
    789 		{
    790 			for ( var i = 0 ; i < attributes.length ; i++ )
    791 			{
    792 				var attName = attributes[i][0], actualAttrValue ;
    793
    794 				if ( ( actualAttrValue = element.getAttribute( attName ) ) )
    795 				{
    796 					var attValue = attributes[i][1] ;
    797
    798 					// Remove the attribute if:
    799 					//    - The override definition value is null ;
    800 					//    - The override definition valie is a string that
    801 					//      matches the attribute value exactly.
    802 					//    - The override definition value is a regex that
    803 					//      has matches in the attribute value.
    804 					if ( attValue === null ||
    805 							( attValue.test && attValue.test( actualAttrValue ) ) ||
    806 							( typeof attValue == 'string' && actualAttrValue == attValue ) )
    807 						element.removeAttribute( attName ) ;
    808 				}
    809 			}
    810 		}
    811
    812 		removeNoAttribsElement( element );
    813 	}
    814
    815 	// If the element has no more attributes, remove it.
    816 	function removeNoAttribsElement( element )
    817 	{
    818 		// If no more attributes remained in the element, remove it,
    819 		// leaving its children.
    820 		if ( !element.hasAttributes() )
    821 		{
    822 			// Removing elements may open points where merging is possible,
    823 			// so let's cache the first and last nodes for later checking.
    824 			var firstChild	= element.getFirst();
    825 			var lastChild	= element.getLast();
    826
    827 			element.remove( true );
    828
    829 			if ( firstChild )
    830 			{
    831 				// Check the cached nodes for merging.
    832 				mergeSiblings( firstChild );
    833
    834 				if ( lastChild && !firstChild.equals( lastChild ) )
    835 					mergeSiblings( lastChild );
    836 			}
    837 		}
    838 	}
    839
    840 	function mergeSiblings( element )
    841 	{
    842 		if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
    843 			return;
    844
    845 		mergeElements( element, element.getNext(), true );
    846 		mergeElements( element, element.getPrevious() );
    847 	}
    848
    849 	function mergeElements( element, sibling, isNext )
    850 	{
    851 		if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
    852 		{
    853 			var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
    854
    855 			if ( hasBookmark )
    856 				sibling = isNext ? sibling.getNext() : sibling.getPrevious();
    857
    858 			if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && element.isIdentical( sibling ) )
    859 			{
    860 				// Save the last child to be checked too, to merge things like
    861 				// <b><i></i></b><b><i></i></b> => <b><i></i></b>
    862 				var innerSibling = isNext ? element.getLast() : element.getFirst();
    863
    864 				if ( hasBookmark )
    865 					( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
    866
    867 				sibling.moveChildren( element, !isNext );
    868 				sibling.remove();
    869
    870 				// Now check the last inner child (see two comments above).
    871 				if ( innerSibling )
    872 					mergeSiblings( innerSibling );
    873 			}
    874 		}
    875 	}
    876
    877 	function getElement( style, targetDocument )
    878 	{
    879 		var el;
    880
    881 		var def = style._.definition;
    882
    883 		var elementName = style.element;
    884
    885 		// The "*" element name will always be a span for this function.
    886 		if ( elementName == '*' )
    887 			elementName = 'span';
    888
    889 		// Create the element.
    890 		el = new CKEDITOR.dom.element( elementName, targetDocument );
    891
    892 		return setupElement( el, style );
    893 	}
    894
    895 	function setupElement( el, style )
    896 	{
    897 		var def = style._.definition;
    898 		var attributes = def.attributes;
    899 		var styles = CKEDITOR.style.getStyleText( def );
    900
    901 		// Assign all defined attributes.
    902 		if ( attributes )
    903 		{
    904 			for ( var att in attributes )
    905 			{
    906 				el.setAttribute( att, attributes[ att ] );
    907 			}
    908 		}
    909
    910 		// Assign all defined styles.
    911 		if ( styles )
    912 			el.setAttribute( 'style', styles );
    913
    914 		return el;
    915 	}
    916
    917 	var varRegex = /#\((.+?)\)/g;
    918 	function replaceVariables( list, variablesValues )
    919 	{
    920 		for ( var item in list )
    921 		{
    922 			list[ item ] = list[ item ].replace( varRegex, function( match, varName )
    923 				{
    924 					return variablesValues[ varName ];
    925 				});
    926 		}
    927 	}
    928
    929
    930 	// Returns an object that can be used for style matching comparison.
    931 	// Attributes names and values are all lowercased, and the styles get
    932 	// merged with the style attribute.
    933 	function getAttributesForComparison( styleDefinition )
    934 	{
    935 		// If we have already computed it, just return it.
    936 		var attribs = styleDefinition._AC;
    937 		if ( attribs )
    938 			return attribs;
    939
    940 		attribs = {};
    941
    942 		var length = 0;
    943
    944 		// Loop through all defined attributes.
    945 		var styleAttribs = styleDefinition.attributes;
    946 		if ( styleAttribs )
    947 		{
    948 			for ( var styleAtt in styleAttribs )
    949 			{
    950 				length++;
    951 				attribs[ styleAtt ] = styleAttribs[ styleAtt ];
    952 			}
    953 		}
    954
    955 		// Includes the style definitions.
    956 		var styleText = CKEDITOR.style.getStyleText( styleDefinition );
    957 		if ( styleText )
    958 		{
    959 			if ( !attribs[ 'style' ] )
    960 				length++;
    961 			attribs[ 'style' ] = styleText;
    962 		}
    963
    964 		// Appends the "length" information to the object.
    965 		attribs._length = length;
    966
    967 		// Return it, saving it to the next request.
    968 		return ( styleDefinition._AC = attribs );
    969 	}
    970
    971 	/**
    972 	 * Get the the collection used to compare the elements and attributes,
    973 	 * defined in this style overrides, with other element. All information in
    974 	 * it is lowercased.
    975 	 * @param {CKEDITOR.style} style
    976 	 */
    977 	function getOverrides( style )
    978 	{
    979 		if( style._.overrides )
    980 			return style._.overrides;
    981
    982 		var overrides = ( style._.overrides = {} ),
    983 			definition = style._.definition.overrides;
    984
    985 		if ( definition )
    986 		{
    987 			// The override description can be a string, object or array.
    988 			// Internally, well handle arrays only, so transform it if needed.
    989 			if ( !CKEDITOR.tools.isArray( definition ) )
    990 				definition = [ definition ];
    991
    992 			// Loop through all override definitions.
    993 			for ( var i = 0 ; i < definition.length ; i++ )
    994 			{
    995 				var override = definition[i];
    996 				var elementName;
    997 				var overrideEl;
    998 				var attrs;
    999
    1000 				// If can be a string with the element name.
    1001 				if ( typeof override == 'string' )
    1002 					elementName = override.toLowerCase();
    1003 				// Or an object.
    1004 				else
    1005 				{
    1006 					elementName = override.element ? override.element.toLowerCase() : style.element;
    1007 					attrs = override.attributes;
    1008 				}
    1009
    1010 				// We can have more than one override definition for the same
    1011 				// element name, so we attempt to simply append information to
    1012 				// it if it already exists.
    1013 				overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} );
    1014
    1015 				if ( attrs )
    1016 				{
    1017 					// The returning attributes list is an array, because we
    1018 					// could have different override definitions for the same
    1019 					// attribute name.
    1020 					var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || new Array() );
    1021 					for ( var attName in attrs )
    1022 					{
    1023 						// Each item in the attributes array is also an array,
    1024 						// where [0] is the attribute name and [1] is the
    1025 						// override value.
    1026 						overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] );
    1027 					}
    1028 				}
    1029 			}
    1030 		}
    1031
    1032 		return overrides;
    1033 	}
    1034
    1035 	function normalizeCssText( unparsedCssText )
    1036 	{
    1037 		// Injects the style in a temporary span object, so the browser parses it,
    1038 		// retrieving its final format.
    1039 		var temp = new CKEDITOR.dom.element( 'span' );
    1040 		temp.setAttribute( 'style', unparsedCssText );
    1041 		return temp.getAttribute( 'style' );
    1042 	}
    1043
    1044 	function applyStyle( document, remove )
    1045 	{
    1046 		// Get all ranges from the selection.
    1047 		var selection = document.getSelection();
    1048 		var ranges = selection.getRanges();
    1049 		var func = remove ? this.removeFromRange : this.applyToRange;
    1050
    1051 		// Apply the style to the ranges.
    1052 		for ( var i = 0 ; i < ranges.length ; i++ )
    1053 			func.call( this, ranges[ i ] );
    1054
    1055 		// Select the ranges again.
    1056 		selection.selectRanges( ranges );
    1057 	}
    1058 })();
    1059
    1060 CKEDITOR.styleCommand = function( style )
    1061 {
    1062 	this.style = style;
    1063 };
    1064
    1065 CKEDITOR.styleCommand.prototype.exec = function( editor )
    1066 {
    1067 	editor.focus();
    1068
    1069 	var doc = editor.document;
    1070
    1071 	if ( doc )
    1072 	{
    1073 		if ( this.state == CKEDITOR.TRISTATE_OFF )
    1074 			this.style.apply( doc );
    1075 		else if ( this.state == CKEDITOR.TRISTATE_ON )
    1076 			this.style.remove( doc );
    1077 	}
    1078
    1079 	return !!doc;
    1080 };
    1081