Index: /CKEditor/branches/prototype/_source/core/htmlparser.js
===================================================================
--- /CKEditor/branches/prototype/_source/core/htmlparser.js	(revision 2847)
+++ /CKEditor/branches/prototype/_source/core/htmlparser.js	(revision 2848)
@@ -26,10 +26,12 @@
  */
 CKEDITOR.htmlParser = function()
-{};
+{
+	( this._ || ( this._ = {} ) ).htmlPartsRegex =
+		new RegExp('<(?:(?:\\/([^>]+)>)|(?:!--(.*?)-->)|(?:([^\\s>]+)\\s*((?:(?:[^"\'>]+)|(?:"[^"]*")|(?:\'[^\']*\'))*)\\/?>))', 'g');
+};
 
 (function()
 {
-	var htmlPartsRegex	= /<(?:(?:\/([^>]+)>)|(?:!--(.*?)-->)|(?:([^\s>]+)\s*((?:(?:[^"'>]+)|(?:"[^"]*")|(?:'[^']*'))*)\/?>))/g,
-		attribsRegex	= /([\w:]+)(?:=(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))?/g,
+	var attribsRegex	= /([\w:]+)(?:=(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))?/g,
 		emptyAttribs	= {checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1};
 
@@ -44,7 +46,9 @@
 		 *		property in this object represent and attribute name and its
 		 *		value is the attribute value.
+		 * @param {Boolean} selfClosing true if the tag closes itself, false if the
+		 * 		tag doesn't.
 		 * @example
 		 * var parser = new CKEDITOR.htmlParser();
-		 * parser.onTagOpener = function( tagName, attributes )
+		 * parser.onTagOpen = function( tagName, attributes, selfClosing )
 		 *     {
 		 *         alert( tagName );  // e.g. "b"
@@ -114,5 +118,5 @@
 				nextIndex = 0;
 
-			while ( ( parts = htmlPartsRegex.exec( html ) ) )
+			while ( ( parts = this._.htmlPartsRegex.exec( html ) ) )
 			{
 				var tagIndex = parts.index;
@@ -120,5 +124,5 @@
 					this.onText( html.substring( nextIndex, tagIndex ) );
 
-				nextIndex = htmlPartsRegex.lastIndex;
+				nextIndex = this._.htmlPartsRegex.lastIndex;
 
 				/*
@@ -143,5 +147,6 @@
 					var attribs = {},
 						attribMatch,
-						attribsPart = parts[ 4 ];
+						attribsPart = parts[ 4 ],
+						selfClosing = !!( attribsPart && attribsPart.charAt( attribsPart.length - 1 ) == '/' );
 
 					if ( attribsPart )
@@ -159,5 +164,5 @@
 					}
 
-					this.onTagOpen( tagName.toLowerCase(), attribs );
+					this.onTagOpen( tagName.toLowerCase(), attribs, selfClosing );
 					continue;
 				}
Index: /CKEditor/branches/prototype/_source/plugins/fakeobjects/plugin.js
===================================================================
--- /CKEditor/branches/prototype/_source/plugins/fakeobjects/plugin.js	(revision 2847)
+++ /CKEditor/branches/prototype/_source/plugins/fakeobjects/plugin.js	(revision 2848)
@@ -32,4 +32,5 @@
 			{
 				var flashExtensionRegex = /\.swf($|#|\?)/i,
+					emptyElements = { base:1,col:1,meta:1,link:1,hr:1,br:1,param:1,img:1,area:1,input:1 },
 					objectTypes =
 					[
@@ -51,5 +52,5 @@
 						}
 					],
-					makeTagOpenerHtml = function( tagName, attributes )
+					makeTagOpenerHtml = function( tagName, attributes, selfClosing )
 					{
 						var attribStr = [], html = [ '<' + tagName ];
@@ -58,4 +59,6 @@
 						if ( attribStr.length > 0 )
 							html.push( ' ', attribStr.join( ' ' ) );
+						if ( emptyElements[ tagName ] || selfClosing )
+							html.push( ' /' );
 						html.push( '>' );
 						return html.join( '' );
@@ -66,196 +69,307 @@
 						return decodeURIComponent( s1.substr( 1, s1.length - 2 ) );
 					},
-					cssWidthRegex = /width\s*:\s*([0-9]+)\s*(?:px)?/i,
-					cssHeightRegex = /height\s*:\s*([0-9]+)\s*(?:px)?/i;
+					cssWidthRegex = /width\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i,
+					cssHeightRegex = /height\s*:\s*([0-9]+)\s*(?:[\w%]+)?\s*;?/i;
+
+					var copyParser = function()
+					{
+						this._ = { html : null };
+						this.output = [];
+
+						CKEDITOR.htmlParser.call( this );
+					};
+					copyParser.prototype = {
+						onTagOpen : function( tagName, attributes, selfClosing )
+						{
+							this.output.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) );
+						},
+
+						onTagClose : function( tagName )
+						{
+							if ( !emptyElements[ tagName] )
+								this.output.push( '</' + tagName + '>' );
+						},
+
+						onText : function( text )
+						{
+							this.output.push( text );
+						},
+
+						onComment : function( comment )
+						{
+							this.output.push( '<!--' + comment + '-->' );
+						},
+
+						parse : function( html )
+						{
+							this._.html = html;
+							return CKEDITOR.htmlParser.prototype.parse.apply( this, arguments );
+						}
+					};
 				
-					/**
-					 * Manages element placeholders in WYSIWYG area.
-					 * @namespace
-					 * @example
-					 */
-					CKEDITOR.plugins.fakeobjects =
-					{
-						/**
-						 * Converts an element into a placeholder &lt;img&gt; element, for adding
-						 * into the WYSIWYG area.
-						 * @param {CKEDITOR.dom.element} element Input DOM element.
-						 * @returns {CKEDITOR.dom.element} The placeholder &lt;img&gt; element.
-						 * @example
-						 */
-						protectElement : function( element )
-						{
-							var $ = element.$.cloneNode( true ),
-								doc = $.ownerDocument,
-								temp = doc.createElement( 'div' ),
-								html;
-
-							// Get the object's HTML code.
-							temp.appendChild( $ );
-							html = temp.innerHTML;
-
-							// Get the fake element's CSS class.
-							var cssClass = 'unknown';
+				/**
+				 * Manages element placeholders in WYSIWYG area.
+				 * @namespace
+				 * @example
+				 */
+				CKEDITOR.plugins.fakeobjects =
+				{
+					/**
+					 * Converts an element into a placeholder &lt;img&gt; element, for adding
+					 * into the WYSIWYG area.
+					 * @param {CKEDITOR.dom.element} element Input DOM element.
+					 * @returns {CKEDITOR.dom.element} The placeholder &lt;img&gt; element.
+					 * @example
+					 */
+					protectElement : function( element )
+					{
+						var $ = element.$.cloneNode( true ),
+							doc = $.ownerDocument,
+							temp = doc.createElement( 'div' ),
+							html;
+
+						// Get the object's HTML code.
+						temp.appendChild( $ );
+						html = temp.innerHTML;
+
+						// Get the fake element's CSS class.
+						var cssClass = 'unknown';
+						for ( var i = 0 ; i < objectTypes.length ; i++ )
+						{
+							if ( objectTypes[i].match( element.getName(), element.$ ) )
+							{
+								cssClass = '_cke_fakeobject' + ' ' + objectTypes[i].cssClass;
+								break;
+							}
+						}
+
+						// Make the fake element.
+						var fakeRawElement = doc.createElement( 'img' ),
+							cssText = $.style.cssText,
+							widthMatch = cssText.match( cssWidthRegex ),
+							heightMatch = cssText.match( cssHeightRegex );
+						fakeRawElement.className = cssClass;
+						fakeRawElement.src = CKEDITOR.getUrl( 'images/spacer.gif' ); 
+						if ( widthMatch)
+							fakeRawElement.style.width = widthMatch[1] + 'px';
+						if ( heightMatch )
+							fakeRawElement.style.height = heightMatch[1] + 'px';
+						return new CKEDITOR.dom.element( fakeRawElement );
+					},
+
+					/**
+					 * Converts a placeholder &lt;img&gt; element back to the real element.
+					 * @param {CKEDITOR.dom.element} fakeImgElement The placeholder &lt;img&gt;.
+					 * @returns {CKEDITOR.dom.element} The real element.
+					 * @example
+					 */
+					restoreElement : function( fakeImgElement )
+					{
+						var html = decodeURIComponent( fakeImgElement.getAttribute( '_cke_protected_html' ) ),
+							realElement = CKEDITOR.dom.element.createFromHtml( html, editor.document );
+
+						if ( fakeImgElement.$.style.width )
+							realElement.setStyle( 'width', fakeImgElement.$.style.width );
+						if ( fakeImgElement.$.style.height )
+							realElement.setStyle( 'height', fakeImgElement.$.style.height );
+
+						return realElement;
+					},
+
+					/**
+					 * Converts protectable elements in an HTML string to placeholders.
+					 * @param {String} html HTML with protectable elements.
+					 * @returns {String} HTML with placeholders.
+					 * @example
+					 */
+					protectHtml : function( html )
+					{
+						var parser = new CKEDITOR.htmlParser(),
+							tagDepth = 0, processedHtml = [],
+							protectedHtml = [], inProtection = false;
+
+						parser.onTagOpen = function( tagName, attributes, selfClosing )
+						{
+							if ( inProtection )
+							{
+								protectedHtml.push( makeTagOpenerHtml( tagName, attributes, selfClosing ) );
+								if ( !( emptyElements[ tagName ] || selfClosing ) )
+									tagDepth++;
+								if ( tagDepth < 1 )
+								{
+									inProtection = false;
+									processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' );
+								}
+								return;
+							}
+
 							for ( var i = 0 ; i < objectTypes.length ; i++ )
 							{
-								if ( objectTypes[i].match( element.getName(), element.$ ) )
+								if ( objectTypes[i].match( tagName, attributes ) )
 								{
-									cssClass = '_cke_fakeobject' + ' ' + objectTypes[i].cssClass;
-									break;
-								}
-							}
-
-							// Make the fake element.
-							var fakeRawElement = doc.createElement( 'img' ),
-								cssText = $.style.cssText,
-								widthMatch = cssText.match( cssWidthRegex ) || [ 80, 80 ],
-								heightMatch = cssText.match( cssHeightRegex ) || [ 80, 80 ];
-							fakeRawElement.className = cssClass;
-							fakeRawElement.src = CKEDITOR.getUrl( 'images/spacer.gif' ); 
-							fakeRawElement.style.width = widthMatch[1] + 'px';
-							fakeRawElement.style.height = heightMatch[1] + 'px';
-							return new CKEDITOR.dom.element( fakeRawElement );
-						},
-
-						/**
-						 * Converts a placeholder &lt;img&gt; element back to the real element.
-						 * @param {CKEDITOR.dom.element} fakeImgElement The placeholder &lt;img&gt;.
-						 * @returns {CKEDITOR.dom.element} The real element.
-						 * @example
-						 */
-						restoreElement : function( fakeImgElement )
-						{
-							var html = decodeURIComponent( fakeImgElement.getAttribute( '_cke_protected_html' ) ),
-								realElement = CKEDITOR.dom.element.createFromHtml( html, editor.document );
-
-							return realElement;
-						},
-
-						/**
-						 * Converts protectable elements in an HTML string to placeholders.
-						 * @param {String} html HTML with protectable elements.
-						 * @returns {String} HTML with placeholders.
-						 * @example
-						 */
-						protectHtml : function( html )
-						{
-							var parser = new CKEDITOR.htmlParser(),
-								tagDepth = 0, processedHtml = [],
-								protectedHtml = [], inProtection = false;
-
-							parser.onTagOpen = function( tagName, attributes )
-							{
-								if ( inProtection )
-								{
-									protectedHtml.push( makeTagOpenerHtml( tagName, attributes ) );
+									inProtection = true;
+									tagDepth = 0;
+
+									// Get the original object's width and height.
+									var styles = attributes.style || '',
+										widthMatch = styles.match( cssWidthRegex ),
+										heightMatch = styles.match( cssHeightRegex );
+
+									// Create the fake <img> tag.
+									processedHtml.push( '<img src="',
+										CKEDITOR.getUrl( 'images/spacer.gif' ),
+										'" ',
+										'class="_cke_fakeobject ' + objectTypes[i].cssClass + '" ' );
+
+									if ( widthMatch || heightMatch )
+									{
+										processedHtml.push( 'style="',
+											widthMatch ? 'width:' + widthMatch[1] + 'px;' : '',
+											heightMatch ? 'height:' + heightMatch[1] + 'px;' : '',
+											'" ' );
+									}
+
+									processedHtml.push( '_cke_protected_html="' );
+									arguments.callee.call( this, tagName, attributes, selfClosing );
 									return;
 								}
-
-								for ( var i = 0 ; i < objectTypes.length ; i++ )
+							}
+
+							processedHtml.push( makeTagOpenerHtml( tagName, attributes ) );
+						};
+
+						parser.onText = function( text )
+						{
+							inProtection ? protectedHtml.push( text ) : processedHtml.push( text );
+						};
+
+						parser.onComment = function( comment )
+						{
+							inProtection ? protectedHtml.push( '<!--' + comment + '-->' ) : processedHtml.push( '<!--' + comment + '-->' );
+						};
+
+						parser.onTagClose = function( tagName )
+						{
+							if ( inProtection )
+							{
+								if ( !emptyElements[ tagName ] )
+									tagDepth--;
+								protectedHtml.push( '</' + tagName + '>' );
+								if ( tagDepth < 1 )
 								{
-									if ( objectTypes[i].match( tagName, attributes ) )
-									{
-										inProtection = true;
-										tagDepth = 1;
-
-										// Get the original object's width and height.
-										var styles = attributes.style || '',
-											widthMatch = styles.match( cssWidthRegex ) || [ 80, 80 ],
-											heightMatch = styles.match( cssHeightRegex ) || [ 80, 80 ];
-
-										// Create the fake <img> tag.
-										processedHtml.push( '<img src="',
-											CKEDITOR.getUrl( 'images/spacer.gif' ),
-											'" ',
-											'class="_cke_fakeobject ' + objectTypes[i].cssClass + '" ',
-											'style="width:' + widthMatch[1] + 'px;height:' + heightMatch[1] + 'px" ',
-											'_cke_protected_html="');
-										arguments.callee.call( this, tagName, attributes );
-										return;
-									}
+									inProtection = false;
+									processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' );
 								}
-
-								processedHtml.push( makeTagOpenerHtml( tagName, attributes ) );
-							};
-
-							parser.onText = function( text )
-							{
-								inProtection ? protectedHtml.push( text ) : processedHtml.push( text );
-							};
-
-							parser.onComment = function( comment )
-							{
-								inProtection ? protectedHtml.push( '<!--' + comment + '-->' ) : processedHtml.push( '<!--' + comment + '-->' );
-							};
-
-							parser.onTagClose = function( tagName )
-							{
-								if ( inProtection )
+							}
+							else
+								processedHtml.push( '</' + tagName + '>' );
+						};
+
+						parser.parse( html );
+						return processedHtml.join( '' );
+					},
+
+					/**
+					 * Restores placeholders in an HTML string back to their original elements.
+					 * @param {String} html HTML with placeholders.
+					 * @returns {String} Restored HTML.
+					 * @example
+					 */
+					restoreHtml : function( html )
+					{
+						/*
+						var protectedHtml = html.replace( protectedHtmlRegex, protectHtmlRestore_ReplaceTags ),
+							protectedParser = new CKEDITOR.htmlParser(),
+							placeholderParser = new CKEDITOR.htmlParser(),
+							processedHtml = [];
+						*/
+						var parser = new copyParser(),
+							innerParser = new copyParser();
+						
+						innerParser.onTagOpen = function( tagName, attributes, selfClosing )
+						{
+							if ( !this.done )
+							{
+								var styles = attributes.style || '';
+
+								styles = styles.replace( cssWidthRegex, '' ).replace( cssHeightRegex, '' );
+								if ( this.width )
+									styles += 'width:' + this.width + ';';
+								if ( this.height )
+									styles += 'height:' + this.height + ';';
+								attributes.style = styles;
+
+								this.done = true;
+							}
+
+							return copyParser.prototype.onTagOpen.apply( this, arguments );
+						};
+
+						parser.onTagOpen = function( tagName, attributes, selfClosing )
+						{
+							if ( tagName == 'img' && attributes._cke_protected_html !== undefined )
+							{
+								var styles = attributes.style || '',
+									protectedHtml = decodeURIComponent( attributes._cke_protected_html ),
+									widthMatch = styles.match( cssWidthRegex ),
+									heightMatch = styles.match( cssHeightRegex );
+
+								if ( widthMatch || heightMatch )
 								{
-									tagDepth--;
-									protectedHtml.push( '</' + tagName + '>' );
-									if ( tagDepth < 1 )
-									{
-										inProtection = false;
-										processedHtml.push( encodeURIComponent( protectedHtml.join( '' ) ), '" />' );
-									}
+									innerParser.width = widthMatch[1] + 'px';
+									innerParser.height = heightMatch[1] + 'px';
+									innerParser.parse( protectedHtml );
+									protectedHtml = innerParser.output.join( '' );
 								}
-								else
-									processedHtml.push( '</' + tagName + '>' );
-							};
-
-							parser.parse( html );
-							return processedHtml.join( '' );
-						},
-
-						/**
-						 * Restores placeholders in an HTML string back to their original elements.
-						 * @param {String} html HTML with placeholders.
-						 * @returns {String} Restored HTML.
-						 * @example
-						 */
-						restoreHtml : function( html )
-						{
-							return html.replace( protectedHtmlRegex, protectHtmlRestore_ReplaceTags );
-						},
-
-						/**
-						 * Adds an object type to be displayed by placeholders.
-						 * @param {Function} matchFunc A predicate function to determine whether
-						 * an object needs to be replaced by placeholders or not. The function
-						 * should have the following signature:
-						 * <blockquote>function( tagName, attributes )</blockquote>
-						 * In which tagName is the object's tag name, and attributes is an object
-						 * storing all the object's attributes.
-						 * @param {String} cssClass The CSS class that should be added to the
-						 * placeholder &lt;img&gt; for representing this type of object.
-						 * @param {Number} priority (Optional) An integer representing the
-						 * priority of this type of object. If an element matches the descriptions
-						 * of two object types, the object type of <strong>lower</strong> priority
-						 * takes precedance.
-						 * @example
-						 */
-						addObjectType : function( matchFunc, cssClass, priority )
-						{
-							if ( priority === undefined )
-								priority = 10;
-
-							var obj = {
-								match : matchFunc,
-								cssClass : cssClass,
-								priority : priority
-							};
-
-							for ( var i = objectTypes.length - 1 ; i >= 0 ; i-- )
-							{
-								if ( objectTypes[i].priority < priority )
-								{
-									objectTypes.splice( i + 1, 0, obj );
-									return;
-								}
-							}
-
-							objectTypes.unshift( obj );
+
+								this.output.push( protectedHtml );
+								return;
+							}
+							return copyParser.prototype.onTagOpen.apply( this, arguments );
+						};
+
+						parser.parse( html );
+						return parser.output.join( '' );
+					},
+
+					/**
+					 * Adds an object type to be displayed by placeholders.
+					 * @param {Function} matchFunc A predicate function to determine whether
+					 * an object needs to be replaced by placeholders or not. The function
+					 * should have the following signature:
+					 * <blockquote>function( tagName, attributes )</blockquote>
+					 * In which tagName is the object's tag name, and attributes is an object
+					 * storing all the object's attributes.
+					 * @param {String} cssClass The CSS class that should be added to the
+					 * placeholder &lt;img&gt; for representing this type of object.
+					 * @param {Number} priority (Optional) An integer representing the
+					 * priority of this type of object. If an element matches the descriptions
+					 * of two object types, the object type of <strong>lower</strong> priority
+					 * takes precedance.
+					 * @example
+					 */
+					addObjectType : function( matchFunc, cssClass, priority )
+					{
+						if ( priority === undefined )
+							priority = 10;
+
+						var obj = {
+							match : matchFunc,
+							cssClass : cssClass,
+							priority : priority
+						};
+
+						for ( var i = objectTypes.length - 1 ; i >= 0 ; i-- )
+						{
+							if ( objectTypes[i].priority < priority )
+							{
+								objectTypes.splice( i + 1, 0, obj );
+								return;
+							}
 						}
-					};
+
+						objectTypes.unshift( obj );
+					}
+				};
 			}
 		} );
Index: /CKEditor/branches/prototype/contents.css
===================================================================
--- /CKEditor/branches/prototype/contents.css	(revision 2847)
+++ /CKEditor/branches/prototype/contents.css	(revision 2848)
@@ -35,4 +35,6 @@
 	background-repeat: no-repeat;
 	border: 1px solid #a9a9a9;
+	width: 80px;
+	height: 80px;
 }
 
