      1 ﻿/*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 (function()
      7 {
      8 	// Regex to scan for   at the end of blocks, which are actually placeholders.
      9 	var tailNbspRegex = /^[\t\r\n ]* $/;
     10
     11 	var protectedSourceMarker = '{cke_protected}';
     12
     13 	function trimFillers( block, fromSource )
     14 	{
     15 		// If the current node is a block, and if we're converting from source or
     16 		// we're not in IE then search for and remove any tailing BR node.
     17 		//
     18 		// Also, any   at the end of blocks are fillers, remove them as well.
     19 		// (#2886)
     20 		var children = block.children;
     21 		var lastChild = children[ children.length - 1 ];
     22 		if ( lastChild )
     23 		{
     24 			if ( ( fromSource || !CKEDITOR.env.ie ) && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' )
     25 				children.pop();
     26 			if ( lastChild.type == CKEDITOR.NODE_TEXT && tailNbspRegex.test( lastChild.value ) )
     27 				children.pop();
     28 		}
     29 	}
     30
     31 	function blockNeedsExtension( block )
     32 	{
     33 		if ( block.children.length < 1 )
     34 			return true;
     35
     36 		var lastChild = block.children[ block.children.length - 1 ];
     37 		return lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br';
     38 	}
     39
     40 	function extendBlockForDisplay( block )
     41 	{
     42 		trimFillers( block, true );
     43
     44 		if ( blockNeedsExtension( block ) )
     45 		{
     46 			if ( CKEDITOR.env.ie )
     47 				block.add( new CKEDITOR.htmlParser.text( '\xa0' ) );
     48 			else
     49 				block.add( new CKEDITOR.htmlParser.element( 'br', {} ) );
     50 		}
     51 	}
     52
     53 	function extendBlockForOutput( block )
     54 	{
     55 		trimFillers( block );
     56
     57 		if ( blockNeedsExtension( block ) )
     58 			block.add( new CKEDITOR.htmlParser.text( '\xa0' ) );
     59 	}
     60
     61 	var dtd = CKEDITOR.dtd;
     62
     63 	// Find out the list of block-like tags that can contain <br>.
     64 	var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent );
     65 	for ( var i in blockLikeTags )
     66 	{
     67 		if ( ! ( 'br' in dtd[i] ) )
     68 			delete blockLikeTags[i];
     69 	}
     70 	// We just avoid filler in <pre> right now.
     71 	// TODO: Support filler for <pre>, line break is also occupy line height.
     72 	delete blockLikeTags.pre;
     73 	var defaultDataFilterRules =
     74 	{
     75 		elementNames :
     76 		[
     77 			// Elements that cause problems in wysiwyg mode.
     78 			[ ( /^(object|embed|param)$/ ), 'cke:$1' ]
     79 		],
     80
     81 		attributeNames :
     82 		[
     83 			// Event attributes (onXYZ) must not be directly set. They can become
     84 			// active in the editing area (IE|WebKit).
     85 			[ ( /^on/ ), '_cke_pa_on' ]
     86 		]
     87 	};
     88
     89 	var defaultDataBlockFilterRules = { elements : {} };
     90
     91 	for ( i in blockLikeTags )
     92 		defaultDataBlockFilterRules.elements[ i ] = extendBlockForDisplay;
     93
     94 	/**
     95 	 * IE sucks with dynamic 'name' attribute after element is created, '_cke_saved_name' is used instead for this attribute.
     96 	 */
     97 	var removeName = function( element )
     98 	{
     99 		var attribs = element.attributes;
    100
    101 		if ( attribs._cke_saved_name )
    102 			delete attribs.name;
    103 	};
    104
    105 	var defaultHtmlFilterRules =
    106 		{
    107 			elementNames :
    108 			[
    109 				// Remove the "cke:" namespace prefix.
    110 				[ ( /^cke:/ ), '' ],
    111
    112 				// Ignore <?xml:namespace> tags.
    113 				[ ( /^\?xml:namespace$/ ), '' ]
    114 			],
    115
    116 			attributeNames :
    117 			[
    118 				// Attributes saved for changes and protected attributes.
    119 				[ ( /^_cke_(saved|pa)_/ ), '' ],
    120
    121 				// All "_cke" attributes are to be ignored.
    122 				[ ( /^_cke.*/ ), '' ]
    123 			],
    124
    125 			elements :
    126 			{
    127 				embed : function( element )
    128 				{
    129 					var parent = element.parent;
    130
    131 					// If the <embed> is child of a <object>, copy the width
    132 					// and height attributes from it.
    133 					if ( parent && parent.name == 'object' )
    134 					{
    135 						element.attributes.width = parent.attributes.width;
    136 						element.attributes.height = parent.attributes.height;
    137 					}
    138 				},
    139
    140 				img : function( element )
    141 				{
    142 					var attribs = element.attributes;
    143
    144 					if ( attribs._cke_saved_name )
    145 						delete attribs.name;
    146 					if ( attribs._cke_saved_src )
    147 						delete attribs.src;
    148 				},
    149
    150 				a : function( element )
    151 				{
    152 					var attribs = element.attributes;
    153
    154 					if ( attribs._cke_saved_name )
    155 						delete attribs.name;
    156 					if ( attribs._cke_saved_href )
    157 						delete attribs.href;
    158 				},
    159
    160 				input : removeName,
    161 				textarea : removeName,
    162 				select : removeName,
    163 				form : removeName
    164 			},
    165
    166 			attributes :
    167 			{
    168 				'class' : function( value, element )
    169 				{
    170 					// Remove all class names starting with "cke_".
    171 					return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
    172 				}
    173 			},
    174
    175 			comment : function( contents )
    176 			{
    177 				if ( contents.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker )
    178 					return new CKEDITOR.htmlParser.cdata( decodeURIComponent( contents.substr( protectedSourceMarker.length ) ) );
    179
    180 				return contents;
    181 			}
    182 		};
    183
    184 	var defaultHtmlBlockFilterRules = { elements : {} };
    185
    186 	for ( i in blockLikeTags )
    187 		defaultHtmlBlockFilterRules.elements[ i ] = extendBlockForOutput;
    188
    189 	if ( CKEDITOR.env.ie )
    190 	{
    191 		// IE outputs style attribute in capital letters. We should convert
    192 		// them back to lower case.
    193 		defaultHtmlFilterRules.attributes.style = function( value, element )
    194 		{
    195 			return value.toLowerCase();
    196 		};
    197 	}
    198
    199 	var protectAttributeRegex = /<(?:a|area|img|input).*?\s((?:href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+)))/gi;
    200
    201 	function protectAttributes( html )
    202 	{
    203 		return html.replace( protectAttributeRegex, '$& _cke_saved_$1' );
    204 	}
    205
    206 	var protectStyleTagsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>/gi;
    207 	var encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
    208
    209 	function protectStyleTagsMatch( match )
    210 	{
    211 		return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
    212 	}
    213
    214 	function protectStyleTags( html )
    215 	{
    216 		return html.replace( protectStyleTagsRegex, protectStyleTagsMatch );
    217 	}
    218
    219 	function unprotectEncodedTagsMatch( match, encoded )
    220 	{
    221 		return decodeURIComponent( encoded );
    222 	}
    223
    224 	function unprotectEncodedTags( html )
    225 	{
    226 		return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch );
    227 	}
    228
    229 	function protectSource( data, protectRegexes )
    230 	{
    231 		var regexes =
    232 			[
    233 				// First of any other protection, we must protect all comments
    234 				// to avoid loosing them (of course, IE related).
    235 				/<!--[\s\S]*?-->/g,
    236
    237 				// Script tags will also be forced to be protected, otherwise
    238 				// IE will execute them.
    239 				/<script[\s\S]*?<\/script>/gi,
    240
    241 				// <noscript> tags (get lost in IE and messed up in FF).
    242 				/<noscript[\s\S]*?<\/noscript>/gi
    243 			]
    244 			.concat( protectRegexes );
    245
    246 		for ( var i = 0 ; i < regexes.length ; i++ )
    247 		{
    248 			data = data.replace( regexes[i], function( match )
    249 				{
    250 					return '<!--' + protectedSourceMarker + encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) + '-->';
    251 				});
    252 		}
    253
    254 		return data;
    255 	}
    256
    257 	CKEDITOR.plugins.add( 'htmldataprocessor',
    258 	{
    259 		requires : [ 'htmlwriter' ],
    260
    261 		init : function( editor )
    262 		{
    263 			var dataProcessor = editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor );
    264
    265 			dataProcessor.writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand;
    266
    267 			dataProcessor.dataFilter.addRules( defaultDataFilterRules );
    268 			dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules );
    269 			dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules );
    270 			dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules );
    271 		}
    272 	});
    273
    274 	CKEDITOR.htmlDataProcessor = function( editor )
    275 	{
    276 		this.editor = editor;
    277
    278 		this.writer = new CKEDITOR.htmlWriter();
    279 		this.dataFilter = new CKEDITOR.htmlParser.filter();
    280 		this.htmlFilter = new CKEDITOR.htmlParser.filter();
    281 	};
    282
    283 	CKEDITOR.htmlDataProcessor.prototype =
    284 	{
    285 		toHtml : function( data, fixForBody )
    286 		{
    287 			// The source data is already HTML, but we need to clean
    288 			// it up and apply the filter.
    289
    290 			data = protectSource( data, this.editor.config.protectedSource );
    291
    292 			// Before anything, we must protect the URL attributes as the
    293 			// browser may changing them when setting the innerHTML later in
    294 			// the code.
    295 			data = protectAttributes( data );
    296
    297 			// IE remvoes style tags from innerHTML. (#3710).
    298 			if ( CKEDITOR.env.ie )
    299 				data = protectStyleTags( data );
    300
    301 			// Call the browser to help us fixing a possibly invalid HTML
    302 			// structure.
    303 			var div = document.createElement( 'div' );
    304 			div.innerHTML = data;
    305 			data = div.innerHTML;
    306
    307 			if ( CKEDITOR.env.ie )
    308 				data = unprotectEncodedTags( data );
    309
    310 			// Now use our parser to make further fixes to the structure, as
    311 			// well as apply the filter.
    312 			var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ),
    313 				writer = new CKEDITOR.htmlParser.basicWriter();
    314
    315 			fragment.writeHtml( writer, this.dataFilter );
    316
    317 			return writer.getHtml( true );
    318 		},
    319
    320 		toDataFormat : function( html, fixForBody )
    321 		{
    322 			var writer = this.writer,
    323 				fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody );
    324
    325 			writer.reset();
    326
    327 			fragment.writeHtml( writer, this.htmlFilter );
    328
    329 			return writer.getHtml( true );
    330 		}
    331 	};
    332 })();
    333
    334 CKEDITOR.config.forceSimpleAmpersand = false;
    335