Index: /CKEditor/branches/prototype/_source/plugins/styles/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/styles/plugin.js	(revision 2618)
+++ /CKEditor/branches/prototype/_source/plugins/styles/plugin.js	(revision 2618)
@@ -0,0 +1,498 @@
+﻿/*
+ * CKEditor - The text editor for Internet - http://ckeditor.com
+ * Copyright (C) 2003-2008 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ *  - GNU General Public License Version 2 or later (the "GPL")
+ *    http://www.gnu.org/licenses/gpl.html
+ *
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ *    http://www.gnu.org/licenses/lgpl.html
+ *
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")
+ *    http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ */
+
+CKEDITOR.plugins.add( 'styles' );
+
+CKEDITOR.STYLE_BLOCK = 1;
+CKEDITOR.STYLE_INLINE = 2;
+CKEDITOR.STYLE_OBJECT = 3;
+
+(function()
+{
+	var blockElements	= { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
+	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 )
+	{
+		var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
+
+		this.type =
+			( element == '#' || blockElements[ element ] ) ?
+				CKEDITOR.STYLE_BLOCK
+			: objectElements[ element ] ?
+				CKEDITOR.STYLE_OBJECT
+			:
+				CKEDITOR.STYLE_INLINE;
+
+		this._ =
+		{
+			definition : styleDefinition
+		};
+	};
+
+	CKEDITOR.style.prototype =
+	{
+		apply : function( document )
+		{
+			// Get all ranges from the selection.
+			var selection = document.getSelection();
+			var ranges = selection.getRanges();
+
+			// Apply the style to the ranges.
+			for ( var i = 0 ; i < ranges.length ; i++ )
+				this.applyToRange( ranges[ i ] );
+
+			// Select the ranges again.
+			selection.selectRanges( ranges );
+		},
+
+		applyToRange : function( range )
+		{
+			return ( this.applyToRange =
+						this.type == CKEDITOR.STYLE_INLINE ?
+							applyInlineStyle
+						: this.type == CKEDITOR.STYLE_BLOCK ?
+							applyBlockStyle
+						: null ).call( this, range );
+		},
+
+		/**
+		 * 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 )
+	{
+		var document = range.document;
+
+		if ( range.collapsed )
+		{
+			// Create the element to be inserted in the DOM.
+			var collapsedElement = getElement( this, document );
+
+			// Insert the empty element into the DOM at the range position.
+			range.insertNode( collapsedElement );
+
+			// Place the selection right inside the empty element.
+			range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
+
+			return;
+		}
+
+		var elementName = this.element;
+		var def = this._.definition;
+		var isUnknownElement;
+
+		// Get the DTD definition for the element. Defaults to "span".
+		var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
+
+		// Bookmark the range so we can re-select it after processing.
+		var bookmark = range.createBookmark();
+
+		// Expand the range.
+		range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
+		range.trim();
+
+		// Get the first node to be processed and the last, which concludes the
+		// processing.
+		var firstNode = range.startContainer.getChild( range.startOffset ) || range.startContainer.getNextSourceNode();
+		var lastNode = range.endContainer.getChild( range.endOffset ) || ( range.endOffset ? range.endContainer.getNextSourceNode() : range.endContainer );
+
+		var currentNode = firstNode;
+
+		var styleRange;
+
+		// Indicates that that some useful inline content has been found, so
+		// the style should be applied.
+		var hasContents;
+
+		while ( currentNode )
+		{
+			var applyStyle = false;
+
+			if ( currentNode.equals( lastNode ) )
+			{
+				currentNode = null;
+				applyStyle = true;
+			}
+			else
+			{
+				var nodeType = currentNode.type;
+				var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
+
+				if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
+				{
+					currentNode = currentNode.getNextSourceNode( true );
+					continue;
+				}
+
+				// Check if the current node can be a child of the style element.
+				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 ) ) )
+				{
+					var currentParent = currentNode.getParent();
+
+					// Check if the style element can be a child of the current
+					// node parent or if the element is not defined in the DTD.
+					if ( currentParent && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) )
+					{
+						// This node will be part of our range, so if it has not
+						// been started, place its start right before the node.
+						// In the case of an element node, it will be included
+						// only if it is entirely inside the range.
+						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 ) ) )
+						{
+							styleRange = new CKEDITOR.dom.range( document );
+							styleRange.setStartBefore( currentNode );
+						}
+
+						// Non element nodes, or empty elements can be added
+						// completely to the range.
+						if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() && currentNode.$.offsetWidth ) )
+						{
+							var includedNode = currentNode;
+							var parentNode;
+
+							// This node is about to be included completelly, but,
+							// if this is the last node in its parent, we must also
+							// check if the parent itself can be added completelly
+							// to the range.
+							while ( !includedNode.$.nextSibling
+								&& ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
+								&& ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) )
+							{
+								includedNode = parentNode;
+							}
+
+							styleRange.setEndAfter( includedNode );
+
+							// If the included node still is the last node in its
+							// parent, it means that the parent can't be included
+							// in this style DTD, so apply the style immediately.
+							if ( !includedNode.$.nextSibling )
+								applyStyle = true;
+
+							if ( !hasContents )
+								hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
+						}
+					}
+					else
+						applyStyle = true;
+				}
+				else
+					applyStyle = true;
+
+				// Get the next node to be processed.
+				currentNode = currentNode.getNextSourceNode();
+			}
+
+			// Apply the style if we have something to which apply it.
+			if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
+			{
+				// Build the style element, based on the style object definition.
+				var styleNode = getElement( this, document );
+
+				var parent = styleRange.getCommonAncestor();
+
+				while ( styleNode && parent )
+				{
+					if ( parent.getName() == elementName )
+					{
+						for ( var attName in def.attribs )
+						{
+							if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
+								styleNode.removeAttribute( attName );
+						}
+
+						for ( var styleName in def.styles )
+						{
+							if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
+								styleNode.removeStyle( styleName );
+						}
+
+						if ( !styleNode.hasAttributes() )
+						{
+							styleNode = null;
+							break;
+						}
+					}
+
+					parent = parent.getParent();
+				}
+
+				if ( styleNode )
+				{
+					// Move the contents of the range to the style element.
+					styleRange.extractContents().appendTo( styleNode );
+
+					// Here we do some cleanup, removing all duplicated
+					// elements from the style element.
+					removeFromElement( this, styleNode );
+
+					// Insert it into the range position (it is collapsed after
+					// extractContents.
+					styleRange.insertNode( styleNode );
+
+					// Let's merge our new style with its neighbors, if possible.
+					mergeSiblings( styleNode );
+
+					// As the style system breaks text nodes constantly, let's normalize
+					// things for performance.
+					// With IE, some paragraphs get broken when calling normalize()
+					// repeatedly. Also, for IE, we must normalize body, not documentElement.
+					// IE is also known for having a "crash effect" with normalize().
+					// We should try to normalize with IE too in some way, somewhere.
+					if ( !CKEDITOR.env.ie )
+						styleNode.$.normalize();
+				}
+
+				// Style applied, let's release the range, so it gets
+				// re-initialization in the next loop.
+				styleRange = null;
+			}
+		}
+
+//		this._FixBookmarkStart( startNode );
+
+		range.moveToBookmark( bookmark );
+	};
+
+	var applyBlockStyle = function( range )
+	{
+	};
+
+	// Removes a style from inside an element.
+	var removeFromElement = function( style, element )
+	{
+		var def = style._.definition;
+		var attribs = def.attributes;
+		var styles = def.styles;
+
+		var innerElements = element.getElementsByTag( style.element );
+
+		for ( var i = innerElements.count() ; --i >= 0 ; )
+		{
+			var innerElement = innerElements.getItem( i );
+
+			for ( var attName in attribs )
+			{
+				// The 'class' element value must match (#1318).
+				if ( attName == 'class' && innerElement.getAttribute( 'class' ) != attribs[ attName ] )
+					continue;
+
+				innerElement.removeAttribute( attName );
+			}
+
+			for ( var styleName in styles )
+			{
+				innerElement.removeStyle( styleName );
+			}
+
+			removeNoAttribsElement( innerElement );
+		}
+	};
+
+	// If the element has no more attributes, remove it.
+	var removeNoAttribsElement = function( element )
+	{
+		// If no more attributes remained in the element, remove it,
+		// leaving its children.
+		if ( !element.hasAttributes() )
+		{
+			// Removing elements may open points where merging is possible,
+			// so let's cache the first and last nodes for later checking.
+			var firstChild	= element.getFirst();
+			var lastChild	= element.getLast();
+
+			element.remove( true );
+
+			if ( firstChild )
+			{
+				// Check the cached nodes for merging.
+				mergeSiblings( firstChild );
+
+				if ( lastChild && !firstChild.equals( lastChild ) )
+					mergeSiblings( lastChild );
+			}
+		}
+	};
+
+	// 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 )
+	{
+		if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
+			return;
+
+		mergeElements( element, element.getNext(), true );
+		mergeElements( element, element.getPrevious() );
+	};
+
+	var mergeElements = function( element, sibling, isNext )
+	{
+		if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
+		{
+			var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
+
+			if ( hasBookmark )
+				sibling = isNext ? sibling.getNext() : sibling.getPrevious();
+
+			if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.getName() == element.getName() )
+			{
+				// Save the last child to be checked too, to merge things like
+				// <b><i></i></b><b><i></i></b> => <b><i></i></b>
+				var innerSibling = isNext ? element.getLast() : element.getFirst();
+
+				if ( hasBookmark )
+					( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
+
+				sibling.moveChildren( element, !isNext );
+				sibling.remove();
+
+				// Now check the last inner child (see two comments above).
+				if ( innerSibling )
+					mergeSiblings( innerSibling );
+			}
+		}
+	};
+
+	// 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 )
+	{
+		var el = style._.element;
+
+		if ( el )
+			return el.clone();
+
+		var def = style._.definition;
+		var variables = style._.variables;
+
+		var elementName = style.element;
+		var attributes = def.attributes;
+		var styles = def.styles;
+
+		// The "*" element name will always be a span for this function.
+		if ( elementName == '*' )
+			elementName = 'span';
+
+		// Create the element.
+		el = new CKEDITOR.dom.element( elementName, targetDocument );
+
+		// Assign all defined attributes.
+		if ( attributes )
+		{
+			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 );
+			}
+		}
+
+		// 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 );
+			}
+		}
+
+		// Save the created element. It will be reused on future calls.
+		return ( style._.element = el );
+	};
+})();
+
+CKEDITOR.styleCommand = function( styleDefinition )
+{
+	this.style = new CKEDITOR.style( styleDefinition );
+};
+
+CKEDITOR.styleCommand.prototype.exec = function( editor )
+{
+	editor.focus();
+
+	var doc = editor.document;
+
+	if ( doc )
+		this.style.apply( doc );
+
+	return !!doc;
+};
