Index: /CKEditor/trunk/_source/core/dom/element.js
===================================================================
--- /CKEditor/trunk/_source/core/dom/element.js	(revision 3116)
+++ /CKEditor/trunk/_source/core/dom/element.js	(revision 3117)
@@ -659,4 +659,42 @@
 		},
 
+		isIdentical : function( otherElement )
+		{
+			if ( this.getName() != otherElement.getName() )
+				return false;
+
+			var thisAttribs = this.$.attributes,
+				otherAttribs = otherElement.$.attributes;
+
+			var thisLength = thisAttribs.length,
+				otherLength = otherAttribs.length;
+
+			if ( !CKEDITOR.env.ie && thisLength != otherLength )
+				return false;
+
+			for ( var i = 0 ; i < thisLength ; i++ )
+			{
+				var attribute = thisAttribs[ i ];
+				
+				if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != otherElement.getAttribute( attribute.nodeName ) )
+					return false;
+			}
+
+			// For IE, we have to for both elements, because it's difficult to
+			// know how the atttibutes collection is organized in its DOM.
+			if ( CKEDITOR.env.ie )
+			{
+				for ( i = 0 ; i < otherLength ; i++ )
+				{
+					attribute = otherAttribs[ i ];
+					
+					if ( ( !CKEDITOR.env.ie || ( attribute.specified && attribute.nodeName != '_cke_expando' ) ) && attribute.nodeValue != thisAttribs.getAttribute( attribute.nodeName ) )
+						return false;
+				}
+			}
+
+			return true;
+		},
+
 		/**
 		 * Indicates that the element has defined attributes.
@@ -747,19 +785,19 @@
 		{
 			skip || ( skip = {} );
-			var attributes = this.$.attributes ;
+			var attributes = this.$.attributes;
 
 			for ( var n = 0 ; n < attributes.length ; n++ )
 			{
-				var attr = attributes[n] ;
+				var attr = attributes[n];
 
 				if ( attr.specified )
 				{
-					var attrName = attr.nodeName ;
+					var attrName = attr.nodeName;
 					if ( attrName in skip )
-						continue ;
+						continue;
 
 					var attrValue = this.getAttribute( attrName );
 					if ( !attrValue )
-						attrValue = attr.nodeValue ;
+						attrValue = attr.nodeValue;
 
 					target.setAttribute( attrName, attrValue );
@@ -768,5 +806,5 @@
 
 			if ( this.$.style.cssText !== '' )
-				target.$.style.cssText = this.$.style.cssText ;
+				target.$.style.cssText = this.$.style.cssText;
 		},
 
Index: /CKEditor/trunk/_source/core/test.js
===================================================================
--- /CKEditor/trunk/_source/core/test.js	(revision 3116)
+++ /CKEditor/trunk/_source/core/test.js	(revision 3117)
@@ -72,9 +72,9 @@
 						if ( attName == 'style' )
 						{
+							// Safari adds some extra space to the end.
+							attValue = attValue.replace( /\s+/g, '' );
+
 							// IE doesn't add the final ";"
 							attValue = attValue.replace( /([^"';\s])\s*(["']?)$/, '$1;$2' );
-
-							// Safari adds some extra space to the end.
-							attValue = attValue.replace( /\s+(["']?)$/, '$1' );
 						}
 
Index: /CKEditor/trunk/_source/plugins/styles/plugin.js
===================================================================
--- /CKEditor/trunk/_source/plugins/styles/plugin.js	(revision 3116)
+++ /CKEditor/trunk/_source/plugins/styles/plugin.js	(revision 3117)
@@ -81,6 +81,14 @@
 	var objectElements	= { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
 
-	CKEDITOR.style = function( styleDefinition )
-	{
+	CKEDITOR.style = function( styleDefinition, variablesValues )
+	{
+		if ( variablesValues )
+		{
+			styleDefinition = CKEDITOR.tools.clone( styleDefinition );
+
+			replaceVariables( styleDefinition.attributes, variablesValues );
+			replaceVariables( styleDefinition.styles, variablesValues );
+		}
+
 		var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
 
@@ -99,19 +107,4 @@
 	};
 
-	var applyStyle = function( document, remove )
-	{
-		// Get all ranges from the selection.
-		var selection = document.getSelection();
-		var ranges = selection.getRanges();
-		var func = remove ? this.removeFromRange : this.applyToRange;
-
-		// Apply the style to the ranges.
-		for ( var i = 0 ; i < ranges.length ; i++ )
-			func.call( this, ranges[ i ] );
-
-		// Select the ranges again.
-		selection.selectRanges( ranges );
-	};
-
 	CKEDITOR.style.prototype =
 	{
@@ -138,5 +131,5 @@
 		removeFromRange : function( range )
 		{
-			return ( this.removeFromRange = 
+			return ( this.removeFromRange =
 						this.type == CKEDITOR.STYLE_INLINE ?
 							removeInlineStyle
@@ -180,7 +173,6 @@
 				return false;
 
-			var def = this._.definition;
-			var attribs = def.attributes;
-			var styles = def.styles;
+			var def = this._.definition,
+				attribs;
 
 			// If no attributes are defined in the element.
@@ -188,31 +180,28 @@
 				return true;
 
-			for ( var attName in attribs )
-			{
-				if ( element.getAttribute( attName ) == attribs[ attName ] )
-				{
-					if ( !fullMatch )
-						return true;
-				}
-				else if ( fullMatch )
-					return false;
+			attribs = getAttributesForComparison( def );
+
+			if ( attribs._length )
+			{
+				for ( var attName in attribs )
+				{
+					if ( attName == '_length' )
+						continue;
+
+					if ( compareAttributeValues( attName, attribs[ attName ], element.getAttribute( attName ) ) )
+					{
+						if ( !fullMatch )
+							return true;
+					}
+					else if ( fullMatch )
+						return false;
+				}
 			}
 
 			return true;
-		},
-
-		/**
-		 * Sets the value of a variable attribute or style, to be used when
-		 * appliying the style. This function must be called before using any
-		 * other function in this object.
-		 */
-		setVariable : function( name, value )
-		{
-			var variables = this._.variables || ( this._variables = {} );
-			variables[ name ] = value;
 		}
 	};
 
-	var applyInlineStyle = function( range )
+	function applyInlineStyle( range )
 	{
 		var document = range.document;
@@ -344,6 +333,9 @@
 				var styleNode = getElement( this, document );
 
+				// Get the element that holds the entire range.
 				var parent = styleRange.getCommonAncestor();
 
+				// Loop through the parents, removing the redundant attributes
+				// from the element to be applied.
 				while ( styleNode && parent )
 				{
@@ -407,7 +399,7 @@
 
 		range.moveToBookmark( bookmark );
-	};
-
-	var removeInlineStyle = function( range )
+	}
+
+	function removeInlineStyle( range )
 	{
 		/*
@@ -422,5 +414,5 @@
 		if ( range.collapsed )
 		{
-			/* 
+			/*
 			 * If the range is collapsed, try to remove the style from all ancestor
 			 * elements, until a block boundary is reached.
@@ -520,9 +512,9 @@
 			}
 		}
-		
+
 		range.moveToBookmark( bookmark );
-	};
-
-	var applyBlockStyle = function( range )
+	}
+
+	function applyBlockStyle( range )
 	{
 		// Bookmark the range so we can re-select it after processing.
@@ -572,8 +564,8 @@
 
 		range.moveToBookmark( bookmark );
-	};
+	}
 
 	// Removes a style from an element itself, don't care about its subtree.
-	var removeFromElement = function( style, element )
+	function removeFromElement( style, element )
 	{
 		var def = style._.definition,
@@ -593,8 +585,8 @@
 
 		removeNoAttribsElement( element );
-	};
+	}
 
 	// Removes a style from inside an element.
-	var removeFromInsideElement = function( style, element )
+	function removeFromInsideElement( style, element )
 	{
 		var def = style._.definition;
@@ -606,8 +598,8 @@
 		for ( var i = innerElements.count() ; --i >= 0 ; )
 			removeFromElement( style, innerElements.getItem( i ) );
-	};
+	}
 
 	// If the element has no more attributes, remove it.
-	var removeNoAttribsElement = function( element )
+	function removeNoAttribsElement( element )
 	{
 		// If no more attributes remained in the element, remove it,
@@ -631,44 +623,7 @@
 			}
 		}
-	};
-
-	// Get the the collection used to compare the attributes defined in this
-	// style with attributes in an element. All information in it is lowercased.
-	// V2
-//	var getAttribsForComparison = function( style )
-//	{
-//		// If we have already computed it, just return it.
-//		var attribs = style._.attribsForComparison;
-//		if ( attribs )
-//			return attribs;
-
-//		attribs = {};
-
-//		var def = style._.definition;
-
-//		// Loop through all defined attributes.
-//		var styleAttribs = def.attributes;
-//		if ( styleAttribs )
-//		{
-//			for ( var styleAtt in styleAttribs )
-//			{
-//				attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase();
-//			}
-//		}
-
-//		// Includes the style definitions.
-//		if ( this._GetStyleText().length > 0 )
-//		{
-//			attribs['style'] = this._GetStyleText().toLowerCase();
-//		}
-
-//		// Appends the "length" information to the object.
-//		FCKTools.AppendLengthProperty( attribs, '_length' );
-
-//		// Return it, saving it to the next request.
-//		return ( this._GetAttribsForComparison_$ = attribs );
-//	},
-
-	var mergeSiblings = function( element )
+	}
+
+	function mergeSiblings( element )
 	{
 		if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
@@ -677,7 +632,7 @@
 		mergeElements( element, element.getNext(), true );
 		mergeElements( element, element.getPrevious() );
-	};
-
-	var mergeElements = function( element, sibling, isNext )
+	}
+
+	function mergeElements( element, sibling, isNext )
 	{
 		if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
@@ -688,5 +643,5 @@
 				sibling = isNext ? sibling.getNext() : sibling.getPrevious();
 
-			if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.getName() == element.getName() )
+			if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && element.isIdentical( sibling ) )
 			{
 				// Save the last child to be checked too, to merge things like
@@ -705,20 +660,15 @@
 			}
 		}
-	};
-
-	// Regex used to match all variables defined in an attribute or style
-	// value. The variable name is returned with $2.
-	var styleVariableAttNameRegex = /#\(\s*("|')(.+?)\1[^\)]*\s*\)/g;
-
-	var getElement = function( style, targetDocument )
+	}
+
+	function getElement( style, targetDocument )
 	{
 		var el;
 
 		var def = style._.definition;
-		var variables = style._.variables;
 
 		var elementName = style.element;
 		var attributes = def.attributes;
-		var styles = def.styles;
+		var styles = getStyleText( def );
 
 		// The "*" element name will always be a span for this function.
@@ -734,14 +684,5 @@
 			for ( var att in attributes )
 			{
-				var attValue = attributes[ att ];
-				if ( attValue && variables )
-				{
-					attValue = attValue.replace( styleVariableAttNameRegex, function()
-						{
-							// The second group in the regex is the variable name.
-							return variables[ arguments[2] ] || arguments[0];
-						});
-				}
-				el.setAttribute( att, attValue );
+				el.setAttribute( att, attributes[ att ] );
 			}
 		}
@@ -749,21 +690,146 @@
 		// Assign all defined styles.
 		if ( styles )
-		{
-			for ( var styleName in styles )
-				el.setStyle( styleName, styles[ styleName ] );
-
-			if ( variables )
-			{
-				attValue = el.getAttribute( 'style' ).replace( styleVariableAttNameRegex, function()
-					{
-						// The second group in the regex is the variable name.
-						return variables[ arguments[2] ] || arguments[0];
-					});
-				el.setAttribute( 'style', attValue );
-			}
-		}
+			el.setAttribute( 'style', styles );
 
 		return el;
-	};
+	}
+
+	var varRegex = /#\((.+?)\)/g;
+	function replaceVariables( list, variablesValues )
+	{
+		for ( var item in list )
+		{
+			list[ item ] = list[ item ].replace( varRegex, function( match, varName )
+				{
+					return variablesValues[ varName ];
+				});
+		}
+	}
+
+	var spacesRegex = /\s+/g;
+
+	// Returns an object that can be used for style matching comparison.
+	// Attributes names and values are all lowercased, and the styles get
+	// merged with the style attribute.
+	function getAttributesForComparison( styleDefinition )
+	{
+		// If we have already computed it, just return it.
+		var attribs = styleDefinition._AC;
+		if ( attribs )
+			return attribs;
+
+		attribs = {};
+
+		var length = 0;
+
+		// Loop through all defined attributes.
+		var styleAttribs = styleDefinition.attributes;
+		if ( styleAttribs )
+		{
+			for ( var styleAtt in styleAttribs )
+			{
+				length++;
+				attribs[ styleAtt.toLowerCase() ] = styleAttribs[ styleAtt ].toLowerCase();
+			}
+		}
+
+		// Includes the style definitions.
+		var styleText = getStyleText( styleDefinition );
+		if ( styleText.length > 0 )
+		{
+			if ( !attribs[ 'style' ] )
+				length++;
+
+			attribs['style'] = styleText.replace( spacesRegex, '' ).toLowerCase();
+		}
+
+		// Appends the "length" information to the object.
+		attribs._length = length;
+
+		// Return it, saving it to the next request.
+		return ( styleDefinition._AC = attribs );
+	}
+
+	var semicolonFixRegex = /\s*(?:;\s*|$)/;
+
+	// Build the cssText based on the styles definition.
+	function getStyleText( styleDefinition )
+	{
+		// If we have already computed it, just return it.
+		var stylesDef = styleDefinition._ST;
+		if ( stylesDef )
+			return stylesDef;
+
+		stylesDef = styleDefinition.styles;
+
+		// Builds the StyleText.
+
+		var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '';
+
+		if ( stylesText.length )
+			stylesText = stylesText.replace( semicolonFixRegex, ';' );
+
+		for ( var style in stylesDef )
+			stylesText += style + ':' + stylesDef[ style ] + ';';
+
+		// Browsers make some changes to the style when applying them. So, here
+		// we normalize it to the browser format.
+		if ( stylesText.length )
+		{
+			stylesText = normalizeCssText( stylesText );
+
+			if ( stylesText.length )
+				stylesText = stylesText.replace( semicolonFixRegex, ';' );
+		}
+
+		// Return it, saving it to the next request.
+		return ( styleDefinition._ST = stylesText );
+	}
+
+	function normalizeCssText( unparsedCssText )
+	{
+		// Injects the style in a temporary span object, so the browser parses it,
+		// retrieving its final format.
+		var tempSpan = document.createElement( 'span' );
+		tempSpan.style.cssText = unparsedCssText;
+		return tempSpan.style.cssText;
+	}
+
+	// valueA is our internal "for comparison" value.
+	// valueB is the value retrieved from the element.
+	function compareAttributeValues( attName, valueA, valueB )
+	{
+		if ( valueA == valueB || ( !valueA && !valueB ) )
+			return true;
+		else if ( !valueA || !valueB )
+			return false;
+
+		valueB = valueB.toLowerCase();
+
+		if ( attName == 'style' )
+		{
+			valueB = valueB.replace( spacesRegex, '' );
+			if ( valueB.charAt( valueB.length - 1 ) != ';' )
+				valueB += ';';
+		}
+
+		// Return true if they match or if valueA is null and valueB is an empty string
+		return ( valueA == valueB );
+	}
+
+	function applyStyle( document, remove )
+	{
+		// Get all ranges from the selection.
+		var selection = document.getSelection();
+		var ranges = selection.getRanges();
+		var func = remove ? this.removeFromRange : this.applyToRange;
+
+		// Apply the style to the ranges.
+		for ( var i = 0 ; i < ranges.length ; i++ )
+			func.call( this, ranges[ i ] );
+
+		// Select the ranges again.
+		selection.selectRanges( ranges );
+	}
 })();
 
Index: /CKEditor/trunk/_source/tests/plugins/styles/styles.html
===================================================================
--- /CKEditor/trunk/_source/tests/plugins/styles/styles.html	(revision 3116)
+++ /CKEditor/trunk/_source/tests/plugins/styles/styles.html	(revision 3117)
@@ -180,5 +180,5 @@
 			style.applyToRange( range );
 
-			assert.areSame( '<b lang="it" style="font-size: 10pt; text-decoration: line-through;" title="test">this is some sample text</b>', getInnerHtml( '_P1' ) );
+			assert.areSame( '<b lang="it" style="font-size:10pt;text-decoration:line-through;" title="test">this is some sample text</b>', getInnerHtml( '_P1' ) );
 		},
 
@@ -235,5 +235,5 @@
 			style.applyToRange( range );
 
-			assert.areSame( '<span style="font-size: 1.5em;">this <span style="font-weight: 600;">is</span> some sample text</span>', getInnerHtml( '_P1' ) );
+			assert.areSame( '<span style="font-size:1.5em;">this <span style="font-weight:600;">is</span> some sample text</span>', getInnerHtml( '_P1' ) );
 		},
 
@@ -252,4 +252,73 @@
 		},
 
+		test_inline14 : function()
+		{
+			var para = doc.getById( '_P1' );
+
+			para.setHtml( 'this is some sample text' );
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( para.getFirst(), 0 );
+			range.setEnd( para.getFirst(), 7 );
+
+			var style = new CKEDITOR.style( { element : 'b' } );
+			style.applyToRange( range );
+
+			assert.areSame( '<b>this is</b> some sample text', getInnerHtml( '_P1' ), 'First range' );
+
+			para.setHtml( para.getHtml() );
+
+			range = new CKEDITOR.dom.range( doc );
+			range.setStart( para.getFirst().getFirst(), 5 );
+			range.setEnd( para.getChild( 1 ), 5 );
+
+			style.applyToRange( range );
+
+			assert.areSame( '<b>this is some</b> sample text', getInnerHtml( '_P1' ), 'Second range' );
+		},
+
+		test_inline15 : function()
+		{
+			var para = doc.getById( '_P1' );
+
+			para.setHtml( 'this is some sample text' );
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( para.getFirst(), 0 );
+			range.setEnd( para.getFirst(), 7 );
+
+			var style = new CKEDITOR.style( { element : 'span', styles : { 'font-family' : '#(family)' } }, { family : 'Arial,Helvetica,sans-serif' } );
+			style.applyToRange( range );
+
+			assert.areSame( '<span style="font-family:arial,helvetica,sans-serif;">this is</span> some sample text', getInnerHtml( '_P1' ), 'First range' );
+
+			para.setHtml( para.getHtml() );
+
+			range = new CKEDITOR.dom.range( doc );
+			range.setStart( para.getFirst().getFirst(), 5 );
+			range.setEnd( para.getChild( 1 ), 5 );
+
+			style = new CKEDITOR.style( { element : 'span', styles : { 'font-family' : '#(family)' } }, { family : 'Georgia,serif' } );
+			style.applyToRange( range );
+
+			assert.areSame( '<span style="font-family:arial,helvetica,sans-serif;">this <span style="font-family:georgia,serif;">is</span></span><span style="font-family:georgia,serif;"> some</span> sample text', getInnerHtml( '_P1' ), 'Second range' );
+		},
+
+		test_inline16 : function()
+		{
+			var para = doc.getById( '_P1' );
+
+			para.setHtml( '<b lang="pt" style="font-size:11pt;color:red;">this is some sample text</b>' );
+
+			var range = new CKEDITOR.dom.range( doc );
+			range.setStart( para.getFirst().getFirst(), 4 );
+			range.setEnd( para.getFirst(), 10 );
+
+			var style = new CKEDITOR.style( { element : 'b', styles : { color : 'red', 'font-weight' : '700' } } );
+			style.applyToRange( range );
+
+			assert.areSame( '<b lang="pt" style="font-size:11pt;color:red;">this<b style="font-weight:700;"> is some sample text</b></b>', getInnerHtml( '_P1' ), 'First range' );
+		},
+
 		test_inline_nobreak1 : function()
 		{
@@ -278,4 +347,76 @@
 
 			assert.areSame( 'this is some <strong><i>sample</i> text<\/strong>. you are using <a href="http://www.fckeditor.net/">fckeditor<\/a>.', getInnerHtml( '_P1' ) );
+		},
+		
+		test_checkElementRemovable1 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<b>Test</b>', doc );
+
+			var style = new CKEDITOR.style( { element : 'b' } );
+
+			assert.isTrue( style.checkElementRemovable( element ) );
+		},
+
+		test_checkElementRemovable2 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<b>Test</b>', doc );
+
+			var style = new CKEDITOR.style( { element : 'i' } );
+
+			assert.isFalse( style.checkElementRemovable( element ) );
+		},
+
+		test_checkElementRemovable3 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<b>Test</b>', doc );
+
+			var style = new CKEDITOR.style( { element : 'b', attributes : { lang : 'pt' } } );
+
+			assert.isTrue( style.checkElementRemovable( element ) );
+		},
+
+		test_checkElementRemovable4 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<b>Test</b>', doc );
+
+			var style = new CKEDITOR.style( { element : 'b', attributes : { lang : 'pt' } } );
+
+			assert.isFalse( style.checkElementRemovable( element, true ) );
+		},
+
+		test_checkElementRemovable5 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<span lang="pt" style="color : #fff">Test</span>', doc );
+
+			var style = new CKEDITOR.style( { element : 'span', attributes : { lang : 'pt' }, style : { color : '#ffffff' } } );
+
+			assert.isTrue( style.checkElementRemovable( element, true ) );
+		},
+
+		test_checkElementRemovable6 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<span lang="pt" style="color : #fff">Test</span>', doc );
+
+			var style = new CKEDITOR.style( { element : 'span', attributes : { lang : 'pt' }, style : { color : '#fffff0' } } );
+
+			assert.isTrue( style.checkElementRemovable( element, true ) );
+		},
+
+		test_checkElementRemovable7 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<span lang="pt" style="color : #fff">Test</span>', doc );
+
+			var style = new CKEDITOR.style( { element : 'span', attributes : { lang : 'fr' }, style : { color : '#ffffff' } } );
+
+			assert.isFalse( style.checkElementRemovable( element, true ) );
+		},
+
+		test_checkElementRemovable8 : function()
+		{
+			var element = CKEDITOR.dom.element.createFromHtml( '<span lang="pt" style="font-size: 10px">Test</span>', doc );
+
+			var style = new CKEDITOR.style( { element : 'span', attributes : { lang : 'pt' , style : 'font-size:10px;' } } );
+
+			assert.isTrue( style.checkElementRemovable( element, true ) );
 		},
 
@@ -286,5 +427,5 @@
 //window.onload = function()
 //{
-//	testCase.test_inline4();
+//	testCase.test_checkElementRemovable8();
 //}
 
