// $Revision: 12 $
/*
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 * Copyright (C) 2003-2007 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 ==
 *
 * This is a sample implementation for a custom Data Processor for phpBB.
 * check the following files:
 *  styles/xxx/template/bbcode.html
 *  includes/bbcode.php
 * for a list of available bbcodes
 */

FCK.DataProcessor =
{
	_replaceTab 	: new Array,
	_replaceCounter : 0,
	_bbcode_array   : new Object,
	_bbcode_counter : 0,
	_bbcode_tag		: "",
	_smile_counter	: 0,
	_smiley_regex	: "",
	_mode			: "",
	_data			: "",
	_inColor		: 0,
	_inSize			: 0,
	_inList			: new Array,

	// built-in support for custom BBcodes
	_bbcodeSupport  : {
		table	: 0,
		hr		: 0
	},

	// Collection of element definitions:
	//		0 : Prefix
	//		1 : Suffix
	//		2 : Ignore children
	_BasicElements : {
		body	: [ ],
		b		: [ "[b]", "[/b]" ],
		u		: [ "[u]", "[/u]" ],
		i		: [ "[i]", "[/i]" ],
		em		: [ "[i]", "[/i]" ],
		li		: [ "[*]", "[/*]" ]
	},

	/*
	 * Returns a string representing the HTML format of "data". The returned
	 * value will be loaded in the editor.
	 * The HTML must be from <html> to </html>, eventually including
	 * the DOCTYPE.
	 *     @param {String} data The data to be converted in the
	 *            DataProcessor specific format.
	 */
	ConvertToHtml : function( data )
	{
		return '<html><head><title></title></head><body class="content" id="fckcontent">' + FCK.DataProcessor.ConvertDataToHtmlTags( data ) + '</body></html>' ;
	},

	/**
	 * Convert custom BBcode tag definition to valid regex pattern
	 */
	_PatternToRegex : function( bbcode_match )
	{
		bbcode_match = FCK.DataProcessor._QuoteMeta( bbcode_match ) ;
		bbcode_match = bbcode_match.replace( /{text\d*}/gi, "(.*?)" ) ;
		bbcode_match = bbcode_match.replace( /{attrlist\d*}/gi, "(\\s*(?:(?:align|bgcolor|border|bordercolor|cellpadding|cellspacing|colspan|height|rowspan|summary|valign|width)=(?:\\\".*?\\\"|\\S*?)\\s*)*?)" ) ;
		bbcode_match = bbcode_match.replace( /{email\d*}/gi, "([a-z0-9]+[a-z0-9\\-\\._]*@(?:(?:[0-9]{1,3}\\.){3,5}[0-9]{1,3}|[a-z0-9]+[a-z0-9\\-\\._]*\\.[a-z]+))" ) ;
		bbcode_match = bbcode_match.replace( /{simpletext\d*}/gi, "([a-zA-Z0-9-+.,_ ]+)" ) ;
		bbcode_match = bbcode_match.replace( /{identifier\d*}/gi, "([a-zA-Z0-9-_]+)" ) ;
		bbcode_match = bbcode_match.replace( /{color\d*}/gi, "([a-z]+|#[0-9abcdef]+)" ) ;
		bbcode_match = bbcode_match.replace( /{number\d*}/gi, "([0-9]+)" ) ;

		return bbcode_match ;
	},

	/**
	 * Convert bbcode text to HTML
	 * Important notes:
	 * When the page loads, value is set to "_fck_first_run".
	 * The real content is in iframe.
	 * Text between [code] tags is htmlencoded.
	 */
	ConvertDataToHtmlTags : function( data )
	{
		if ( FCK.DataProcessor._mode == "html" )
			return data ;

		var firstRun = false ;

		if ( data == '_fck_first_run' )
		{
			data = window.parent.document.getElementById("postingiframe").innerHTML ;
			firstRun = true ;
		}

		var i = 0 ;
		FCK.DataProcessor._replaceCounter = 0 ;
		FCK.DataProcessor._replaceTab = new Array ;

		data = data.replace( /<!--\{(\d+)\}-->/gi, FCK.DataProcessor._MatchCommentToData );

		FCKTempBin.Reset() ;

		// Convert < & > to their HTML entities.
		data = FCKTools.HTMLEncode( data ) ;

		// Convert line breaks to <br>.
		data = data.replace( /(?:\r\n|\n|\r)/g, '<br>' ) ;

		// [code] part 1
		data = FCK.DataProcessor._ConvertCodeToHtml( data, 0, firstRun ) ;

		if ( firstRun )
		{
			data = data.replace( /&gt;?/g, ">").replace( /&lt;?/g, "<").replace( /&nbsp;?/g," ").replace( /&quot;?/g, '"' ).replace( /&amp;?/g, "&") ;
			data = data.replace( /&quot;?/g, '"' ) ;
		}

		// custom bbcodes
		if( window.parent.fck_bb_tpl && window.parent.fck_bb_match )
		{
			var bbcode_match = "";
			var bbcode_tpl = "";

			for ( i=0 ; i < window.parent.fck_bb_tpl.length ; i++ )
			{
				bbcode_match = window.parent.fck_bb_match[i] ;
				bbcode_tpl = window.parent.fck_bb_tpl[i] ;

				FCK.DataProcessor._bbcode_array = new Object ;
				FCK.DataProcessor._bbcode_counter = 0 ;
				bbcode_tpl = bbcode_tpl.replace( /{(URL|LOCAL_URL|EMAIL|TEXT|ATTRLIST|SIMPLETEXT|IDENTIFIER|COLOR|NUMBER)[0-9]*\}/gi, FCK.DataProcessor._MatchCustomBbcode );

				re_bbcode = new RegExp( FCK.DataProcessor._PatternToRegex( bbcode_match ), "gi" ) ;

				if ( bbcode_tpl != '' )
					data = data.replace( re_bbcode, bbcode_tpl ) ;
				else
				{
					FCK.DataProcessor._bbcode_tag = window.parent.fck_bb_tag[i] ;
					data = data.replace( re_bbcode, FCK.DataProcessor._MatchFakeBbcode ) ;
				}
			}
		}

		if (window.parent.fckUrlStatus)
		{
			// [email]
			data = data.replace( /\[email\](.+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>' ) ;
			data = data.replace( /\[email\=([^\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>' ) ;

			// [url]
			data = data.replace( /\[url\](.+?)\[\/url]/gi, '<a href="$1">$1</a>' ) ;
			data = data.replace( /\[url\=([^\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>' ) ;
		}

		// [attachment]
		data = data.replace( /\[attachment=(\d+)\]([^\[]+)?\[\/attachment\]/gi, FCK.DataProcessor._MatchAttachmentToHtml ) ;

		// [list]
		data = FCK.DataProcessor._ConvertListToHtml( data ) ;

		// [b]
		data = data.replace( /\[b\](.+?)\[\/b]/gi, '<b>$1</b>' ) ;

		// [i]
		data = data.replace( /\[i\](.+?)\[\/i]/gi, '<i>$1</i>' ) ;

		// [u]
		data = data.replace( /\[u\](.+?)\[\/u]/gi, '<u>$1</u>' ) ;

		// [color]
		data = data.replace( /\[color\=#([0-9a-f]{6})](.+?)\[\/color]/gi, '<span style="color:$1;">$2</span>' ) ;
		data = data.replace( /\[color\=([a-z\-]+)](.+?)\[\/color]/gi, '<font color="$1">$2</font>' ) ;

		// [size]
		data = data.replace( /\[size\=([\-\+]?(?!100)\d+)\](.*?)\[\/size\]/gi, '<span style="font-size: $1%;">$2</span>' ) ;
		data = data.replace( /\[size\=([\-\+]?100)\](.*?)\[\/size\]/gi, '$2' ) ;

		// [quote]
		data = FCK.DataProcessor._ConvertQuoteToHtml( data ) ;

		// [img]
		if (window.parent.fckImgStatus)
			data = data.replace( /\[img\](.+?)\[\/img\]/gi, '<img alt="" src="$1"/>');

		// [flash]
		if ( window.parent.fckFlashStatus )
		{
			data = data.replace( /\[flash=(\d+),(\d+)\](.+?)\[\/flash\]/gi, FCK.DataProcessor._MatchFlashToHtml ) ;
		}

		// smiles
		if ( window.parent.fck_smiley_code && window.parent.fck_smiley_img )
		{
			for ( i = 0 ; i < window.parent.fck_smiley_code.length ; i++ )
			{
				FCK.DataProcessor._smile_counter = i ;

				var re_smiley = new RegExp( "(?:^|[\\n .])(" + FCK.DataProcessor._QuoteMeta( window.parent.fck_smiley_code[i] ) + ")(?![^<>]*>)", "gi" ) ;
				FCK.DataProcessor._smiley_regex = re_smiley ;
				data = data.replace( re_smiley, FCK.DataProcessor._MatchSmileToHtml ) ;
			}
		}

		// [code] part 2
		for( i = 0; i < FCK.DataProcessor._replaceTab.length; i++ )
			data = data.replace( "__FCKREPLACE" + i + "__", FCK.DataProcessor._replaceTab[i] ) ;

		// replace multiple spaces with &nbsp;
		data = FCK.DataProcessor._ReplaceSpacesWithEntities( data ) ;
		FCK.DataProcessor._mode = "html" ;

		if (!data.length)
			data = "&nbsp;" ;

		return data ;
	},

	_MatchCommentToData : function ( sMatch )
	{
		return FCKConfig.ProtectedSource.Revert( sMatch, false ) ;
	},

	/**
	 * Called when custom bbcode tag is found, create fake element and return SPAN entity
	 */
	_MatchFakeBbcode : function ( sMatch )
	{
		oText = document.createTextNode( sMatch ) ;
		_fckrealelement = FCKTempBin.AddElement( oText ) ;

		return '<span _fckbbcode="' + FCK.DataProcessor._bbcode_tag + '" _fckfakeelement="true" _fckrealelement="' + _fckrealelement + '" style="color: #000000; background-color: #ffff00" contenteditable="false">[' + FCK.DataProcessor._bbcode_tag + ']</span>'
	},

	/**
	 * Replace multiple spaces in HTML code into &nbsp;
	 */
	_ReplaceSpacesWithEntities : function ( data )
	{
		var result = "" ;
		var openingTag = closingTag = 0 ;
		while( openingTag != -1 )
		{
			openingTag = data.indexOf("<") ;
			if ( openingTag == -1 )
				result += data.replace( / (?= )/g, "&nbsp;" ) ;
			else
			{
				closingTag = data.indexOf(">") ;
				if ( closingTag == -1 )
				{
					result += data.substring( 0, openingTag ).replace( / (?= )/g, "&nbsp;" ) ;
					data = data.substring( openingTag +1 ) ;
				}
				else
				{
					result += data.substring( 0, openingTag ).replace( / (?= )/g, "&nbsp;" ) + data.substring( openingTag, closingTag + 1 ) ;
					data = data.substring( closingTag + 1 ) ;
				}
			}
		}

		return result;
	},

	_ConvertEntities : function ( e )
	{
		for (var i = 0 ; i < e.childNodes.length ; i++ )
		{
			if ( e.childNodes[i].nodeType == 3 )
			{
  				e.childNodes[i].nodeValue = e.childNodes[i].nodeValue.replace( / /g, '&nbsp;' ) ;
			}
			else
				FCK.DataProcessor._ConvertEntities( e.childNodes[i] ) ;
		}
	},

	/**
	 * Escape characters that have special meaning in regular expressions
	 */
	_QuoteMeta : function ( str )
	{
		return str.replace( /([.\\+*?|\[\]\^()$])+?/gi, "\\$1" );
	},

	/**
	 * Called when smiley is found
	 */
	_MatchSmileToHtml : function ( sMatch )
	{
		FCK.DataProcessor._smiley_regex.test( sMatch ) ;
		return '<img _fckfakelement="true" alt="' + RegExp.$1 + '" src="' + window.parent.fck_smiley_img[FCK.DataProcessor._smile_counter] + '" class="_fake_smiley" _moz_resizing="false"/>' ;
	},

	/**
	 * Called when [flash] tag is found
	 */
	_MatchFlashToHtml : function ( sMatch )
	{
		var reg = /\[flash=(\d+),(\d+)\](.+?)\[\/flash\]/gi ;
		reg.test( sMatch ) ;

		//get rid of HTML entities in src
		var ta = document.createElement("textarea") ;
		ta.innerHTML = RegExp.$3 ;
		var src = ta.value ;

		oEmbed		= document.createElement( 'EMBED' ) ;
		oEmbed.setAttribute( 'type'			, 'application/x-shockwave-flash', 0 ) ;
		oEmbed.setAttribute( 'pluginspage'	, 'http://www.macromedia.com/go/getflashplayer', 0 ) ;
		oEmbed.setAttribute( 'src', src, 0 ) ;
		oEmbed.setAttribute( "width" , RegExp.$1, 0 ) ;
		oEmbed.setAttribute( "height", RegExp.$2, 0 ) ;

		_fckrealelement = FCKTempBin.AddElement( oEmbed ) ;

		return '<img class="FCK__flash" style="width: ' + RegExp.$1 + 'px;height: ' + RegExp.$2 + 'px" width="1" height="1" src="' + FCKConfig.FullBasePath + 'images/spacer.gif" _fckfakeelement="true" _fckrealelement="' + _fckrealelement + '" _fckflash="true"/>';
	},

	_MatchQuoteToHtml : function ( sMatch )
	{
		var reg = /\[quote(?:\="([^\]]+)")?](.+)\[\/quote]/gi ;
		reg.test( sMatch ) ;

		if ( RegExp.$1.length )
			return '<blockquote title="' + RegExp.$1 + '">' + FCK.DataProcessor._ConvertQuoteToHtmlSecondPass( RegExp.$2 ) + '</blockquote>';
		else
			return '<blockquote>' + FCK.DataProcessor._ConvertQuoteToHtmlSecondPass( RegExp.$2 ) + '</blockquote>';
	},

	/**
	 * Convert [quote] tags
	 */
	_ConvertQuoteToHtml : function ( data )
	{
		data = data.replace( /\[quote(?:\="([^\]]+)")?](.+)\[\/quote]/gi, FCK.DataProcessor._MatchQuoteToHtml ) ;

		return data;
	},

	_ConvertQuoteToHtmlSecondPass : function ( data )
	{
		data = data.replace( /\[quote]/gi, '<blockquote><div>' ) ;
		data = data.replace( /\[quote\="([^\]]+)"]/gi, function( sMatch ) { var reg = /\[quote\="([^\]]+)"]/gi ; reg.test(sMatch) ; return '<blockquote title="' + RegExp.$1 + '"><div>'; } ) ;
		data = data.replace( /\[\/quote]/gi, '</div></blockquote>' ) ;

		return data;
	},

	/**
	 * Convert various [list] tags
	 */
	_ConvertListToHtml : function ( data )
	{
		// [list]
		data = data.replace( /\[list\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return "<ul>" + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;

		// [list=i]
		data = data.replace( /\[list=i\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: lower-roman;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=I]
		data = data.replace( /\[list=I\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: upper-roman;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=disc|circle|square]
		data = data.replace( /\[list=disc\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: disc;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;
		data = data.replace( /\[list=circle\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: circle;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;
		data = data.replace( /\[list=square\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: square;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;

		// [list=a]
		data = data.replace( /\[list=[a-z]\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: lower-alpha;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=A]
		data = data.replace( /\[list=[A-Z]\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: upper-alpha;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=1]
		data = data.replace( /\[list=\d+\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: decimal;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		return data;
	},

	_ConvertListToHtmlSecondPass : function ( data )
	{
		// [list]
		data = data.replace( /\[list\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return "<ul>" + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;

		// [list=i]
		data = data.replace( /\[list=i\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: lower-roman;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=I]
		data = data.replace( /\[list=I\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: upper-roman;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=disc|circle|square]
		data = data.replace( /\[list=disc\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: disc;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;
		data = data.replace( /\[list=circle\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: circle;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;
		data = data.replace( /\[list=square\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ul"); return '<ul style="list-style-type: square;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ul>"; } ) ;

		// [list=a]
		data = data.replace( /\[list=[a-z]\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: lower-alpha;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=A]
		data = data.replace( /\[list=[A-Z]\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: upper-alpha;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		// [list=1]
		data = data.replace( /\[list=\d+\](?:<br>)?(.+)\[\/list]/gi, function( sMatch ) { FCK.DataProcessor._inList.push("ol"); return '<ol style="list-style-type: decimal;">' + FCK.DataProcessor._MatchListToHtml( sMatch ) + "</ol>"; } ) ;

		if ( (data.indexOf( "[list" ) != -1 && data.indexOf( "[/list]" ) == -1) || ( data.indexOf( "[/list]" ) < data.indexOf( "[list" ) ) )
		{
			// [list]
			data = data.replace( /\[list\]/gi, "<ul>" ) ;

			// [/list]
			data = data.replace( /\[\/list\]/gi, FCK.DataProcessor._CloseList ) ;

			// [list=i]
			data = data.replace( /\[list=i\]/gi, '<ol style="list-style-type: lower-roman;">' ) ;

			// [list=I]
			data = data.replace( /\[list=I\]/gi, '<ol style="list-style-type: upper-roman;">' ) ;

			// [list=disc|circle|square]
			data = data.replace( /\[list=disc\]/gi, '<ul style="list-style-type: disc;">' ) ;
			data = data.replace( /\[list=circle\]/gi, '<ul style="list-style-type: circle;">' ) ;
			data = data.replace( /\[list=square\]/gi, '<ul style="list-style-type: square;">' ) ;

			// [list=a]
			data = data.replace( /\[list=[a-z]\]/gi, '<ol style="list-style-type: lower-alpha;">' ) ;

			// [list=A]
			data = data.replace( /\[list=[A-Z]\]/gi, '<ol style="list-style-type: upper-alpha;">' ) ;

			// [list=1]
			data = data.replace( /\[list=\d+\]/gi, '<ol style="list-style-type: decimal;">' ) ;
		}
		return data;
	},

	_CloseList : function ( sMatch )
	{
		return "</" + FCK.DataProcessor._inList.shift() + ">" ;
	},

	/**
	 * Called when [attachment] is found
	 */
	_MatchAttachmentToHtml : function ( source )
	{
		var reg = /\[attachment=(\d+)\](.+)?\[\/attachment\]/gi ;
		reg.test( source ) ;
		var index = RegExp.$1 ;
		source = source.replace( /\[attachment=(\d+)\](.+)?\[\/attachment\]/gi, '<img alt="$2" src="' + window.parent['_attachment_array'][index].replace( /&amp;/gi, "&").replace( /^.\//, FCKConfig.BaseHref ) + '" title="_fck_attachment_$1" class="attach-image"/>');
		return source;
	},

	/**
	 * Called when [list] is found
	 */
	_MatchListToHtml : function ( source )
	{
		source = source.replace( /^\[list(?:=[a-z0-9]+)?\]/gi, "" ) ;
		source = source.replace( /\[\/list\]$/gi, "" ) ;

		source = source.replace( /\[\/\*]/gi, '</li>' ) ;
		return FCK.DataProcessor._ConvertListToHtmlSecondPass( source.replace(/\[\*\]/gi, '<li>' ) ) ;
	},

	/**
	 * Cut [code] parts from text and add them to temp arrays
	 * @param {String} source Text or part of processed test
	 * @param {Integer} depth, >0 if we are inside [code] tag
	 * @param {Boolean} firstRun, set to true if we are processing the document after the page loads
	 */
	_ConvertCodeToHtml : function ( source, depth, firstRun )
	{
		var codeEndLength = 7;
		var end = source.indexOf( "[/code]" ) ;
		var lastEnd = source.lastIndexOf( "[/code]" ) ;
		var startMatch = source.match( /\[code(?:=([a-z]+))?\]/ ) ;
		var start = -1;

		// because in iframe text between [code] tags is html encoded, we must
		// decode it the first time page loads
		var fixText = function ( text )
		{
			if ( !firstRun )
				return text ;

			//we already did FCKTools.HTMLEncode on text, that's why &amp; is in regex
			text = text.replace( /&amp;#(\d{2});?/g, function ( sMatch ) { sMatch.match(/&amp;#(\d{2});?/); return String.fromCharCode( RegExp.$1 ) ; }  ) ;

			return FCKTools.HTMLDecode( text ) ;
		};

		if ( startMatch )
		{
			start = source.indexOf( startMatch[0] ) ;
			var codeStartLength = startMatch[0].length;
			if ( startMatch[1] != undefined )
				startTag = '<code class="codebox" title="' + startMatch[1] + '">';
			else
				startTag = '<code class="codebox">';
		}

		if ( !depth )
		{
			//depth 0  and no ending tag, end work
			if ( end == -1 )
				return source;
			//depth 0  and no starting tag, end work
			if ( start == -1 )
				return source;

			FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] = startTag ;
			return source.substring(0, start) + FCK.DataProcessor._ConvertCodeToHtml( source.substring(start + codeStartLength), 1, firstRun ) ;
		}
		else
		{
			//we have last ending tag here, doesn't matter what depth is it, end code section
			if ( end == lastEnd )
			{
				FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += fixText( source.substring(0, lastEnd) ) + "</code>" ;
				return "__FCKREPLACE" + (FCK.DataProcessor._replaceCounter++) + "__" + source.substring(lastEnd + codeEndLength) ;
			}
			else
			{
				//we have another starting tag
				if ( start != -1 && start < end )
				{
					FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += fixText( source.substring(0, start + codeStartLength ) ) ;
					return FCK.DataProcessor._ConvertCodeToHtml( source.substring( start + codeStartLength ), depth + 1, firstRun ) ;
				}

				//depth is 1 and we have end tag first
				if ( depth == 1 )
				{
					FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += fixText( source.substring(0, end) ) + "</code>" ;
					return "__FCKREPLACE" + (FCK.DataProcessor._replaceCounter++) + "__" + FCK.DataProcessor._ConvertCodeToHtml( source.substring( end + codeEndLength ), 0, firstRun ) ;
				}
				FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += fixText( source.substring( 0, end + codeEndLength ) ) ;
				return FCK.DataProcessor._ConvertCodeToHtml( source.substring( end + codeEndLength, firstRun ), depth -1 );
			}
		}
	},

	/**
	 * Cut <code> parts from html and add them to temp arrays
	 */
	_ConvertCodeToDataFormat : function ( source, depth )
	{
		var codeEndLength = 7 ;
		var end = source.toLowerCase().indexOf( "</code>" ) ;
		var lastEnd = source.toLowerCase().lastIndexOf( "</code>" ) ;
		var startMatch = source.match( /\<code class="?codebox"?(?: title="?([a-z]*)"?)?>/i ) ;
		var start = -1 ;

		if ( startMatch )
		{
			start = source.indexOf( startMatch[0] ) ;
			var codeStartLength = startMatch[0].length ;
			if ( startMatch[1] != undefined && startMatch[1] )
				startTag = '[code=' + startMatch[1] + ']' ;
			else
				startTag = '[code]' ;
		}

		if ( !depth )
		{
			//depth 0  and no ending tag, end work
			if ( end == -1 )
				return source ;
			//depth 0  and no starting tag, end work
			if ( start == -1 )
				return source ;

			FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] = startTag ;
			return source.substring(0, start) + FCK.DataProcessor._ConvertCodeToDataFormat( source.substring(start + codeStartLength), 1 ) ;
		}
		else
		{
			//we have last ending tag here, doesn't matter what depth is it, end code section
			if ( end == lastEnd )
			{
				FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += FCKTools.HTMLDecode( source.substring(0, lastEnd).replace( /<[^>]*?>/g, "" ) ) + "[/code]" ;
				return "__FCKREPLACE" + (FCK.DataProcessor._replaceCounter++) + "__" + source.substring(lastEnd + codeEndLength) ;
			}
			else
			{
				//we have another starting tag
				if ( start != -1 && start < end )
				{
					FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += FCKTools.HTMLDecode( source.substring(0, start + codeStartLength ).replace( /<[^>]*?>/g, "" ) ) ;
					return FCK.DataProcessor._ConvertCodeToDataFormat( source.substring( start + codeStartLength ), depth + 1 ) ;
				}

				//depth is 1 and we have end tag first
				if ( depth == 1 )
				{
					FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += FCKTools.HTMLDecode( source.substring(0, end).replace( /<[^>]*?>/g, "" ) ) + "[/code]" ;
					return "__FCKREPLACE" + (FCK.DataProcessor._replaceCounter++) + "__" + FCK.DataProcessor._ConvertCodeToDataFormat( source.substring( end + codeEndLength ), 0 ) ;
				}

				FCK.DataProcessor._replaceTab[FCK.DataProcessor._replaceCounter] += FCKTools.HTMLDecode( source.substring(0, end + codeEndLength).replace( /<[^>]*?>/g, "" ) ) ;
				return FCK.DataProcessor._ConvertCodeToDataFormat( source.substring( end + codeEndLength ), depth -1 ) ;
			}
		}
	},

	/**
	 * Most of basic HTML to [code] conversion is done here.
	 * This function is based on FCKXHtml._AppendNode.
	 */
	_AppendNode : function( htmlNode, stringBuilder )
	{
		if ( !htmlNode )
			return ;

		var tempSB = new Array ;

		switch ( htmlNode.nodeType )
		{
			// Element Node.
			case 1 :

				// Here we found an element that is not the real element, but a
				// fake one (like the Flash placeholder image), so we must get the real one.
				if ( htmlNode.getAttribute('_fckfakelement') )
					return this._AppendNode( FCK.GetRealElement( htmlNode ), stringBuilder ) ;

				// Mozilla insert custom nodes in the DOM.
				if ( FCKBrowserInfo.IsGecko && htmlNode.hasAttribute('_moz_editor_bogus_node') )
					return ;

				// This is for elements that are instrumental to FCKeditor and
				// must be removed from the final HTML.
				if ( htmlNode.getAttribute('_fcktemp') )
					return ;

				// Get the element name.
				var sNodeName = htmlNode.tagName.toLowerCase()  ;

				if ( FCKBrowserInfo.IsIE )
				{
					// IE doens't include the scope name in the nodeName. So, add the namespace.
					if ( htmlNode.scopeName && htmlNode.scopeName != 'HTML' && htmlNode.scopeName != 'FCK' )
						sNodeName = htmlNode.scopeName.toLowerCase() + ':' + sNodeName ;
				}
				else
				{
					if ( sNodeName.StartsWith( 'fck:' ) )
						sNodeName = sNodeName.Remove( 0,4 ) ;
				}

				// Check if the node name is valid, otherwise ignore this tag.
				// If the nodeName starts with a slash, it is a orphan closing tag.
				// On some strange cases, the nodeName is empty, even if the node exists.
				if ( !FCKRegexLib.ElementName.test( sNodeName ) )
					return ;

				// Remove the <br> if it is a bogus node.
				if ( sNodeName == 'br' && htmlNode.getAttribute( 'type', 2 ) == '_moz' )
					return ;

				// The already processed nodes must be marked to avoid then to be duplicated (bad formatted HTML).
				// So here, the "mark" is checked... if the element is Ok, then mark it.
				if ( htmlNode._fckxhtmljob && htmlNode._fckxhtmljob == FCKXHtml.CurrentJobNum )
					return ;

				var basicElement = this._BasicElements[ sNodeName ] ;
				if ( basicElement )
				{
					if ( !basicElement[2] )
						this._AppendChildNodes( htmlNode, tempSB ) ;

					if ( sNodeName == 'li' || tempSB.length )
					{
						if ( basicElement[0] )
							stringBuilder.push( basicElement[0] ) ;

						for ( var i = 0 ; i < tempSB.length ; i ++ )
							stringBuilder.push( tempSB[i] ) ;

						if ( basicElement[1] )
							stringBuilder.push( basicElement[1] ) ;
					}
				}
				else
				{
					switch ( sNodeName )
					{
						case 'br':

							stringBuilder.push( "\n" ) ;

							break ;

						case 'table':

							if ( this._bbcodeSupport['table'] )
							{
								stringBuilder.push( '[tableo' + this._GetAttributesStr( htmlNode, "_AppendNode" ) + '][/tableo]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[tablec][/tablec]' ) ;
							}

							break ;

						case 'td':

							if ( this._bbcodeSupport['table'] )
							{
								stringBuilder.push( '[tdo' + this._GetAttributesStr( htmlNode, "_AppendNode" ) + '][/tdo]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[tdc][/tdc]' ) ;
							}

							break ;

						case 'tbody':

							if ( this._bbcodeSupport['table'] )
							{
								stringBuilder.push( '[tbodyo][/tbodyo]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[tbodyc][/tbodyc]' ) ;
							}

							break ;

						case 'tr':

							if ( this._bbcodeSupport['table'] )
							{
								stringBuilder.push( '[tro][/tro]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[trc][/trc]' ) ;
							}

							break ;

						case 'caption':

							if ( this._bbcodeSupport['table'] )
							{
								stringBuilder.push( '[caption]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[/caption]' ) ;
							}

							break ;

						case 'hr':

							if ( this._bbcodeSupport['hr'] )
							{
								stringBuilder.push( '[hr][/hr]' ) ;
							}

							break ;

						case 'a' :

							if ( window.parent.fckUrlStatus )
							{
								// Get the actual Link href.
								var href = htmlNode.getAttribute( '_fcksavedurl' ) ;
								if ( href == null )
									href = htmlNode.getAttribute( 'href' , 2 ) || '' ;

								if ( !href.length )
									break ;

								if ( href.StartsWith( 'mailto:' ) )
								{
									stringBuilder.push( '[email]' + href.substring( 7 ) ) ;
									stringBuilder.push( '[/email]' ) ;
								}
								else
								{
									stringBuilder.push( '[url=' + href + ']' ) ;
									this._AppendChildNodes( htmlNode, stringBuilder ) ;
									stringBuilder.push( '[/url]' ) ;
								}
							}

							break ;

						case 'embed' :

							if ( window.parent.fckFlashStatus )
								stringBuilder.push( '[flash=' + htmlNode.width + ',' + htmlNode.height + ']' + htmlNode.src + '[/flash]' ) ;

							break ;

						case 'img' :
							var src = alt = "";
							var width = height = 300 ;

							src =  htmlNode.getAttribute( 'src' ) ;
							alt =  htmlNode.getAttribute( 'alt' ) ;

							if ( htmlNode.getAttribute( '_fckrealelement' ) )
							{
								var real = FCK.GetRealElement( htmlNode ) ;
								src = real.src ;
								if ( real.width )
  									width = real.width ;
  								if ( real.height )
  									height = real.height ;
							}

							if ( htmlNode.getAttribute( '_fckflash' ) )
							{
								if ( window.parent.fckFlashStatus )
									stringBuilder.push( '[flash=' + width + ',' + height + ']' + src + '[/flash]' ) ;
							}
							else if ( htmlNode.className == "_fake_smiley" )
								stringBuilder.push( " " + alt ) ;
							else if ( htmlNode.getAttribute( 'title' ).substring(0,16) == "_fck_attachment_" )
								stringBuilder.push( '[attachment=' +  htmlNode.getAttribute( 'title' ).substring( 16 ) + ']' + alt + '[/attachment]' ) ;
							else
								if ( window.parent.fckImgStatus )
									stringBuilder.push( "[img]" + src + "[/img]" ) ;
								else
									stringBuilder.push( '<img ' + this._GetAttributesStr( htmlNode, "_AppendNode" ) + '/>' ) ;

							break ;

						case 'code' :

							if ( !htmlNode.innerHTML.length )
								break ;

							if ( htmlNode.className == "codebox" )
							{
								if ( htmlNode.getAttribute( 'title' ).length )
									stringBuilder.push( '[code=' + htmlNode.getAttribute( 'title' ) + ']' ) ;
								else
									stringBuilder.push( '[code]' ) ;

								stringBuilder.push( htmlNode.innerHTML ) ;
								stringBuilder.push( '[/code]' ) ;
							}

							break ;

						case 'ol' :
						case 'ul' :

							switch ( htmlNode.style.listStyleType )
							{
								case 'lower-alpha' :
									stringBuilder.push( '[list=a]' ) ;
								break ;

								case 'upper-alpha' :
									stringBuilder.push( '[list=A]' ) ;
								break ;

								case 'decimal' :
									stringBuilder.push( '[list=1]' ) ;
								break ;

								case 'disc' :
								case 'circle' :
								case 'square' :
									stringBuilder.push( '[list=' + htmlNode.style.listStyleType + ']' ) ;
								break ;

								case 'upper-roman' :
									stringBuilder.push( '[list=I]' ) ;
								break ;

								case 'lower-roman' :
									stringBuilder.push( '[list=i]' ) ;
								break ;

								default :
									if ( sNodeName == "ol" )
										stringBuilder.push( '[list=1]' ) ;
									else
										stringBuilder.push( '[list]' ) ;
								break ;
							}

							this._AppendChildNodes( htmlNode, stringBuilder ) ;
							stringBuilder.push( '[/list]' ) ;

							break;

						case 'blockquote' :

							if ( !htmlNode.innerHTML.length )
								break ;

							if ( typeof( htmlNode.childNodes[0].childNodes[0] ) != "undefined" && typeof( htmlNode.childNodes[0].childNodes[0].tagName ) != "undefined" && htmlNode.childNodes[0].tagName.toLowerCase() == "div" && htmlNode.childNodes[0].childNodes[0].tagName.toLowerCase() == "cite" )
							{
								var reCite = /(.+) \S+:/ ;
								if ( reCite.test( htmlNode.childNodes[0].childNodes[0].innerHTML ) )
								{
									stringBuilder.push( '[quote="' + RegExp.$1 + '"]' ) ;
									htmlNode.childNodes[0].removeChild( htmlNode.childNodes[0].childNodes[0] ) ;
									this._AppendChildNodes( htmlNode.childNodes[0], stringBuilder ) ;
									stringBuilder.push( '[/quote]' ) ;
								}
							}
							else
							{
								var title = htmlNode.getAttribute( 'title' ) ;
								if ( title )
									stringBuilder.push( '[quote="' + title + '"]' ) ;
								else
									 stringBuilder.push( '[quote]' ) ;
								this._AppendChildNodes( htmlNode, stringBuilder ) ;
								stringBuilder.push( '[/quote]' ) ;
							}

							break;

						case 'font' :

							if ( !htmlNode.innerHTML.length )
								break ;

							var reHexColor = /(#[0-9a-f]{6})/ ;
							var pr = "" ;
							var sf = "" ;
							if ( reHexColor.test( htmlNode.getAttribute( 'color' ) ) )
							{
								if ( FCK.DataProcessor._inColor > 0 )
									pr += '[/color]' ;
								else
									sf = '[/color]' + sf ;

								pr += '[color=' + RegExp.$1 + ']' ;
							}

							FCK.DataProcessor._inColor++ ;
							this._AppendChildNodes( htmlNode, tempSB ) ;
							FCK.DataProcessor._inColor-- ;

							if ( tempSB.length )
							{
								stringBuilder.push( pr ) ;
								for ( var i = 0 ; i < tempSB.length ; i ++ )
									stringBuilder.push( tempSB[i] ) ;
								stringBuilder.push( sf ) ;
							}

							break ;

						case 'span' :

							var _inSize = false ;
							var _inColor = false ;

							if ( !htmlNode.innerHTML.length )
								break ;

							var reSize = /([\-\+]?\d+)%/ ;
							var reHexColor = /(#[0-9a-f]{6})/ ;
							var reRgbColor = /rgb\((\d+), (\d+), (\d+)\);?/ ;
							var pr = "" ;
							var sf = "" ;
							if ( reSize.test( htmlNode.style.fontSize ) )
							{
								_inSize = true ;

								if ( FCK.DataProcessor._inSize > 0 )
									pr += '[/size]' ;
								else
									sf = '[/size]' + sf ;

								pr += '[size=' + RegExp.$1 + ']' ;
							}
							if ( reHexColor.test( htmlNode.style.color ) )
							{
								_inColor = true ;

								if ( FCK.DataProcessor._inColor > 0 )
									pr += '[/color]' ;
								else
									sf = '[/color]' + sf ;

								pr += '[color=' + RegExp.$1 + ']' ;
							}
							if ( reRgbColor.test( htmlNode.style.color ) )
							{
								_inColor = true ;

								if ( FCK.DataProcessor._inColor > 0 )
									pr += '[/color]' ;
								else
									sf = '[/color]' + sf ;

								var red = parseInt(RegExp.$1).toString(16) ;
								var green = parseInt(RegExp.$2).toString(16) ;
								var blue = parseInt(RegExp.$3).toString(16) ;

								pr += '[color=#' + red.replace(/^(\d)$/, '0$1') + green.replace(/^(\d)$/, '0$1') + blue.replace(/^(\d)$/, '0$1') + ']' ;
							}

							if ( _inColor )
								FCK.DataProcessor._inColor++ ;
							if ( _inSize )
								FCK.DataProcessor._inSize++ ;

							this._AppendChildNodes( htmlNode, tempSB ) ;

							if ( _inColor )
								FCK.DataProcessor._inColor-- ;
							if ( _inSize )
								FCK.DataProcessor._inSize-- ;

							if ( tempSB.length )
							{
								stringBuilder.push( pr ) ;
								for ( var i = 0 ; i < tempSB.length ; i ++ )
									stringBuilder.push( tempSB[i] ) ;
								stringBuilder.push( sf ) ;
							}

							break ;

						default :
							this._AppendChildNodes( htmlNode, stringBuilder ) ;
							break ;
					}
				}

				htmlNode._fckxhtmljob = FCKXHtml.CurrentJobNum ;
				return ;

			// Text Node.
			case 3 :
				var textValue = htmlNode.nodeValue ;

				if ( !htmlNode.previousSibling ||
					( stringBuilder.length > 0 && stringBuilder[ stringBuilder.length - 1 ].EndsWith( '\n' ) ) )
				{
					textValue = textValue.LTrim() ;
				}

				//if ( !htmlNode.nextSibling )
					//textValue = textValue.RTrim() ;

				stringBuilder.push( textValue ) ;
				return ;
		}
	},

	/**
	 * Used by FCK.DataProcessor._AppendNode to append child nodes to stringbuilder
	 */
	_AppendChildNodes : function( htmlNode, stringBuilder, isBlockElement )
	{
		var child = htmlNode.firstChild ;

		while ( child )
		{
			this._AppendNode( child, stringBuilder ) ;
			child = child.nextSibling ;
		}
	},

	/**
	 * Here we are sorting attributes, because browsers seem to place attributes in random order.
	 * It's important, because we are doing replacements using regular expressions.
	 */
	_GetAttributesStr : function( htmlNode, mode )
	{
		var attStr = '' ;
		var order = new Array ;
		var aAttributes = htmlNode.attributes ;

		for ( var i = 0 ; i < aAttributes.length ; i++ )
			if ( aAttributes[i].specified )
				order.push( i ) ;

		if ( aAttributes.length > 1 )
		{
			if ( order.length > 1 )
				for ( var i = 0 ; i < order.length - 1 ; i++ )
					for ( var j = 1 ; j < order.length ; j++ )
						if ( aAttributes[order[j]].nodeName.toLowerCase() < aAttributes[order[j-1]].nodeName.toLowerCase() )
						{
							var temp = order[j];
							order[j] = order[j-1];
							order[j-1] = temp;
						}
		}

		for ( var n = 0 ; n < order.length ; n++ )
		{
			var oAttribute = aAttributes[order[n]] ;

				var sAttName = oAttribute.nodeName.toLowerCase() ;
				var sAttValue ;

				// Ignore any attribute starting with "_fck".
				if ( sAttName.StartsWith( '_fck' ) && ( mode != "_AppendNormalizedNode" ||
				( sAttName != "_fckrealelement"  && sAttName != "_fckflash" ) ) )
					continue ;
				// There is a bug in Mozilla that returns '_moz_xxx' attributes as specified.
				else if ( sAttName.indexOf( '_moz' ) == 0 )
					continue ;
				// For "class", nodeValue must be used.
				else if ( sAttName == 'class' )
				{
					// Get the class, removing any fckXXX we can have there.
					sAttValue = oAttribute.nodeValue.replace( /(^|\s*)fck\S+/, '' ).Trim() ;

					if ( sAttValue.length == 0 )
						continue ;
				}
				// XHTML doens't support attribute minimization like "CHECKED". It must be trasformed to cheched="checked".
				else if ( oAttribute.nodeValue === true )
					sAttValue = sAttName ;
				else
					sAttValue = htmlNode.getAttribute( sAttName, 2 ) ;	// We must use getAttribute to get it exactly as it is defined.

				if ( sAttName == "style" )
				{
					sAttValue = htmlNode.getAttribute( sAttName ) ;
					if ( typeof sAttValue == 'object' )
						sAttValue = sAttValue.cssText ;
				}

				if ( mode == "_AppendNormalizedNode" )
					attStr += ' ' + sAttName.toLowerCase() + '="' + sAttValue + '"' ;
				else
					attStr += ' ' + sAttName.toLowerCase() + '="' + sAttValue.toString().toLowerCase() + '"' ;
		}
		return attStr ;
	},

	/**
	 * Used by FCK.DataProcessor._AppendNormalizedNode to append child nodes to stringbuilder
	 */
	_AppendNormalizedChildNodes : function( htmlNode, stringBuilder, isBlockElement )
	{
		var child = htmlNode.firstChild ;

		while ( child )
		{
			this._AppendNormalizedNode( child, stringBuilder ) ;
			child = child.nextSibling ;
		}
	},

	/**
	 *	Converts HTML to string with the same formatting for all browsers ( + attributes are sorted).
	 *
	 *  @param {String} pattern
	 */
	_AppendNormalizedNode : function ( htmlNode, stringBuilder )
	{
		if ( !htmlNode )
			return ;

		switch ( htmlNode.nodeType )
		{
			// Element Node.
			case 1 :

				// Mozilla insert custom nodes in the DOM.
				if ( FCKBrowserInfo.IsGecko && htmlNode.hasAttribute('_moz_editor_bogus_node') )
					return ;

				// This is for elements that are instrumental to FCKeditor and
				// must be removed from the final HTML.
				if ( htmlNode.getAttribute('_fcktemp') )
					return ;

				// Get the element name.
				var sNodeName = htmlNode.tagName.toLowerCase()  ;

				if ( FCKBrowserInfo.IsIE )
				{
					// IE doens't include the scope name in the nodeName. So, add the namespace.
					if ( htmlNode.scopeName && htmlNode.scopeName != 'HTML' && htmlNode.scopeName != 'FCK' )
						sNodeName = htmlNode.scopeName.toLowerCase() + ':' + sNodeName ;
				}
				else
				{
					if ( sNodeName.StartsWith( 'fck:' ) )
						sNodeName = sNodeName.Remove( 0,4 ) ;
				}

				// Check if the node name is valid, otherwise ignore this tag.
				// If the nodeName starts with a slash, it is a orphan closing tag.
				// On some strange cases, the nodeName is empty, even if the node exists.
				if ( !FCKRegexLib.ElementName.test( sNodeName ) )
					return ;

				// Remove the <br> if it is a bogus node.
				if ( sNodeName == 'br' && htmlNode.getAttribute( 'type', 2 ) == '_moz' )
					return ;

				// The already processed nodes must be marked to avoid then to be duplicated (bad formatted HTML).
				// So here, the "mark" is checked... if the element is Ok, then mark it.
				if ( htmlNode._fckxhtmljob && htmlNode._fckxhtmljob == FCKXHtml.CurrentJobNum )
					return ;

				var attribs = this._GetAttributesStr( htmlNode, "_AppendNormalizedNode" ) ;

				stringBuilder.push( '<' ) ;
				stringBuilder.push( sNodeName ) ;

				if ( attribs.length > 0 )
					stringBuilder.push( attribs ) ;

				if ( !htmlNode.childNodes.length )
					stringBuilder.push( '/>' ) ;
				else
				{
					stringBuilder.push( '>' ) ;
					this._AppendNormalizedChildNodes( htmlNode, stringBuilder ) ;
					stringBuilder.push( '<\/' ) ;
					stringBuilder.push( sNodeName ) ;
					stringBuilder.push( '>' ) ;
				}

				htmlNode._fckxhtmljob = FCKXHtml.CurrentJobNum ;
				return ;

			// Text Node.
			case 3 :
				var textValue = htmlNode.nodeValue ;

				stringBuilder.push( FCKTools.HTMLEncode( textValue ) ) ;
				return ;

			// Comment
			case 8 :
				// IE catches the <!DOTYPE ... > as a comment, but it has no
				// innerHTML, so we can catch it, and ignore it.
				if ( FCKBrowserInfo.IsIE && !htmlNode.innerHTML )
					return ;

				stringBuilder.push( "<!--"  ) ;

				try	{ stringBuilder.push( FCKTools.HTMLEncode( htmlNode.nodeValue ) ) ; }
				catch (e) { /* Do nothing... probably this is a wrong format comment. */ }

				stringBuilder.push( " -->" ) ;
				return ;
		}
	},

	/*
	 * Converts a DOM (sub-)tree to a string in the data format.
	 *     @param {Object} rootNode The node that contains the DOM tree to be
	 *            converted to the data format.
	 *     @param {Boolean} excludeRoot Indicates that the root node must not
	 *            be included in the conversion, only its children.
	 *     @param {Boolean} format Indicates that the data must be formatted
	 *            for human reading. Not all Data Processors may provide it.
	 */
	ConvertToDataFormat : function( rootNode, excludeRoot, ignoreIfEmptyParagraph, format )
	{
		FCK.DataProcessor._replaceCounter = 0 ;
		FCK.DataProcessor._replaceTab = new Array ;

		var stringBuilder = new Array() ;
		this._AppendNormalizedNode( rootNode, stringBuilder ) ;
		data = stringBuilder.join( '' ).Trim() ;

		if ( FCK.DataProcessor._mode == "data" )
			return FCK.DataProcessor._data ;

		data = data.replace( /<br(?=[ \/>]).*?>/gi, '\r\n' ) ;

		// [code] part 1
		data = FCK.DataProcessor._ConvertCodeToDataFormat( data, 0 ) ;

		if ( FCKConfig.ForceDivJustify != undefined && FCKConfig.ForceDivJustify )
		{
			//IE: rename p to div
			data = data.replace( /<p align="?([a-z]+)?"?>((?:.|\s)+?)<\/p>/gi, '<div align="$1">$2</div>' );
			//fix missing double quotes
			data = data.replace( /<div align=([a-z]+)>((?:.|\s)+?)<\/div>/gi, '<div align="$1">$2</div>' );
		}

		// remove FCK internal class attributes
		data = data.replace( / class=" FCK__[a-z]+"/gi, "" ) ;

		// custom bbcodes
		if( window.parent.fck_bb_tpl && window.parent.fck_bb_match )
		{
			var bbcode_match = "";
			var bbcode_tpl = "";

			for ( i=0 ; i < window.parent.fck_bb_tpl.length ; i++ )
			{
				bbcode_match = window.parent.fck_bb_match[i];
				bbcode_tpl = window.parent.fck_bb_tpl[i];

				//let's skip some predefined custom bbcodes to handle it better by traversing a DOM Document

				if ( bbcode_tpl != '' )
				{
					switch (bbcode_match)
					{
						// if we find any of the following definitions, we assume that table support is enabled and all bbcodes are defined properly
						case '[caption]{TEXT}[/caption]' :
						case '[tablec][/tablec]' :
						case '[tableo{ATTRLIST}][/tableo]' :
						case '[tbodyc][/tbodyc]' :
						case '[tbodyo][/tbodyo]' :
						case '[tdc][/tdc]' :
						case '[tdo{ATTRLIST}][/tdo]' :
						case '[trc][/trc]' :
						case '[tro][/tro]' :

							this._bbcodeSupport['table'] = 1 ;

							break ;

						case '[hr][/hr]' :

							this._bbcodeSupport['hr'] = 1 ;

							break ;

						default :

							FCK.DataProcessor._bbcode_array = new Object ;
							FCK.DataProcessor._bbcode_counter = 0 ;
							bbcode_match = bbcode_match.replace( /{(URL|LOCAL_URL|EMAIL|TEXT|ATTRLIST|SIMPLETEXT|IDENTIFIER|COLOR|NUMBER)[0-9]*\}/gi, FCK.DataProcessor._MatchCustomBbcode );

							bbcode_tpl = bbcode_tpl.replace( /<br(?=[ \/>]).*?>/gi, '\\s+' ) ;
							bbcode_tpl = FCK.DataProcessor._PatternToRegex( bbcode_tpl ) ;

							var re_bbcode = new RegExp( bbcode_tpl, "gi" ) ;
							data = data.replace( re_bbcode, bbcode_match ) ;

							break ;
					}
				}
			}
		}

		// fake elements for custom bbcodes
		rootNode.innerHTML = data.replace( /\r\n/g, "<br/>" ) ;
		FCK.DataProcessor._ConvertFakeElements( rootNode ) ;

		//String builder start
		stringBuilder = new Array() ;
		this._AppendNode( rootNode, stringBuilder ) ;

		data = stringBuilder.join( '' ).Trim() ;
		//String builder end

		// [code] part 2
		for( var i = 0; i < FCK.DataProcessor._replaceTab.length; i++ )
			data = data.replace( "__FCKREPLACE" + i + "__", FCK.DataProcessor._replaceTab[i] ) ;

		FCK.DataProcessor._data = data ;
		FCK.DataProcessor._mode = "data" ;

		return FCK.DataProcessor._data ;
	},

	/**
	 * Called by FCK.DataProcessor.ConvertToDataFormat to change fake elements into real
	 * (fake elements that held in span element, currently only custom bbcodes)
	 */
	_ConvertFakeElements : function ( e )
	{
		for (var i = 0 ; i < e.childNodes.length ; i++ )
		{
			if ( e.childNodes[i].nodeType == 1 )
			{
				if ( e.childNodes[i].nodeName.toLowerCase() == "span" && e.childNodes[i].getAttribute( '_fckrealelement' ) )
				{
					sText = FCK.GetRealElement( e.childNodes[i] ).data ;
					var oReal = FCK.EditorDocument.createElement( "span" ) ;
					oReal.innerHTML = FCK.DataProcessor._ReplaceSpacesWithEntities( sText ) ;

					e.replaceChild( oReal, e.childNodes[i] ) ;
				}
				else
				{
					FCK.DataProcessor._ConvertFakeElements( e.childNodes[i] ) ;
				}
			}
		}
	},

	/**
	 * Called while building regex for user defined bbcode.
	 * Replace {TEXT} with $1, $2 etc.
	 */
	_MatchCustomBbcode : function( sMatch )
	{
		if ( typeof( FCK.DataProcessor._bbcode_array[sMatch] ) == 'undefined' )
		{
			FCK.DataProcessor._bbcode_counter ++ ;
			FCK.DataProcessor._bbcode_array[sMatch] = "$" + ( FCK.DataProcessor._bbcode_counter ) ;
		}

		return FCK.DataProcessor._bbcode_array[sMatch] ;
	},

	/*
	 * Makes any necessary changes to a piece of HTML for insertion in the
	 * editor selection position.
	 *     @param {String} html The HTML to be fixed.
	 */
	FixHtml : function( html )
	{
		return html ;
	}
} ;

// This Data Processor doesn't support <p>, so let's use <br>.
FCKConfig.EnterMode = 'br' ;

// To avoid pasting invalid markup (which is discarded in any case), let's
// force pasting to plain text.
FCKConfig.ForcePasteAsPlainText	= true ;

// Rename the "Source" buttom to "BBCode".
FCKToolbarItems.RegisterItem( 'Source', new FCKToolbarButton( 'Source', 'BBCode', null, FCK_TOOLBARITEM_ICONTEXT, true, true, 1 ) ) ;

/**
 * Here we create custom handler for links and images
 */
FCKCommands.RegisterCommand( 'Link', new FCKDialogCommand( 'Link', FCKLang.DlgLnkWindowTitle, FCKPlugins.Items['phpbb3'].Path + 'fck_link.html', 400, 330 ) ) ;
FCKCommands.RegisterCommand( 'Image', new FCKDialogCommand( 'Image', FCKLang.DlgImgTitle, FCKPlugins.Items['phpbb3'].Path + 'fck_image.html', 450, 400 ) ) ;

/**
 * Custom bbcode handler
 */

// Create the "Bbcode" toolbar button.
if( window.parent.fck_bb_tpl && window.parent.fck_bb_match )
	for ( i=0 ; i < window.parent.fck_bb_tpl.length ; i++ )
		if ( window.parent.fck_bb_tpl[i] == '' )
		{
			var Tag = window.parent.fck_bb_tag[i] ;
			// Register the related command.
			FCKCommands.RegisterCommand( Tag, new FCKDialogCommand( Tag, Tag, FCKPlugins.Items['phpbb3'].Path + 'fck_bbcode.html?' + Tag, 400, 350 ) ) ;
			var oBbcodeItem = new FCKToolbarButton( Tag, '[' + Tag + ']', null, FCK_TOOLBARITEM_ONLYTEXT ) ;
			FCKToolbarItems.RegisterItem( Tag, oBbcodeItem ) ;
		}

// The object used for all Bbcode operations.
var FCKBbcodes = new Object() ;

// Add a new bbcode at the actual selection.
FCKBbcodes.Add = function( name, value )
{
	var oSpan = FCK.InsertElement( 'span' ) ;
	this.SetupSpan( oSpan, name, value ) ;
}

FCKBbcodes.Replace = function( oSpan, name, value )
{
	this.SetupSpan( oSpan, name, value ) ;
}

FCKBbcodes.SetupSpan = function( span, name, value )
{
	span.innerHTML = '[' + name + ']' ;

	span.style.backgroundColor = '#ffff00' ;
	span.style.color = '#000000' ;

	if ( FCKBrowserInfo.IsGecko )
		span.style.cursor = 'default' ;

	oText = document.createTextNode( value ) ;
	_fckrealelement = FCKTempBin.AddElement( oText ) ;

	span.setAttribute( '_fckbbcode', name ) ;
	span.setAttribute( '_fckfakeelement', "true") ;
	span.setAttribute( '_fckrealelement', _fckrealelement) ;
	span.contentEditable = false ;

	// To avoid it to be resized.
	span.onresizestart = function()
	{
		FCK.EditorWindow.event.returnValue = false ;
		return false ;
	}
}

// On Gecko we must do this trick so the user select all the SPAN when clicking on it.
FCKBbcodes._SetupClickListener = function()
{
	FCKBbcodes._ClickListener = function( e )
	{
		if ( e.target.tagName == 'SPAN' && e.target.getAttribute("_fckbbcode") ) {
			FCKSelection.SelectNode( e.target ) ;
		}
	}

	FCK.EditorDocument.addEventListener( 'click', FCKBbcodes._ClickListener, true ) ;
}

// Open the Bbcode dialog on double click.
FCKBbcodes.OnDoubleClick = function( span )
{
	if ( span.tagName == 'SPAN' && span.getAttribute("_fckbbcode") )
	{
		FCKCommands.GetCommand( span.getAttribute("_fckbbcode") ).Execute() ;
	}
}

FCK.RegisterDoubleClickHandler( FCKBbcodes.OnDoubleClick, 'SPAN' ) ;

// We must process the SPAN tags to replace then with the real resulting value of the bbcode.
FCKXHtml.TagProcessors['span'] = function( node, htmlNode )
{
	if ( htmlNode.getAttribute("_fckbbcode") )
		node = FCKXHtml.XML.createTextNode( '[' + htmlNode.getAttribute("_fckbbcode") + ']' ) ;
	else
		FCKXHtml._AppendChildNodes( node, htmlNode, false ) ;

	return node ;
}

BlockquoteOnDoubleClick = function( block )
{
	if ( block.tagName == 'BLOCKQUOTE' )
	{
		FCKCommands.GetCommand( "Quote" ).Execute() ;
	}
}

FCK.RegisterDoubleClickHandler( BlockquoteOnDoubleClick, 'BLOCKQUOTE' ) ;
FCKCommands.RegisterCommand( 'Quote', new FCKDialogCommand( 'Quotation', "Quotation", FCKPlugins.Items['phpbb3'].Path + 'fck_blockquote.html', 280, 220 ) ) ;
