Index: /CKEditor/trunk/_source/core/htmlparser/fragment.js
===================================================================
--- /CKEditor/trunk/_source/core/htmlparser/fragment.js	(revision 3313)
+++ /CKEditor/trunk/_source/core/htmlparser/fragment.js	(revision 3314)
@@ -42,4 +42,10 @@
 	var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1};
 
+	// Block-level elements whose internal structure should be respected during
+	// parser fixing.
+	var nonBreakingBlocks = CKEDITOR.tools.extend(
+			{table:1,ul:1,ol:1,dl:1},
+			CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl );
+
 	/**
 	 * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
@@ -57,7 +63,8 @@
 			fragment = new CKEDITOR.htmlParser.fragment(),
 			pendingInline = [],
-			currentNode = fragment;
-
-		var checkPending = function( newTagName )
+			currentNode = fragment,
+			returnPoint;
+
+		function checkPending( newTagName )
 		{
 			if ( pendingInline.length > 0 )
@@ -77,5 +84,5 @@
 						// Add it to the current node and make it the current,
 						// so the new element will be added inside of it.
-						currentNode.add( pendingElement );
+						pendingElement.parent = currentNode;
 						currentNode = pendingElement;
 
@@ -87,11 +94,38 @@
 				}
 			}
-		};
+		}
+
+		function addElement( element, target, enforceCurrent )
+		{
+			target = target || currentNode || fragment;
+
+			// If the target is the fragment and this element can't go inside
+			// body (if fixForBody).
+			if ( fixForBody && !target.type && !CKEDITOR.dtd.$body[ element.name ] )
+			{
+				var savedCurrent = currentNode;
+
+				// Create a <p> in the fragment.
+				currentNode = target;
+				parser.onTagOpen( 'p', {} );
+
+				// The new target now is the <p>.
+				target = currentNode;
+
+				if ( enforceCurrent )
+					currentNode = savedCurrent;
+			}
+
+			target.add( element );
+
+			if ( element.returnPoint )
+			{
+				currentNode = element.returnPoint;
+				delete element.returnPoint;
+			}
+		}
 
 		parser.onTagOpen = function( tagName, attributes, selfClosing )
 		{
-			if ( fixForBody && !currentNode.type && !CKEDITOR.dtd.$body[ tagName ] )
-				this.onTagOpen( 'p', {} );
-
 			var element = new CKEDITOR.htmlParser.element( tagName, attributes );
 
@@ -124,16 +158,27 @@
 				// then just close the current one and append the new one to the
 				// parent. This situation usually happens with <p>, <li>, <dt> and
-				// <dd>, specially in IE.
-				if ( tagName != currentName )
-				{
-					// If it is optional to close the current element, then
-					// close it at this point and simply add the new
-					// element after it.
-					if ( !optionalClose[ currentName ] )
-					{
-						// The current element is an inline element, which
-						// cannot hold the new one. Put it in the pending list,
-						// and try adding the new one after it.
-						pendingInline.unshift( currentNode );
+				// <dd>, specially in IE. Do not enter in this if block in this case.
+				if ( tagName == currentName )
+				{
+					addElement( currentNode, currentNode.parent );
+				}
+				else
+				{
+					if ( nonBreakingBlocks[ currentName ] )
+					{
+						if ( !returnPoint )
+							returnPoint = currentNode;
+					}
+					else
+					{
+						addElement( currentNode, currentNode.parent, true );
+
+						if ( !optionalClose[ currentName ] )
+						{
+							// The current element is an inline element, which
+							// cannot hold the new one. Put it in the pending list,
+							// and try adding the new one after it.
+							pendingInline.unshift( currentNode );
+						}
 					}
 
@@ -143,5 +188,5 @@
 				// In any of the above cases, we'll be adding, or trying to
 				// add it to the parent.
-				currentNode = currentNode.parent;
+				currentNode = currentNode.returnPoint || currentNode.parent;
 
 				if ( reApply )
@@ -154,7 +199,11 @@
 			checkPending( tagName );
 
-			currentNode.add( element );
-
-			if ( !element.isEmpty )
+			element.parent = currentNode;
+			element.returnPoint = returnPoint;
+			returnPoint = 0;
+
+			if ( element.isEmpty )
+				addElement( element );
+			else
 				currentNode = element;
 		};
@@ -162,14 +211,15 @@
 		parser.onTagClose = function( tagName )
 		{
-			var closingElement = currentNode,
-				index = 0;
-
-			while ( closingElement && closingElement.name != tagName )
+			var index = 0,
+				pendingAdd = [],
+				candidate = currentNode;
+
+			while ( candidate.type && candidate.name != tagName )
 			{
 				// If this is an inline element, add it to the pending list, so
 				// it will continue after the closing tag.
-				if ( !closingElement._.isBlockLike )
-				{
-					pendingInline.unshift( closingElement );
+				if ( !candidate._.isBlockLike )
+				{
+					pendingInline.unshift( candidate );
 
 					// Increase the index, so it will not get checked again in
@@ -178,9 +228,30 @@
 				}
 
-				closingElement = closingElement.parent;
-			}
-
-			if ( closingElement )
-				currentNode = closingElement.parent;
+				// This node should be added to it's parent at this point. But,
+				// it should happen only if the closing tag is really closing
+				// one of the nodes. So, for now, we just cache it.
+				pendingAdd.push( candidate );
+
+				candidate = candidate.parent;
+			}
+
+			if ( candidate.type )
+			{
+				// Add all elements that have been found in the above loop.
+				for ( var i = 0 ; i < pendingAdd.length ; i++ )
+				{
+					var node = pendingAdd[ i ];
+					addElement( node, node.parent );
+				}
+
+				currentNode = candidate;
+
+				addElement( candidate, candidate.parent );
+
+				// The parent should start receiving new nodes now, except if
+				// addElement changed the currentNode.
+				if ( candidate == currentNode )
+					currentNode = currentNode.parent;
+			}
 			else if ( pendingInline.length > index )
 			{
@@ -224,5 +295,23 @@
 		};
 
+		// Parse it.
 		parser.parse( fragmentHtml );
+
+		// Close all pending nodes.
+		while ( currentNode.type )
+		{
+			var parent = currentNode.parent,
+				node = currentNode;
+
+			if ( fixForBody && !parent.type && !CKEDITOR.dtd.$body[ node.name ] )
+			{
+				currentNode = parent;
+				parser.onTagOpen( 'p', {} );
+				parent = currentNode;
+			}
+
+			parent.add( node );
+			currentNode = parent;
+		}
 
 		return fragment;
Index: /CKEditor/trunk/_source/tests/core/htmlparser/fragment.html
===================================================================
--- /CKEditor/trunk/_source/tests/core/htmlparser/fragment.html	(revision 3313)
+++ /CKEditor/trunk/_source/tests/core/htmlparser/fragment.html	(revision 3314)
@@ -1,3 +1,3 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -11,8 +11,20 @@
 	//<![CDATA[
 
-CKEDITOR.test.addTestCase( (function()
+var tc;
+
+CKEDITOR.test.addTestCase( tc = (function()
 {
 	// Local reference to the "assert" object.
 	var assert = CKEDITOR.test.assert;
+
+	function testParser( input, expected )
+	{
+		var fragment = CKEDITOR.htmlParser.fragment.fromHtml( input, true ),
+			writer = new CKEDITOR.htmlParser.basicWriter();
+
+		fragment.writeHtml( writer );
+
+		assert.areSame( expected, writer.getHtml( true ) );
+	}
 
 	return {
@@ -26,7 +38,118 @@
 		},
 
+		test_parser_1 : function()
+		{
+			testParser(	'<table><tr><td>1</td><p><b>2</b> Test</p><td>3</td></tr></table>',
+						'<p><b>2</b> Test</p><table><tr><td>1</td><td>3</td></tr></table>' );
+		},
+
+		test_parser_2 : function()
+		{
+			testParser(	'<b><table><tr><td>1</td><td>2</td></tr></table></b>',
+						'<table><tr><td><b>1</b></td><td><b>2</b></td></tr></table>' );
+		},
+
+		test_parser_3_1 : function()
+		{
+			testParser(	'<b><i>Table:<table><tr><td>1</td><td>2</td></tr></table></i></b>',
+						'<p><b><i>Table:</i></b></p><table><tr><td><b><i>1</i></b></td><td><b><i>2</i></b></td></tr></table>' );
+		},
+
+		test_parser_3_2 : function()
+		{
+			testParser(	'<b><i><table><tr><td>1</td><td>2</td></tr></table>Table</i></b>',
+						'<table><tr><td><b><i>1</i></b></td><td><b><i>2</i></b></td></tr></table><p><b><i>Table</i></b></p>' );
+		},
+
+		test_parser_4 : function()
+		{
+			testParser(	'<b><i>Test',
+						'<p><b><i>Test</i></b></p>' );
+		},
+
+		test_parser_5 : function()
+		{
+			testParser(	'<p>Para 1<p>Para 2<p>Para 3',
+						'<p>Para 1</p><p>Para 2</p><p>Para 3</p>' );
+		},
+
+		test_parser_6 : function()
+		{
+			testParser(	'<b>A</b><i>B</i>',
+						'<p><b>A</b><i>B</i></p>' );
+		},
+
+		test_parser_7 : function()
+		{
+			testParser(	'<p>Para 1<hr>Para 2<h1>Para 3',
+						'<p>Para 1</p><hr /><p>Para 2</p><h1>Para 3</h1>' );
+		},
+
+		/**
+		 * Test remove empty inline element.
+		 */
+		test_parser_8 : function()
+		{
+			testParser(	'<p><b></b>text</p>',
+						'<p>text</p>' );
+		},
+		
+		/**
+		 *  Test remove multiple empty inline elements. 
+		 */
+		test_parser_8_2 : function()
+		{
+			testParser(	'<p><b><i></b></i>text</p>',
+						'<p>text</p>' );
+		},
+
+		/**
+		 * Test fixing malformed inline element closing.
+		 */
+		test_parser_9 : function()
+		{
+			testParser(	'<p><b>bold<i>ita</b>lic</i></p>',
+						'<p><b>bold<i>ita</i></b><i>lic</i></p>' );
+		},
+
+		test_ticket_10 : function()
+		{
+			testParser(	'<table><tbody><tr><td>A<b></b></td></tr></tbody></table>',
+						'<table><tbody><tr><td>A</td></tr></tbody></table>' );
+		},
+		
+		/**
+		 *  Test fixing paragraph inside table row.
+		 */
+		test_ticket_3195 : function()
+		{
+			testParser(	'<table><tr><td>1</td><p>2</p><td>3</td></tr></table>',
+						'<p>2</p><table><tr><td>1</td><td>3</td></tr></table>' );
+		},
+		
+		/**
+		 *  Test fixing paragraph inside list.
+		 */
+		test_ticket_3195_2 : function()
+		{
+			testParser(	'<ul><li>1</li><p>2</p><li>3</li></ul>',
+						'<p>2</p><ul><li>1</li><li>3</li></ul>' );
+		},
+
+		/**
+		 *  Test fixing 'div' inside paragraph.
+		 */
+		test_ticket_3195_3 : function()
+		{
+			testParser(	'<p>1<div>2</div><span>3</span></p>',
+						'<p>1</p><div>2</div><p><span>3</span></p>' );
+		},
+		
 		name : document.title
 	};
 })() );
+
+// Uncomment the following to run a single test.
+// window.onload = tc.test_ticket_3195_2;
 
 	//]]>
