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