1 /*
  2 Copyright (c) 2003-2012, 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 	// Safari transforms the   to \xa0. (#4172)
 10 	var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/;
 11 
 12 	var protectedSourceMarker = '{cke_protected}';
 13 
 14 	// Return the last non-space child node of the block (#4344).
 15 	function lastNoneSpaceChild( block )
 16 	{
 17 		var lastIndex = block.children.length,
 18 			last = block.children[ lastIndex - 1 ];
 19 		while (  last && last.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( last.value ) )
 20 			last = block.children[ --lastIndex ];
 21 		return last;
 22 	}
 23 
 24 	function getNodeIndex( node ) {
 25 		var parent = node.parent;
 26 		return parent ? CKEDITOR.tools.indexOf( parent.children, node ) : -1;
 27 	}
 28 
 29 	function trimFillers( block, fromSource )
 30 	{
 31 		// If the current node is a block, and if we're converting from source or
 32 		// we're not in IE then search for and remove any tailing BR node.
 33 		//
 34 		// Also, any   at the end of blocks are fillers, remove them as well.
 35 		// (#2886)
 36 		var children = block.children, lastChild = lastNoneSpaceChild( block );
 37 		if ( lastChild )
 38 		{
 39 			if ( ( fromSource || !CKEDITOR.env.ie ) && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' )
 40 				children.pop();
 41 			if ( lastChild.type == CKEDITOR.NODE_TEXT && tailNbspRegex.test( lastChild.value ) )
 42 				children.pop();
 43 		}
 44 	}
 45 
 46 	function blockNeedsExtension( block, fromSource, extendEmptyBlock )
 47 	{
 48 		if( !fromSource && ( !extendEmptyBlock ||
 49 			typeof extendEmptyBlock == 'function' && ( extendEmptyBlock( block ) === false ) ) )
 50 			return false;
 51 
 52 	// 1. For IE version >=8,  empty blocks are displayed correctly themself in wysiwiyg;
 53 	// 2. For the rest, at least table cell and list item need no filler space.
 54 	// (#6248)
 55 	if ( fromSource && CKEDITOR.env.ie &&
 56 		( document.documentMode > 7
 57 			|| block.name in CKEDITOR.dtd.tr
 58 			|| block.name in CKEDITOR.dtd.$listItem ) )
 59 		return false;
 60 
 61 		var lastChild = lastNoneSpaceChild( block );
 62 
 63 		return !lastChild || lastChild &&
 64 				( lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br'
 65 				// Some of the controls in form needs extension too,
 66 				// to move cursor at the end of the form. (#4791)
 67 				|| block.name == 'form' && lastChild.name == 'input' );
 68 	}
 69 
 70 	function getBlockExtension( isOutput, emptyBlockFiller )
 71 	{
 72 		return function( node )
 73 		{
 74 			trimFillers( node, !isOutput );
 75 
 76 			if ( blockNeedsExtension( node, !isOutput, emptyBlockFiller ) )
 77 			{
 78 				if ( isOutput || CKEDITOR.env.ie )
 79 					node.add( new CKEDITOR.htmlParser.text( '\xa0' ) );
 80 				else
 81 					node.add( new CKEDITOR.htmlParser.element( 'br', {} ) );
 82 			}
 83 		};
 84 	}
 85 
 86 	var dtd = CKEDITOR.dtd;
 87 
 88 	// Define orders of table elements.
 89 	var tableOrder = [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ];
 90 
 91 	// Find out the list of block-like tags that can contain <br>.
 92 	var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent );
 93 	for ( var i in blockLikeTags )
 94 	{
 95 		if ( ! ( 'br' in dtd[i] ) )
 96 			delete blockLikeTags[i];
 97 	}
 98 	// We just avoid filler in <pre> right now.
 99 	// TODO: Support filler for <pre>, line break is also occupy line height.
100 	delete blockLikeTags.pre;
101 	var defaultDataFilterRules =
102 	{
103 		elements : {},
104 		attributeNames :
105 		[
106 			// Event attributes (onXYZ) must not be directly set. They can become
107 			// active in the editing area (IE|WebKit).
108 			[ ( /^on/ ), 'data-cke-pa-on' ]
109 		]
110 	};
111 
112 	var defaultDataBlockFilterRules = { elements : {} };
113 
114 	for ( i in blockLikeTags )
115 		defaultDataBlockFilterRules.elements[ i ] = getBlockExtension();
116 
117 	var defaultHtmlFilterRules =
118 		{
119 			elementNames :
120 			[
121 				// Remove the "cke:" namespace prefix.
122 				[ ( /^cke:/ ), '' ],
123 
124 				// Ignore <?xml:namespace> tags.
125 				[ ( /^\?xml:namespace$/ ), '' ]
126 			],
127 
128 			attributeNames :
129 			[
130 				// Attributes saved for changes and protected attributes.
131 				[ ( /^data-cke-(saved|pa)-/ ), '' ],
132 
133 				// All "data-cke-" attributes are to be ignored.
134 				[ ( /^data-cke-.*/ ), '' ],
135 
136 				[ 'hidefocus', '' ]
137 			],
138 
139 			elements :
140 			{
141 				$ : function( element )
142 				{
143 					var attribs = element.attributes;
144 
145 					if ( attribs )
146 					{
147 						// Elements marked as temporary are to be ignored.
148 						if ( attribs[ 'data-cke-temp' ] )
149 							return false;
150 
151 						// Remove duplicated attributes - #3789.
152 						var attributeNames = [ 'name', 'href', 'src' ],
153 							savedAttributeName;
154 						for ( var i = 0 ; i < attributeNames.length ; i++ )
155 						{
156 							savedAttributeName = 'data-cke-saved-' + attributeNames[ i ];
157 							savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] );
158 						}
159 					}
160 
161 					return element;
162 				},
163 
164 				// The contents of table should be in correct order (#4809).
165 				table : function( element )
166 				{
167 					// Clone the array as it would become empty during the sort call.
168 					var children = element.children.slice( 0 );
169 					children.sort( function ( node1, node2 )
170 								   {
171 										 var index1, index2;
172 
173 										 // Compare in the predefined order.
174 										 if ( node1.type == CKEDITOR.NODE_ELEMENT &&
175 													node2.type == node1.type )
176 										 {
177 											 index1 = CKEDITOR.tools.indexOf( tableOrder, node1.name );
178 											 index2 = CKEDITOR.tools.indexOf( tableOrder, node2.name );
179 										 }
180 
181 										 // Make sure the sort is stable, if no order can be established above.
182 										 if ( !( index1 > -1 && index2 > -1 && index1 != index2 ) )
183 										 {
184 											 index1 = getNodeIndex( node1 );
185 											 index2 = getNodeIndex( node2 );
186 										 }
187 
188 										 return index1 > index2 ? 1 : -1;
189 									 } );
190 				},
191 
192 				embed : function( element )
193 				{
194 					var parent = element.parent;
195 
196 					// If the <embed> is child of a <object>, copy the width
197 					// and height attributes from it.
198 					if ( parent && parent.name == 'object' )
199 					{
200 						var parentWidth = parent.attributes.width,
201 							parentHeight = parent.attributes.height;
202 						parentWidth && ( element.attributes.width = parentWidth );
203 						parentHeight && ( element.attributes.height = parentHeight );
204 					}
205 				},
206 				// Restore param elements into self-closing.
207 				param : function( param )
208 				{
209 					param.children = [];
210 					param.isEmpty = true;
211 					return param;
212 				},
213 
214 				// Remove empty link but not empty anchor.(#3829)
215 				a : function( element )
216 				{
217 					if ( !( element.children.length ||
218 							element.attributes.name ||
219 							element.attributes[ 'data-cke-saved-name' ] ) )
220 					{
221 						return false;
222 					}
223 				},
224 
225 				// Remove dummy span in webkit.
226 				span: function( element )
227 				{
228 					if ( element.attributes[ 'class' ] == 'Apple-style-span' )
229 						delete element.name;
230 				},
231 
232 				// Empty <pre> in IE is reported with filler node ( ).
233 				pre : function( element ) { CKEDITOR.env.ie && trimFillers( element ); },
234 
235 				html : function( element )
236 				{
237 					delete element.attributes.contenteditable;
238 					delete element.attributes[ 'class' ];
239 				},
240 
241 				body : function( element )
242 				{
243 					delete element.attributes.spellcheck;
244 					delete element.attributes.contenteditable;
245 				},
246 
247 				style : function( element )
248 				{
249 					var child = element.children[ 0 ];
250 					child && child.value && ( child.value = CKEDITOR.tools.trim( child.value ));
251 
252 					if ( !element.attributes.type )
253 						element.attributes.type = 'text/css';
254 				},
255 
256 				title : function( element )
257 				{
258 					var titleText = element.children[ 0 ];
259 					titleText && ( titleText.value = element.attributes[ 'data-cke-title' ] || '' );
260 				}
261 			},
262 
263 			attributes :
264 			{
265 				'class' : function( value, element )
266 				{
267 					// Remove all class names starting with "cke_".
268 					return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
269 				}
270 			}
271 		};
272 
273 	if ( CKEDITOR.env.ie )
274 	{
275 		// IE outputs style attribute in capital letters. We should convert
276 		// them back to lower case, while not hurting the values (#5930)
277 		defaultHtmlFilterRules.attributes.style = function( value, element )
278 		{
279 			return value.replace( /(^|;)([^\:]+)/g, function( match )
280 				{
281 					return match.toLowerCase();
282 				});
283 		};
284 	}
285 
286 	function protectReadOnly( element )
287 	{
288 		var attrs = element.attributes;
289 
290 		// We should flag that the element was locked by our code so
291 		// it'll be editable by the editor functions (#6046).
292 		if ( attrs.contenteditable != "false" )
293 			attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1;
294 
295 		attrs.contenteditable = "false";
296 	}
297 	function unprotectReadyOnly( element )
298 	{
299 		var attrs = element.attributes;
300 		switch( attrs[ 'data-cke-editable' ] )
301 		{
302 			case 'true':	attrs.contenteditable = 'true';	break;
303 			case '1':		delete attrs.contenteditable;	break;
304 		}
305 	}
306 	// Disable form elements editing mode provided by some browers. (#5746)
307 	for ( i in { input : 1, textarea : 1 } )
308 	{
309 		defaultDataFilterRules.elements[ i ] = protectReadOnly;
310 		defaultHtmlFilterRules.elements[ i ] = unprotectReadyOnly;
311 	}
312 
313 	var protectElementRegex = /<(a|area|img|input|source)\b([^>]*)>/gi,
314 		protectAttributeRegex = /\b(on\w+|href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi;
315 
316 	var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
317 		encodedElementsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
318 
319 	var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
320 		unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
321 
322 	var protectSelfClosingRegex = /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi;
323 
324 	function protectAttributes( html )
325 	{
326 		return html.replace( protectElementRegex, function( element, tag, attributes )
327 		{
328 			return '<' +  tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName )
329 			{
330 				// Avoid corrupting the inline event attributes (#7243).
331 				// We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218)
332 				if ( !( /^on/ ).test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 )
333 					return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr;
334 
335 				return fullAttr;
336 			}) + '>';
337 		});
338 	}
339 
340 	function protectElements( html )
341 	{
342 		return html.replace( protectElementsRegex, function( match )
343 			{
344 				return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
345 			});
346 	}
347 
348 	function unprotectElements( html )
349 	{
350 		return html.replace( encodedElementsRegex, function( match, encoded )
351 			{
352 				return decodeURIComponent( encoded );
353 			});
354 	}
355 
356 	function protectElementsNames( html )
357 	{
358 		return html.replace( protectElementNamesRegex, '$1cke:$2');
359 	}
360 
361 	function unprotectElementNames( html )
362 	{
363 		return html.replace( unprotectElementNamesRegex, '$1$2' );
364 	}
365 
366 	function protectSelfClosingElements( html )
367 	{
368 		return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
369 	}
370 
371 	function protectPreFormatted( html )
372 	{
373 		return html.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
374 	}
375 
376 	function protectRealComments( html )
377 	{
378 		return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match )
379 			{
380 				return '<!--' + protectedSourceMarker +
381 						'{C}' +
382 						encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
383 						'-->';
384 			});
385 	}
386 
387 	function unprotectRealComments( html )
388 	{
389 		return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data )
390 			{
391 				return decodeURIComponent( data );
392 			});
393 	}
394 
395 	function unprotectSource( html, editor )
396 	{
397 		var store = editor._.dataStore;
398 
399 		return html.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match, data )
400 			{
401 				return decodeURIComponent( data );
402 			}).replace( /\{cke_protected_(\d+)\}/g, function( match, id )
403 			{
404 				return store && store[ id ] || '';
405 			});
406 	}
407 
408 	function protectSource( data, editor )
409 	{
410 		var protectedHtml = [],
411 			protectRegexes = editor.config.protectedSource,
412 			store = editor._.dataStore || ( editor._.dataStore = { id : 1 } ),
413 			tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g;
414 
415 		var regexes =
416 			[
417 				// Script tags will also be forced to be protected, otherwise
418 				// IE will execute them.
419 				( /<script[\s\S]*?<\/script>/gi ),
420 
421 				// <noscript> tags (get lost in IE and messed up in FF).
422 				/<noscript[\s\S]*?<\/noscript>/gi
423 			]
424 			.concat( protectRegexes );
425 
426 		// First of any other protection, we must protect all comments
427 		// to avoid loosing them (of course, IE related).
428 		// Note that we use a different tag for comments, as we need to
429 		// transform them when applying filters.
430 		data = data.replace( (/<!--[\s\S]*?-->/g), function( match )
431 			{
432 				return  '<!--{cke_tempcomment}' + ( protectedHtml.push( match ) - 1 ) + '-->';
433 			});
434 
435 		for ( var i = 0 ; i < regexes.length ; i++ )
436 		{
437 			data = data.replace( regexes[i], function( match )
438 				{
439 					match = match.replace( tempRegex, 		// There could be protected source inside another one. (#3869).
440 						function( $, isComment, id )
441 						{
442 							return protectedHtml[ id ];
443 						}
444 					);
445 
446 					// Avoid protecting over protected, e.g. /\{.*?\}/
447 					return ( /cke_temp(comment)?/ ).test( match ) ? match
448 						: '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->';
449 				});
450 		}
451 		data = data.replace( tempRegex,	function( $, isComment, id )
452 			{
453 				return '<!--' + protectedSourceMarker +
454 						( isComment ? '{C}' : '' ) +
455 						encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) +
456 						'-->';
457 			}
458 		);
459 
460 		// Different protection pattern is used for those that
461 		// live in attributes to avoid from being HTML encoded.
462 		return data.replace( /(['"]).*?\1/g, function ( match )
463 		{
464 			return match.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match, data )
465 			{
466 				store[ store.id ] = decodeURIComponent( data );
467 				return '{cke_protected_'+ ( store.id++ )  + '}';
468 			});
469 		});
470 	}
471 
472 	CKEDITOR.plugins.add( 'htmldataprocessor',
473 	{
474 		requires : [ 'htmlwriter' ],
475 
476 		init : function( editor )
477 		{
478 			var dataProcessor = editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor );
479 
480 			dataProcessor.writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand;
481 
482 			dataProcessor.dataFilter.addRules( defaultDataFilterRules );
483 			dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules );
484 			dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules );
485 
486 			var defaultHtmlBlockFilterRules = { elements : {} };
487 			for ( i in blockLikeTags )
488 				defaultHtmlBlockFilterRules.elements[ i ] = getBlockExtension( true, editor.config.fillEmptyBlocks );
489 
490 			dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules );
491 		},
492 
493 		onLoad : function()
494 		{
495 			! ( 'fillEmptyBlocks' in CKEDITOR.config ) && ( CKEDITOR.config.fillEmptyBlocks = 1 );
496 		}
497 	});
498 
499 	CKEDITOR.htmlDataProcessor = function( editor )
500 	{
501 		this.editor = editor;
502 
503 		this.writer = new CKEDITOR.htmlWriter();
504 		this.dataFilter = new CKEDITOR.htmlParser.filter();
505 		this.htmlFilter = new CKEDITOR.htmlParser.filter();
506 	};
507 
508 	CKEDITOR.htmlDataProcessor.prototype =
509 	{
510 		toHtml : function( data, fixForBody )
511 		{
512 			// The source data is already HTML, but we need to clean
513 			// it up and apply the filter.
514 
515 			data = protectSource( data, this.editor );
516 
517 			// Before anything, we must protect the URL attributes as the
518 			// browser may changing them when setting the innerHTML later in
519 			// the code.
520 			data = protectAttributes( data );
521 
522 			// Protect elements than can't be set inside a DIV. E.g. IE removes
523 			// style tags from innerHTML. (#3710)
524 			data = protectElements( data );
525 
526 			// Certain elements has problem to go through DOM operation, protect
527 			// them by prefixing 'cke' namespace. (#3591)
528 			data = protectElementsNames( data );
529 
530 			// All none-IE browsers ignore self-closed custom elements,
531 			// protecting them into open-close. (#3591)
532 			data = protectSelfClosingElements( data );
533 
534 			// Compensate one leading line break after <pre> open as browsers
535 			// eat it up. (#5789)
536 			data = protectPreFormatted( data );
537 
538 			// Call the browser to help us fixing a possibly invalid HTML
539 			// structure.
540 			var div = new CKEDITOR.dom.element( 'div' );
541 
542 			// Add fake character to workaround IE comments bug. (#3801)
543 			div.setHtml( 'a' + data );
544 			data = div.getHtml().substr( 1 );
545 
546 			// Restore shortly protected attribute names.
547 			data = data.replace( new RegExp( ' data-cke-' + CKEDITOR.rnd + '-', 'ig' ), ' ' );
548 
549 			// Unprotect "some" of the protected elements at this point.
550 			data = unprotectElementNames( data );
551 
552 			data = unprotectElements( data );
553 
554 			// Restore the comments that have been protected, in this way they
555 			// can be properly filtered.
556 			data = unprotectRealComments( data );
557 
558 			// Now use our parser to make further fixes to the structure, as
559 			// well as apply the filter.
560 			var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ),
561 				writer = new CKEDITOR.htmlParser.basicWriter();
562 
563 			fragment.writeHtml( writer, this.dataFilter );
564 			data = writer.getHtml( true );
565 
566 			// Protect the real comments again.
567 			data = protectRealComments( data );
568 
569 			return data;
570 		},
571 
572 		toDataFormat : function( html, fixForBody )
573 		{
574 			var writer = this.writer,
575 				fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody );
576 
577 			writer.reset();
578 
579 			fragment.writeHtml( writer, this.htmlFilter );
580 
581 			var data = writer.getHtml( true );
582 
583 			// Restore those non-HTML protected source. (#4475,#4880)
584 			data = unprotectRealComments( data );
585 			data = unprotectSource( data, this.editor );
586 
587 			return data;
588 		}
589 	};
590 })();
591 
592 /**
593  * Whether to force using "&" instead of "&amp;" in elements attributes
594  * values, it's not recommended to change this setting for compliance with the
595  * W3C XHTML 1.0 standards (<a href="http://www.w3.org/TR/xhtml1/#C_12">C.12, XHTML 1.0</a>).
596  * @name CKEDITOR.config.forceSimpleAmpersand
597  * @name CKEDITOR.config.forceSimpleAmpersand
598  * @type Boolean
599  * @default false
600  * @example
601  * config.forceSimpleAmpersand = false;
602  */
603 
604 /**
605  * Whether a filler text (non-breaking space entity -  ) will be inserted into empty block elements in HTML output,
606  * this is used to render block elements properly with line-height; When a function is instead specified,
607  * it'll be passed a {@link CKEDITOR.htmlParser.element} to decide whether adding the filler text
608  * by expecting a boolean return value.
609  * @name CKEDITOR.config.fillEmptyBlocks
610  * @since 3.5
611  * @type Boolean
612  * @default true
613  * @example
614  * config.fillEmptyBlocks = false;	// Prevent filler nodes in all empty blocks.
615  *
616  * // Prevent filler node only in float cleaners.
617  * config.fillEmptyBlocks = function( element )
618  * {
619  * 	if ( element.attributes[ 'class' ].indexOf ( 'clear-both' ) != -1 )
620  * 		return false;
621  * }
622  */
623