Index: _source/plugins/htmlwriter/plugin.js
===================================================================
--- _source/plugins/htmlwriter/plugin.js	(revision 3676)
+++ _source/plugins/htmlwriter/plugin.js	Tue Nov 24 09:32:36 CST 2009
@@ -67,7 +67,7 @@
 
 		var dtd = CKEDITOR.dtd;
 
-		for ( var e in CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ) )
+		for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) )
 		{
 			this.setRules( e,
 				{
Index: _source/core/tools.js
===================================================================
--- _source/core/tools.js	(revision 4452)
+++ _source/core/tools.js	Tue Nov 24 17:03:30 CST 2009
@@ -63,24 +63,14 @@
 		{
 			var clone;
 
-			// Array.
-			if ( obj && ( obj instanceof Array ) )
-			{
-				clone = [];
-
-				for ( var i = 0 ; i < obj.length ; i++ )
-					clone[ i ] = this.clone( obj[ i ] );
-
-				return clone;
-			}
-
 			// "Static" types.
 			if ( obj === null
 				|| ( typeof( obj ) != 'object' )
 				|| ( obj instanceof String )
 				|| ( obj instanceof Number )
 				|| ( obj instanceof Boolean )
-				|| ( obj instanceof Date ) )
+				|| ( obj instanceof Date )
+				|| ( obj instanceof RegExp ) )
 			{
 				return obj;
 			}
Index: _source/core/htmlparser/element.js
===================================================================
--- _source/core/htmlparser/element.js	(revision 4523)
+++ _source/core/htmlparser/element.js	Tue Nov 24 09:33:56 CST 2009
@@ -35,7 +35,7 @@
 	this.children = [];
 
 	var dtd			= CKEDITOR.dtd,
-		isBlockLike	= !!( dtd.$block[ name ] || dtd.$listItem[ name ] || dtd.$tableContent[ name ] || dtd.$nonEditable[ name ] || name == 'br' ),
+		isBlockLike	= !!( dtd.$nonBodyContent[ name ] || dtd.$block[ name ] || dtd.$listItem[ name ] || dtd.$tableContent[ name ] || dtd.$nonEditable[ name ] || name == 'br' ),
 		isEmpty		= !!dtd.$empty[ name ];
 
 	this.isEmpty	= isEmpty;
Index: _source/plugins/wysiwygarea/plugin.js
===================================================================
--- _source/plugins/wysiwygarea/plugin.js	(revision 4518)
+++ _source/plugins/wysiwygarea/plugin.js	Tue Nov 24 16:40:08 CST 2009
@@ -15,7 +15,7 @@
 	 */
 	var nonExitableElementNames = { table:1,pre:1 };
 	// Matching an empty paragraph at the end of document.
-	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)\s*(:?<\/\1>)?\s*$/gi;
+	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>|&nbsp;|\u00A0|&#160;)\s*(:?<\/\1>)?\s*(?=$|<\/body>)/gi;
 
 	function onInsertHtml( evt )
 	{
@@ -555,27 +555,29 @@
 
 								// Get the HTML version of the data.
 								if ( editor.dataProcessor )
-								{
-									data = editor.dataProcessor.toHtml( data, fixForBody );
-								}
+									data = editor.dataProcessor.toFullPageHtml( data, fixForBody );
 
+								if( !editor.config.fullPage )
+								{
-								data =
-									editor.config.docType +
-									'<html dir="' + editor.config.contentsLangDirection + '">' +
-									'<head>' +
-										'<link type="text/css" rel="stylesheet" href="' +
-										[].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) +
-										'">' +
-										'<style type="text/css" _fcktemp="true">' +
-											editor._.styles.join( '\n' ) +
-										'</style>'+
-									'</head>' +
-									'<body>' +
-										data +
-									'</body>' +
+									data =
+										editor.config.docType +
+										'<html dir="' + editor.config.contentsLangDirection + '">' +
+										'<head>' +
+											'<link type="text/css" rel="stylesheet" href="' +
+											[].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) +
+											'">' +
+											'<style type="text/css" _fcktemp="true">' +
+												editor._.styles.join( '\n' ) +
+											'</style>'+
+										'</head>' +
+										'<body>' +
+											data +
+										'</body>' +
-									'</html>' +
-									activationScript;
+										'</html>';
+								}
 
+								data += activationScript;
+
 								window[ '_cke_htmlToLoad_' + editor.name ] = data;
 								CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady;
 								createIFrame();
@@ -592,10 +594,12 @@
 
 							getData : function()
 							{
-								var data = iframe.getFrameDocument().getBody().getHtml();
+								var frameDoc = iframe.getFrameDocument(),
+									documentElement = frameDoc.getDocumentElement(),
+									data = editor.config.fullPage ? documentElement.getOuterHtml() : frameDoc.getBody().getHtml();
 
 								if ( editor.dataProcessor )
-									data = editor.dataProcessor.toDataFormat( data, fixForBody );
+									data = editor.dataProcessor.toFullPageData( data, fixForBody );
 
 								// Strip the last blank paragraph within document.
 								if ( editor.config.ignoreEmptyParagraph )
@@ -673,7 +677,7 @@
 							}
 						} ) + ')' );
 			}
-		} )();
+})();
 
 	}
 })();
Index: _source/core/dom/element.js
===================================================================
--- _source/core/dom/element.js	(revision 4534)
+++ _source/core/dom/element.js	Tue Nov 24 09:35:48 CST 2009
@@ -328,7 +328,10 @@
 		 */
 		getHtml : function()
 		{
-			return this.$.innerHTML;
+			var retval = this.$.innerHTML;
+			// Strip <?xml:namespace> tag in the output HTML of
+			// namespaced element in IE(#3341).
+			return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval;
 		},
 
 		getOuterHtml : function()
Index: _source/core/htmlparser/filter.js
===================================================================
--- _source/core/htmlparser/filter.js	(revision 4491)
+++ _source/core/htmlparser/filter.js	Tue Nov 24 16:59:08 CST 2009
@@ -45,6 +45,9 @@
 
 				// Add the comment.
 				this._.comment = transformNamedItem( this._.comment, rules.comment, priority ) || this._.comment;
+
+				// Add root fragment.
+				this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root;
 			},
 
 			onElementName : function( name )
@@ -69,6 +72,12 @@
 				return textFilter ? textFilter.filter( commentText ) : commentText;
 			},
 
+			onFragment : function( element )
+			{
+				var rootFilter = this._.root;
+				return rootFilter ? rootFilter.filter( element ) : element;
+			},
+
 			onElement : function( element )
 			{
 				// We must apply filters set to the specific element name as
@@ -111,6 +120,14 @@
 				}
 
 				return value;
+			},
+
+			clone : function()
+			{
+				var clone = new CKEDITOR.htmlParser.filter();
+				// Shallow copy all the rules.
+				clone._ = CKEDITOR.tools.clone( this._ );
+				return clone;
 			}
 		}
 	});
Index: _source/plugins/fullpage/plugin.js
===================================================================
--- _source/plugins/fullpage/plugin.js	Tue Nov 24 17:20:12 CST 2009
+++ _source/plugins/fullpage/plugin.js	Tue Nov 24 17:20:12 CST 2009
@@ -0,0 +1,200 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+/**
+ * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing
+ *		mode, which handles the main editing area space.
+ */
+
+( function()
+{
+	var docTypeRegex = /<!DOCTYPE[^>]*>\s*/i;
+	
+	var dtd = CKEDITOR.dtd;
+	CKEDITOR.plugins.add( 'fullpage',
+	{
+		requires : [ 'htmldataprocessor' ],
+		afterInit : function( editor )
+		{
+			if( editor.config.fullPage )
+			{
+				var fullPageDataFilterRules =
+				{
+					root : function( element )
+					{
+						// Adding missing parts of full page.
+						var firstChild = element.children[ 0 ];
+						// Full document.
+						if ( firstChild && firstChild.name == 'html' )
+							return;
+						// Missing <html>.
+						if ( !firstChild || firstChild.name in dtd.html )
+							wrapContent( element, 'html' );
+						// Missing <html> and <body>/<head>.
+						else
+						{
+							wrapContent( wrapContent( element, 'html' ),
+									firstChild.name in dtd.head ? 'head' : 'body' );
+						}
+					},
+
+					// Full page support. ( #4067 )
+					elements :
+					{
+						html : function ( element )
+						{
+							// Both <head> and <body> are mandatory.
+							if ( !childByTagName( element, 'head' ) )
+								element.children.splice( 0, 0, new CKEDITOR.htmlParser.element( 'head', {} ) );
+							if ( !childByTagName( element, 'body' ) )
+								element.children.splice( element.children.length, 0, new CKEDITOR.htmlParser.element( 'body', {} ) );
+
+							// 1. Save doc-type as a custom attribute;
+							// 2. Adding missing xml namespace attribute;
+							// 3. Adding missing language direction attribute.
+							var attrs = element.attributes,
+									configDir = editor.config.contentsLangDirection;
+							if( docType )
+							{
+								attrs.cke_doctype = encodeURIComponent( docType );
+								docType.match( /xhtml/i )
+										&& !attrs.xmlns
+										&& ( attrs.xmlns = 'http://www.w3.org/1999/xhtml' );
+								!attrs.dir && configDir && ( attrs.dir = configDir );
+							}
+						},
+
+						head : function ( element )
+						{
+							// <title> is mandatory.
+							if ( !childByTagName( element, 'title' ) )
+								element.children.splice( 0, 0, new CKEDITOR.htmlParser.element( 'title', {} ) );
+
+							// Adding default styles.
+							var styleLinks = [].concat( editor.config.contentsCss ),
+									styleText = editor._.styles.join( '\n' );
+
+							if ( styleLinks )
+							{
+								for ( var i = 0; i < styleLinks.length; i++ )
+								{
+									var link = new CKEDITOR.htmlParser.element( 'link',
+									{
+										type: 'text/css' ,
+										rel : 'stylesheet',
+										href : styleLinks[ i ],
+										cke_temp : 1
+									} );
+									element.add( link );
+								}
+							}
+							if ( styleText )
+							{
+								var style = new CKEDITOR.htmlParser.element( 'style',
+								{
+									type: 'text/css',
+									cke_temp : 1
+								} );
+								style.add( new CKEDITOR.htmlParser.text( styleText ) );
+								element.add( style );
+							}
+						}
+					}
+				};
+
+				var fullPageHtmlFilterRules =
+				{
+					elements :
+					{
+						html : function ( element )
+						{
+							var attrs = element.attributes,
+								docTypeAttr = attrs.cke_doctype;
+
+							if( docTypeAttr )
+							{
+								docType = decodeURIComponent( docTypeAttr );
+								delete attrs.cke_doctype;
+							}
+						},
+
+						body : function( element )
+						{
+							delete element.attributes.spellcheck;
+							delete element.attributes.contenteditable;
+						},
+						// Elements used interally in 'wysiwyg' mode shouldn't appear in output.
+						style : removeInternal,
+						link : removeInternal
+					},
+					attributeNames :
+					[
+						[ 'hidefocus', '' ]
+					]
+				};
+
+				var dataProcessor = editor.dataProcessor;
+				dataProcessor.fullPageDataFilter.addRules( fullPageDataFilterRules );
+				dataProcessor.fullPageHtmlFilter.addRules( fullPageHtmlFilterRules );
+
+				var docType = editor.config.docType;
+
+				function removeDocType( evt )
+				{
+					var data = evt.data;
+					// Record and sweep the doc-type declaration.
+					evt.data = data.replace( docTypeRegex, function( match )
+					{
+						docType = match;
+						return '';
+					} );
+				}
+
+				function prependDocType( evt )
+				{
+					// Prepend doc type to final data format.
+					if( docType )
+						evt.data = docType + '\n' + evt.data;
+				}
+
+				// Properly apply doc-type on html or vice versa.
+				dataProcessor.on( 'fullPageHtmlPreProcess', removeDocType );
+				dataProcessor.on( 'fullPageHtmlPostProcess', prependDocType );
+				dataProcessor.on( 'fullPageDataPostProcess', prependDocType );
+			}
+		}
+	} );
+
+	function removeInternal( element )
+	{
+		if( element.attributes.cke_temp )
+			return false;
+	}
+
+	function childByTagName( element, tagName )
+	{
+		var children = element.children,
+			child;
+		for ( var i = 0; i < children.length; i++ )
+		{
+			child = children[ i ];
+			if( child.name && child.name == tagName )
+				return child;
+		}
+	}
+	function wrapContent( element, tagName )
+	{
+		var childrens = element.children,
+			root = new CKEDITOR.htmlParser.element( tagName, {} );
+		element.children = [];
+		element.add( root );
+		for ( var i = 0; i < childrens.length; i++ )
+		{
+			root.add( childrens[ i ] );
+		}
+		return root;
+	}
+
+} )();
Index: _source/core/config.js
===================================================================
--- _source/core/config.js	(revision 4363)
+++ _source/core/config.js	Tue Nov 24 09:35:46 CST 2009
@@ -198,7 +198,7 @@
 	 * @type String
 	 * @example
 	 */
-	plugins : 'about,basicstyles,blockquote,button,clipboard,colorbutton,colordialog,contextmenu,elementspath,enterkey,entities,filebrowser,find,flash,font,format,forms,horizontalrule,htmldataprocessor,image,indent,justify,keystrokes,link,list,maximize,newpage,pagebreak,pastefromword,pastetext,popup,preview,print,removeformat,resize,save,scayt,smiley,showblocks,sourcearea,stylescombo,table,tabletools,specialchar,tab,templates,toolbar,undo,wysiwygarea,wsc',
+	plugins : 'about,basicstyles,blockquote,button,clipboard,colorbutton,colordialog,contextmenu,elementspath,enterkey,entities,filebrowser,find,flash,font,format,forms,fullpage,horizontalrule,htmldataprocessor,image,indent,justify,keystrokes,link,list,maximize,newpage,pagebreak,pastefromword,pastetext,popup,preview,print,removeformat,resize,save,scayt,smiley,showblocks,sourcearea,stylescombo,table,tabletools,specialchar,tab,templates,toolbar,undo,wysiwygarea,wsc',
 
 	/**
 	 * List of additional plugins to be loaded. This is a tool setting which
Index: _source/core/dtd.js
===================================================================
--- _source/core/dtd.js	(revision 4496)
+++ _source/core/dtd.js	Tue Nov 24 15:53:32 CST 2009
@@ -51,22 +51,33 @@
 		N = {'#':1},
 		O = X({param:1},K),
 		P = X({form:1},A,D,E,I),
-		Q = {li:1};
+		Q = {li:1},
+        R = {style:1,script:1},
+        S = {base:1,link:1,meta:1,title:1},
+		T = X(S,R),
+		U = {head:1,body:1},
+		V = {html:1};
 
-	var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1};
+	var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1},
+		inline = {a:1,abbr:1,acronym:1,b:1,basefont:1,bdo:1,big:1,br:1,cite:1,code:1,dfn:1,em:1,font:1,i:1,img:1,input:1,kbd:1,label:1,q:1,s:1,samp:1,select:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,textarea:1,tt:1,u:1,'var':1,applet:1,button:1,del:1,iframe:1,ins:1,map:1,object:1,script:1};
 
     return /** @lends CKEDITOR.dtd */ {
 
 		// The "$" items have been added manually.
 
+	    $ : V,
+
+	    // List of elements living outside body.
+	    $nonBodyContent: X(V,U,S),
+
-		/**
+	    /**
 		 * List of block elements, like "p" or "div".
 		 * @type Object
 		 * @example
 		 */
 		$block : block,
 
-		$body : X({script:1}, block),
+		$body : X({script:1,style:1}, block),
 
 		$cdata : {script:1,style:1},
 
@@ -120,7 +131,12 @@
 		 */
 		$tableContent : {caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1},
 
+        html: U,
+	    head: T,
+	    style: N,
+	    script: N,
+	    body: X(inline, block),
-        col : {},
+	    col : {},
         tr : {td:1,th:1},
         img : {},
         colgroup : {col:1},
@@ -200,7 +216,11 @@
         pre : X(G,C),
         p : L,
         em : L,
-        dfn : L
+        dfn : L,
+	    base: {},
+		link: {},
+	    meta: {},
+	    title: N
     };
 })();
 
Index: _source/core/htmlparser/basicwriter.js
===================================================================
--- _source/core/htmlparser/basicwriter.js	(revision 3308)
+++ _source/core/htmlparser/basicwriter.js	Tue Nov 24 15:53:32 CST 2009
@@ -117,6 +117,7 @@
 		reset : function()
 		{
 			this._.output = [];
+			this._.indent = false;
 		},
 
 		/**
Index: _source/core/htmlparser/fragment.js
===================================================================
--- _source/core/htmlparser/fragment.js	(revision 4373)
+++ _source/core/htmlparser/fragment.js	Tue Nov 24 15:53:32 CST 2009
@@ -114,7 +114,9 @@
 					elementName = realElementName;
 				else
 					elementName =  element.name;
-				if ( !( elementName in CKEDITOR.dtd.$body ) )
+				if ( elementName
+						&& !( elementName in CKEDITOR.dtd.$body )
+						&& !( elementName in CKEDITOR.dtd.$nonBodyContent )  )
 				{
 					var savedCurrent = currentNode;
 
@@ -180,15 +182,14 @@
 			}
 
 			var currentName = currentNode.name,
-				currentDtd = ( currentName && CKEDITOR.dtd[ currentName ] ) || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span );
+				currentDtd = currentName &&
+				             ( CKEDITOR.dtd[ currentName ]
+						       || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );
 
 			// If the element cannot be child of the current element.
-			if ( !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
+			if ( currentDtd   // Fragment could receive any elements.
+				 && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] )
 			{
-				// If this is the fragment node, just ignore this tag and add
-				// its children.
-				if ( !currentName )
-					return;
 
 				var reApply = false,
 					addPoint;   // New position to start adding nodes.
@@ -330,6 +331,9 @@
 					index--;
 				}
 			}
+
+			if( tagName == 'body' )
+				fixForBody = false;
 		};
 
 		parser.onText = function( text )
@@ -345,7 +349,9 @@
 
 			checkPending();
 
-			if ( fixForBody && !currentNode.type )
+			if ( fixForBody
+				 && CKEDITOR.tools.trim( text )
+				 && ( !currentNode.type || currentNode.name == 'body' ) )
 				this.onTagOpen( fixForBody, {} );
 
 			// Shrinking consequential spaces into one single for all elements
@@ -375,7 +381,9 @@
 			var parent = currentNode.parent,
 				node = currentNode;
 
-			if ( fixForBody && !parent.type && !CKEDITOR.dtd.$body[ node.name ] )
+			if ( fixForBody
+				 && ( !parent.type || parent.name == 'body' )
+				 && !CKEDITOR.dtd.$body[ node.name ] )
 			{
 				currentNode = parent;
 				parser.onTagOpen( fixForBody, {} );
@@ -444,6 +452,10 @@
 		 */
 		writeHtml : function( writer, filter )
 		{
+			// Filtering the root fragment.
+			if ( !this.name )
+				filter && filter.onFragment( this );
+
 			for ( var i = 0, len = this.children.length ; i < len ; i++ )
 				this.children[i].writeHtml( writer, filter );
 		}
Index: _source/plugins/htmldataprocessor/plugin.js
===================================================================
--- _source/plugins/htmldataprocessor/plugin.js	(revision 4453)
+++ _source/plugins/htmldataprocessor/plugin.js	Tue Nov 24 17:18:38 CST 2009
@@ -206,38 +206,51 @@
 		return html.replace( protectAttributeRegex, '$& _cke_saved_$1' );
 	}
 
-	var protectStyleTagsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>/gi;
-	var encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
+	var protectTagContentsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>|<(:?link|meta|base).*?>/gi,
+		encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
+
+	var shelveTagNamesRegex = /(<\/?)((?:html|body|head|title).*?>)/gi,
+		unshelveTagNamesRegex = /(<\/?)cke_shelved:([^>]*>)/gi;
+
 	var protectElementNamesRegex = /(<\/?)((?:object|embed|param)[\s\S]*?>)/gi;
 	var protectSelfClosingRegex = /<cke:(param|embed)([\s\S]*?)\/?>/gi;
 
-	function protectStyleTagsMatch( match )
+	function encodeMatched( match )
 	{
 		return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
 	}
+	function unprotectEncodedTagsMatch( match, encoded )
+	{
+		return decodeURIComponent( encoded );
+	}
 
-	function protectStyleTags( html )
+	function protectTagContents( html )
 	{
-		return html.replace( protectStyleTagsRegex, protectStyleTagsMatch );
+		return html.replace( protectTagContentsRegex, encodeMatched );
 	}
-	function protectElementsNames( html )
+	function unprotectTagContents( html )
 	{
-		return html.replace( protectElementNamesRegex, '$1cke:$2');
+		return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch );
 	}
-	function protectSelfClosingElements( html )
+
+	function shelveTagNames( html, isUnShelve )
 	{
-		return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
+		return html.replace( shelveTagNamesRegex, '$1cke_shelved:$2' );
 	}
 
-	function unprotectEncodedTagsMatch( match, encoded )
+	function unShelveTagNames( html )
 	{
-		return decodeURIComponent( encoded );
+		return html.replace( unshelveTagNamesRegex, '$1$2' );
 	}
 
-	function unprotectEncodedTags( html )
+	function protectElementsNames( html )
 	{
-		return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch );
+		return html.replace( protectElementNamesRegex, '$1cke:$2');
 	}
+	function protectSelfClosingElements( html )
+	{
+		return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
+	}
 
 	function protectSource( data, protectRegexes )
 	{
@@ -295,76 +308,103 @@
 			dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules );
 			dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules );
 			dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules );
-		}
-	});
 
-	CKEDITOR.htmlDataProcessor = function( editor )
-	{
-		this.editor = editor;
+			// Full page specialized filters. 
+			dataProcessor.fullPageHtmlFilter = dataProcessor.htmlFilter.clone();
+			dataProcessor.fullPageDataFilter = dataProcessor.dataFilter.clone();
 
-		this.writer = new CKEDITOR.htmlWriter();
-		this.dataFilter = new CKEDITOR.htmlParser.filter();
-		this.htmlFilter = new CKEDITOR.htmlParser.filter();
-	};
-
-	CKEDITOR.htmlDataProcessor.prototype =
+			// The source data is already HTML, but we need to clean it up.
+			dataProcessor.on( 'htmlPreProcess', function( evt )
-	{
+			{
-		toHtml : function( data, fixForBody )
-		{
-			// The source data is already HTML, but we need to clean
-			// it up and apply the filter.
-
+				var data = evt.data;
-			data = protectSource( data, this.editor.config.protectedSource );
+				data = protectSource( data, this.editor.config.protectedSource );
 
-			// Before anything, we must protect the URL attributes as the
-			// browser may changing them when setting the innerHTML later in
-			// the code.
-			data = protectAttributes( data );
+				// Before anything, we must protect the URL attributes as the
+				// browser may changing them when setting the innerHTML later in
+				// the code.
+				data = protectAttributes( data );
 
-			// IE remvoes style tags from innerHTML. (#3710).
-			if ( CKEDITOR.env.ie )
+				// IE remvoes style tags from innerHTML. (#3710).
+				if ( CKEDITOR.env.ie )
-				data = protectStyleTags( data );
+					data = protectTagContents( data );
 
-			// Certain elements has problem to go through DOM operation, protect
-			// them by prefixing 'cke' namespace.(#3591)
-			data = protectElementsNames( data );
+				// Certain elements has problem to go through DOM operation, protect
+				// them by prefixing 'cke' namespace.(#3591)
+				data = protectElementsNames( data );
 
-			// All none-IE browsers ignore self-closed custom elements,
-			// protecting them into open-close.(#3591)
-			data = protectSelfClosingElements( data );
+				// All none-IE browsers ignore self-closed custom elements,
+				// protecting them into open-close.(#3591)
+				data = protectSelfClosingElements( data );
 
+				data = shelveTagNames( data );
-			// Call the browser to help us fixing a possibly invalid HTML
-			// structure.
+				// Call the browser to help us fixing a possibly invalid HTML
+				// structure.
-			var div = document.createElement( 'div' );
+				var div = new CKEDITOR.dom.element( 'div' );
-			// Add fake character to workaround IE comments bug. (#3801)
+				// Add fake character to workaround IE comments bug. (#3801)
-			div.innerHTML = 'a' + data;
-			data = div.innerHTML.substr( 1 );
+				div.setHtml( 'a' + data ); 
+				data = div.getHtml().substr( 1 );
 
+				data = unShelveTagNames( data );
-			if ( CKEDITOR.env.ie )
+				if ( CKEDITOR.env.ie )
-				data = unprotectEncodedTags( data );
+					data = unprotectTagContents( data );
 
-			// Now use our parser to make further fixes to the structure, as
-			// well as apply the filter.
-			var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ),
-				writer = new CKEDITOR.htmlParser.basicWriter();
+				evt.data = data;
+			} );
+		}
+	});
 
-			fragment.writeHtml( writer, this.dataFilter );
+	CKEDITOR.htmlDataProcessor = function( editor )
+	{
+		this.editor = editor;
 
-			return writer.getHtml( true );
-		},
+		this.writer = new CKEDITOR.htmlWriter();
+		this.basicWriter = new CKEDITOR.htmlParser.basicWriter();
+		this.dataFilter = new CKEDITOR.htmlParser.filter();
+		this.htmlFilter = new CKEDITOR.htmlParser.filter();
+	};
 
-		toDataFormat : function( html, fixForBody )
+	CKEDITOR.htmlDataProcessor.prototype =
-		{
+	{
-			var writer = this.writer,
-				fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody );
+		process : function( type, data, filter, writer, fixForBody )
+		{
+			data = this.fire( type + 'PreProcess', data );
 
+			// Now use our parser to make fixes to the structure, as
+			// well as apply the filter.
+			var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody );
 			writer.reset();
+			fragment.writeHtml( writer, filter );
+			data = writer.getHtml( true );
 
-			fragment.writeHtml( writer, this.htmlFilter );
+			return this.fire( type + 'PostProcess', data );
+		},
 
-			return writer.getHtml( true );
+		toFullPageHtml : function( data, fixForBody )
+		{
+			data = this.fire( 'fullPageHtmlPreProcess', data );
+			data = this.process( 'html', data, this.fullPageDataFilter, this.basicWriter, fixForBody );
+			return this.fire( 'fullPageHtmlPostProcess', data )
+		},
+
+		toFullPageData : function( data, fixForBody )
+		{
+			data = this.fire( 'fullPageDataPreProcess', data );
+			data = this.process( 'dataFormat', data, this.fullPageHtmlFilter, this.writer, fixForBody );
+			return this.fire( 'fullPageDataPostProcess', data );
+		},
+
+		toHtml : function( data, fixForBody )
+		{
+			return this.process( 'html', data, this.dataFilter, this.basicWriter, fixForBody );
+		},
+
+		toDataFormat : function( html, fixForBody )
+		{
+			return this.process( 'dataFormat', html, this.htmlFilter, this.writer, fixForBody );
 		}
 	};
+
+	CKEDITOR.event.implementOn( CKEDITOR.htmlDataProcessor.prototype, true );
 })();
 
 /**

