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