2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
8 * Represents an HTML data processor, which is responsible for translating and
9 * transforming the editor data on input and output.
12 * @extends CKEDITOR.dataProcessor
13 * @constructor Creates an htmlDataProcessor class instance.
14 * @param {CKEDITOR.editor} editor
16 CKEDITOR
.htmlDataProcessor = function( editor
) {
17 var dataFilter
, htmlFilter
,
23 * Data filter used when processing input by {@link #toHtml}.
25 * @property {CKEDITOR.htmlParser.filter}
27 this.dataFilter
= dataFilter
= new CKEDITOR
.htmlParser
.filter();
30 * HTML filter used when processing output by {@link #toDataFormat}.
32 * @property {CKEDITOR.htmlParser.filter}
34 this.htmlFilter
= htmlFilter
= new CKEDITOR
.htmlParser
.filter();
37 * The HTML writer used by this data processor to format the output.
39 * @property {CKEDITOR.htmlParser.basicWriter}
41 this.writer
= new CKEDITOR
.htmlParser
.basicWriter();
43 dataFilter
.addRules( defaultDataFilterRulesEditableOnly
);
44 dataFilter
.addRules( defaultDataFilterRulesForAll
, { applyToAll: true } );
45 dataFilter
.addRules( createBogusAndFillerRules( editor
, 'data' ), { applyToAll: true } );
46 htmlFilter
.addRules( defaultHtmlFilterRulesEditableOnly
);
47 htmlFilter
.addRules( defaultHtmlFilterRulesForAll
, { applyToAll: true } );
48 htmlFilter
.addRules( createBogusAndFillerRules( editor
, 'html' ), { applyToAll: true } );
50 editor
.on( 'toHtml', function( evt
) {
51 var evtData
= evt
.data
,
52 data
= evtData
.dataValue
,
55 // The source data is already HTML, but we need to clean
56 // it up and apply the filter.
57 data
= protectSource( data
, editor
);
59 // Protect content of textareas. (#9995)
60 // Do this before protecting attributes to avoid breaking:
61 // <textarea><img src="..." /></textarea>
62 data
= protectElements( data
, protectTextareaRegex
);
64 // Before anything, we must protect the URL attributes as the
65 // browser may changing them when setting the innerHTML later in
67 data
= protectAttributes( data
);
69 // Protect elements than can't be set inside a DIV. E.g. IE removes
70 // style tags from innerHTML. (#3710)
71 data
= protectElements( data
, protectElementsRegex
);
73 // Certain elements has problem to go through DOM operation, protect
74 // them by prefixing 'cke' namespace. (#3591)
75 data
= protectElementsNames( data
);
77 // All none-IE browsers ignore self-closed custom elements,
78 // protecting them into open-close. (#3591)
79 data
= protectSelfClosingElements( data
);
81 // Compensate one leading line break after <pre> open as browsers
83 data
= protectPreFormatted( data
);
85 // There are attributes which may execute JavaScript code inside fixBin.
86 // Encode them greedily. They will be unprotected right after getting HTML from fixBin. (#10)
87 data
= protectInsecureAttributes( data
);
89 var fixBin
= evtData
.context
|| editor
.editable().getName(),
92 // Old IEs loose formats when load html into <pre>.
93 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.version
< 9 && fixBin
== 'pre' ) {
95 data
= '<pre>' + data
+ '</pre>';
99 // Call the browser to help us fixing a possibly invalid HTML
101 var el
= editor
.document
.createElement( fixBin
);
102 // Add fake character to workaround IE comments bug. (#3801)
103 el
.setHtml( 'a' + data
);
104 data
= el
.getHtml().substr( 1 );
106 // Restore shortly protected attribute names.
107 data
= data
.replace( new RegExp( 'data-cke-' + CKEDITOR
.rnd
+ '-', 'ig' ), '' );
109 isPre
&& ( data
= data
.replace( /^<pre>|<\/pre>$/gi, '' ) );
111 // Unprotect "some" of the protected elements at this point.
112 data
= unprotectElementNames( data
);
114 data
= unprotectElements( data
);
116 // Restore the comments that have been protected, in this way they
117 // can be properly filtered.
118 data
= unprotectRealComments( data
);
120 if ( evtData
.fixForBody
=== false ) {
123 fixBodyTag
= getFixBodyTag( evtData
.enterMode
, editor
.config
.autoParagraph
);
126 // Now use our parser to make further fixes to the structure, as
127 // well as apply the filter.
128 data
= CKEDITOR
.htmlParser
.fragment
.fromHtml( data
, evtData
.context
, fixBodyTag
);
130 // The empty root element needs to be fixed by adding 'p' or 'div' into it.
131 // This avoids the need to create that element on the first focus (#12630).
133 fixEmptyRoot( data
, fixBodyTag
);
136 evtData
.dataValue
= data
;
139 // Filter incoming "data".
140 // Add element filter before htmlDataProcessor.dataFilter when purifying input data to correct html.
141 editor
.on( 'toHtml', function( evt
) {
142 if ( evt
.data
.filter
.applyTo( evt
.data
.dataValue
, true, evt
.data
.dontFilter
, evt
.data
.enterMode
) )
143 editor
.fire( 'dataFiltered' );
146 editor
.on( 'toHtml', function( evt
) {
147 evt
.data
.dataValue
.filterChildren( that
.dataFilter
, true );
150 editor
.on( 'toHtml', function( evt
) {
151 var evtData
= evt
.data
,
152 data
= evtData
.dataValue
,
153 writer
= new CKEDITOR
.htmlParser
.basicWriter();
155 data
.writeChildrenHtml( writer
);
156 data
= writer
.getHtml( true );
158 // Protect the real comments again.
159 evtData
.dataValue
= protectRealComments( data
);
163 editor
.on( 'toDataFormat', function( evt
) {
164 var data
= evt
.data
.dataValue
;
166 // #10854 - we need to strip leading blockless <br> which FF adds
167 // automatically when editable contains only non-editable content.
168 // We do that for every browser (so it's a constant behavior) and
169 // not in BR mode, in which chance of valid leading blockless <br> is higher.
170 if ( evt
.data
.enterMode
!= CKEDITOR
.ENTER_BR
)
171 data
= data
.replace( /^<br
*\/?>/i, '' );
173 evt
.data
.dataValue
= CKEDITOR
.htmlParser
.fragment
.fromHtml(
174 data
, evt
.data
.context
, getFixBodyTag( evt
.data
.enterMode
, editor
.config
.autoParagraph
) );
177 editor
.on( 'toDataFormat', function( evt
) {
178 evt
.data
.dataValue
.filterChildren( that
.htmlFilter
, true );
181 // Transform outcoming "data".
182 // Add element filter after htmlDataProcessor.htmlFilter when preparing output data HTML.
183 editor
.on( 'toDataFormat', function( evt
) {
184 evt
.data
.filter
.applyTo( evt
.data
.dataValue
, false, true );
187 editor
.on( 'toDataFormat', function( evt
) {
188 var data
= evt
.data
.dataValue
,
189 writer
= that
.writer
;
192 data
.writeChildrenHtml( writer
);
193 data
= writer
.getHtml( true );
195 // Restore those non-HTML protected source. (#4475,#4880)
196 data
= unprotectRealComments( data
);
197 data
= unprotectSource( data
, editor
);
199 evt
.data
.dataValue
= data
;
203 CKEDITOR
.htmlDataProcessor
.prototype = {
205 * Processes the (potentially malformed) input HTML to a purified form which
206 * is suitable for using in the WYSIWYG editable.
208 * This method fires the {@link CKEDITOR.editor#toHtml} event which makes it possible
209 * to hook into the process at various stages.
211 * **Note:** Since CKEditor 4.3 the signature of this method changed and all options
212 * are now grouped in one `options` object. Previously `context`, `fixForBody` and `dontFilter`
213 * were passed separately.
215 * @param {String} data The raw data.
216 * @param {Object} [options] The options object.
217 * @param {String} [options.context] The tag name of a context element within which
218 * the input is to be processed, defaults to the editable element.
219 * If `null` is passed, then data will be parsed without context (as children of {@link CKEDITOR.htmlParser.fragment}).
220 * See {@link CKEDITOR.htmlParser.fragment#fromHtml} for more details.
221 * @param {Boolean} [options.fixForBody=true] Whether to trigger the auto paragraph for non-block content.
222 * @param {CKEDITOR.filter} [options.filter] When specified, instead of using the {@link CKEDITOR.editor#filter main filter},
223 * the passed instance will be used to filter the content.
224 * @param {Boolean} [options.dontFilter] Do not filter data with {@link CKEDITOR.filter} (note: transformations
225 * will still be applied).
226 * @param {Number} [options.enterMode] When specified, it will be used instead of the {@link CKEDITOR.editor#enterMode main enterMode}.
227 * @param {Boolean} [options.protectedWhitespaces] Indicates that content was wrapped with `<span>` elements to preserve
228 * leading and trailing whitespaces. Option used by the {@link CKEDITOR.editor#method-insertHtml} method.
231 toHtml: function( data
, options
, fixForBody
, dontFilter
) {
232 var editor
= this.editor
,
233 context
, filter
, enterMode
, protectedWhitespaces
;
235 // Typeof null == 'object', so check truthiness of options too.
236 if ( options
&& typeof options
== 'object' ) {
237 context
= options
.context
;
238 fixForBody
= options
.fixForBody
;
239 dontFilter
= options
.dontFilter
;
240 filter
= options
.filter
;
241 enterMode
= options
.enterMode
;
242 protectedWhitespaces
= options
.protectedWhitespaces
;
244 // Backward compatibility. Since CKEDITOR 4.3 every option was a separate argument.
249 // Fall back to the editable as context if not specified.
250 if ( !context
&& context
!== null )
251 context
= editor
.editable().getName();
253 return editor
.fire( 'toHtml', {
256 fixForBody: fixForBody
,
257 dontFilter: dontFilter
,
258 filter: filter
|| editor
.filter
,
259 enterMode: enterMode
|| editor
.enterMode
,
260 protectedWhitespaces: protectedWhitespaces
265 * See {@link CKEDITOR.dataProcessor#toDataFormat}.
267 * This method fires the {@link CKEDITOR.editor#toDataFormat} event which makes it possible
268 * to hook into the process at various stages.
270 * @param {String} html
271 * @param {Object} [options] The options object.
272 * @param {String} [options.context] The tag name of the context element within which
273 * the input is to be processed, defaults to the editable element.
274 * @param {CKEDITOR.filter} [options.filter] When specified, instead of using the {@link CKEDITOR.editor#filter main filter},
275 * the passed instance will be used to apply content transformations to the content.
276 * @param {Number} [options.enterMode] When specified, it will be used instead of the {@link CKEDITOR.editor#enterMode main enterMode}.
279 toDataFormat: function( html
, options
) {
280 var context
, filter
, enterMode
;
282 // Do not shorten this to `options && options.xxx`, because
283 // falsy `options` will be passed instead of undefined.
285 context
= options
.context
;
286 filter
= options
.filter
;
287 enterMode
= options
.enterMode
;
290 // Fall back to the editable as context if not specified.
291 if ( !context
&& context
!== null )
292 context
= this.editor
.editable().getName();
294 return this.editor
.fire( 'toDataFormat', {
296 filter: filter
|| this.editor
.filter
,
298 enterMode: enterMode
|| this.editor
.enterMode
303 // Produce a set of filtering rules that handles bogus and filler node at the
304 // end of block/pseudo block, in the following consequence:
305 // 1. elements:<block> - this filter removes any bogus node, then check
306 // if it's an empty block that requires a filler.
307 // 2. elements:<br> - After cleaned with bogus, this filter checks the real
308 // line-break BR to compensate a filler after it.
310 // Terms definitions:
311 // filler: An element that's either <BR> or &NBSP; at the end of block that established line height.
312 // bogus: Whenever a filler is proceeded with inline content, it becomes a bogus which is subjected to be removed.
314 // Various forms of the filler:
315 // In output HTML: Filler should be consistently &NBSP; <BR> at the end of block is always considered as bogus.
316 // In Wysiwyg HTML: Browser dependent - see env.needsBrFiller. Either BR for when needsBrFiller is true, or &NBSP; otherwise.
317 // <BR> is NEVER considered as bogus when needsBrFiller is true.
318 function createBogusAndFillerRules( editor
, type
) {
319 function createFiller( isOutput
) {
320 return isOutput
|| CKEDITOR
.env
.needsNbspFiller
?
321 new CKEDITOR
.htmlParser
.text( '\xa0' ) :
322 new CKEDITOR
.htmlParser
.element( 'br', { 'data-cke-bogus': 1 } );
325 // This text block filter, remove any bogus and create the filler on demand.
326 function blockFilter( isOutput
, fillEmptyBlock
) {
328 return function( block
) {
329 // DO NOT apply the filler if it's a fragment node.
330 if ( block
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
)
335 // Add fillers to input (always) and to output (if fillEmptyBlock is ok with that).
336 var shouldFillBlock
= !isOutput
||
337 ( typeof fillEmptyBlock
== 'function' ? fillEmptyBlock( block
) : fillEmptyBlock
) !== false;
339 if ( shouldFillBlock
&& isEmptyBlockNeedFiller( block
) ) {
340 block
.add( createFiller( isOutput
) );
345 // Append a filler right after the last line-break BR, found at the end of block.
346 function brFilter( isOutput
) {
347 return function( br
) {
348 // DO NOT apply the filer if parent's a fragment node.
349 if ( br
.parent
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
)
352 var attrs
= br
.attributes
;
353 // Dismiss BRs that are either bogus or eol marker.
354 if ( 'data-cke-bogus' in attrs
|| 'data-cke-eol' in attrs
) {
355 delete attrs
[ 'data-cke-bogus' ];
359 // Judge the tail line-break BR, and to insert bogus after it.
360 var next
= getNext( br
), previous
= getPrevious( br
);
362 if ( !next
&& isBlockBoundary( br
.parent
) )
363 append( br
.parent
, createFiller( isOutput
) );
364 else if ( isBlockBoundary( next
) && previous
&& !isBlockBoundary( previous
) )
365 createFiller( isOutput
).insertBefore( next
);
369 // Determinate whether this node is potentially a bogus node.
370 function maybeBogus( node
, atBlockEnd
) {
372 // BR that's not from IE<11 DOM, except for a EOL marker.
373 if ( !( isOutput
&& !CKEDITOR
.env
.needsBrFiller
) &&
374 node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.name
== 'br' &&
375 !node
.attributes
[ 'data-cke-eol' ] ) {
382 if ( node
.type
== CKEDITOR
.NODE_TEXT
&& ( match
= node
.value
.match( tailNbspRegex
) ) ) {
383 // We need to separate tail NBSP out of a text node, for later removal.
385 ( new CKEDITOR
.htmlParser
.text( node
.value
.substring( 0, match
.index
) ) ).insertBefore( node
);
386 node
.value
= match
[ 0 ];
389 // From IE<11 DOM, at the end of a text block, or before block boundary.
390 if ( !CKEDITOR
.env
.needsBrFiller
&& isOutput
&& ( !atBlockEnd
|| node
.parent
.name
in textBlockTags
) )
395 var previous
= node
.previous
;
397 // Following a line-break at the end of block.
398 if ( previous
&& previous
.name
== 'br' )
401 // Or a single NBSP between two blocks.
402 if ( !previous
|| isBlockBoundary( previous
) )
410 // Removes all bogus inside of this block, and to convert fillers into the proper form.
411 function cleanBogus( block
) {
413 var last
= getLast( block
), node
, previous
;
416 // Check for bogus at the end of this block.
417 // e.g. <p>foo<br /></p>
418 maybeBogus( last
, 1 ) && bogus
.push( last
);
421 // Check for bogus at the end of any pseudo block contained.
422 if ( isBlockBoundary( last
) && ( node
= getPrevious( last
) ) && maybeBogus( node
) ) {
423 // Bogus must have inline proceeding, instead single BR between two blocks,
424 // is considered as filler, e.g. <hr /><br /><hr />
425 if ( ( previous
= getPrevious( node
) ) && !isBlockBoundary( previous
) )
427 // Convert the filler into appropriate form.
429 createFiller( isOutput
).insertAfter( node
);
434 last
= last
.previous
;
438 // Now remove all bogus collected from above.
439 for ( var i
= 0 ; i
< bogus
.length
; i
++ )
443 // Judge whether it's an empty block that requires a filler node.
444 function isEmptyBlockNeedFiller( block
) {
446 // DO NOT fill empty editable in IE<11.
447 if ( !isOutput
&& !CKEDITOR
.env
.needsBrFiller
&& block
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
)
450 // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg;
451 // 2. For the rest, at least table cell and list item need no filler space. (#6248)
452 if ( !isOutput
&& !CKEDITOR
.env
.needsBrFiller
&&
453 ( document
.documentMode
> 7 ||
454 block
.name
in CKEDITOR
.dtd
.tr
||
455 block
.name
in CKEDITOR
.dtd
.$listItem
) ) {
459 var last
= getLast( block
);
460 return !last
|| block
.name
== 'form' && last
.name
== 'input' ;
463 var rules
= { elements: {} },
464 isOutput
= type
== 'html',
465 textBlockTags
= CKEDITOR
.tools
.extend( {}, blockLikeTags
);
467 // Build the list of text blocks.
468 for ( var i
in textBlockTags
) {
469 if ( !( '#' in dtd
[ i
] ) )
470 delete textBlockTags
[ i
];
473 for ( i
in textBlockTags
)
474 rules
.elements
[ i
] = blockFilter( isOutput
, editor
.config
.fillEmptyBlocks
);
476 // Editable element has to be checked separately.
477 rules
.root
= blockFilter( isOutput
, false );
478 rules
.elements
.br
= brFilter( isOutput
);
482 function getFixBodyTag( enterMode
, autoParagraph
) {
483 return ( enterMode
!= CKEDITOR
.ENTER_BR
&& autoParagraph
!== false ) ? enterMode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' : false;
486 // Regex to scan for at the end of blocks, which are actually placeholders.
487 // Safari transforms the to \xa0. (#4172)
488 var tailNbspRegex
= /(?: |\xa0)$/;
490 var protectedSourceMarker
= '{cke_protected}';
492 function getLast( node
) {
493 var last
= node
.children
[ node
.children
.length
- 1 ];
494 while ( last
&& isEmpty( last
) )
495 last
= last
.previous
;
499 function getNext( node
) {
500 var next
= node
.next
;
501 while ( next
&& isEmpty( next
) )
506 function getPrevious( node
) {
507 var previous
= node
.previous
;
508 while ( previous
&& isEmpty( previous
) )
509 previous
= previous
.previous
;
513 // Judge whether the node is an ghost node to be ignored, when traversing.
514 function isEmpty( node
) {
515 return node
.type
== CKEDITOR
.NODE_TEXT
&&
516 !CKEDITOR
.tools
.trim( node
.value
) ||
517 node
.type
== CKEDITOR
.NODE_ELEMENT
&&
518 node
.attributes
[ 'data-cke-bookmark' ];
521 // Judge whether the node is a block-like element.
522 function isBlockBoundary( node
) {
524 ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.name
in blockLikeTags
||
525 node
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
);
528 function append( parent
, node
) {
529 var last
= parent
.children
[ parent
.children
.length
- 1 ];
530 parent
.children
.push( node
);
531 node
.parent
= parent
;
534 node
.previous
= last
;
538 function getNodeIndex( node
) {
539 return node
.parent
? node
.getIndex() : -1;
542 var dtd
= CKEDITOR
.dtd
,
543 // Define orders of table elements.
544 tableOrder
= [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ],
545 // List of all block elements.
546 blockLikeTags
= CKEDITOR
.tools
.extend( {}, dtd
.$blockLimit
, dtd
.$block
);
549 // DATA filter rules ------------------------------------------------------
552 var defaultDataFilterRulesEditableOnly
= {
554 input: protectReadOnly
,
555 textarea: protectReadOnly
559 // These rules will also be applied to non-editable content.
560 var defaultDataFilterRulesForAll
= {
562 // Event attributes (onXYZ) must not be directly set. They can become
563 // active in the editing area (IE|WebKit).
564 [ ( /^on/ ), 'data-cke-pa-on' ],
566 // Don't let some old expando enter editor. Concerns only IE8,
567 // but for consistency remove on all browsers.
568 [ ( /^data-cke-expando$/ ), '' ]
572 // Disable form elements editing mode provided by some browsers. (#5746)
573 function protectReadOnly( element
) {
574 var attrs
= element
.attributes
;
576 // We should flag that the element was locked by our code so
577 // it'll be editable by the editor functions (#6046).
578 if ( attrs
.contenteditable
!= 'false' )
579 attrs
[ 'data-cke-editable' ] = attrs
.contenteditable
? 'true' : 1;
581 attrs
.contenteditable
= 'false';
585 // HTML filter rules ------------------------------------------------------
588 var defaultHtmlFilterRulesEditableOnly
= {
590 embed: function( element
) {
591 var parent
= element
.parent
;
593 // If the <embed> is child of a <object>, copy the width
594 // and height attributes from it.
595 if ( parent
&& parent
.name
== 'object' ) {
596 var parentWidth
= parent
.attributes
.width
,
597 parentHeight
= parent
.attributes
.height
;
599 element
.attributes
.width
= parentWidth
;
601 element
.attributes
.height
= parentHeight
;
605 // Remove empty link but not empty anchor. (#3829, #13516)
606 a: function( element
) {
607 var attrs
= element
.attributes
;
609 if ( !( element
.children
.length
|| attrs
.name
|| attrs
.id
|| element
.attributes
[ 'data-cke-saved-name' ] ) )
615 // These rules will also be applied to non-editable content.
616 var defaultHtmlFilterRulesForAll
= {
618 // Remove the "cke:" namespace prefix.
621 // Ignore <?xml:namespace> tags.
622 [ ( /^\?xml:namespace$/ ), '' ]
626 // Attributes saved for changes and protected attributes.
627 [ ( /^data-cke-(saved|pa)-/ ), '' ],
629 // All "data-cke-" attributes are to be ignored.
630 [ ( /^data-cke-.*/ ), '' ],
636 $: function( element
) {
637 var attribs
= element
.attributes
;
640 // Elements marked as temporary are to be ignored.
641 if ( attribs
[ 'data-cke-temp' ] )
644 // Remove duplicated attributes - #3789.
645 var attributeNames
= [ 'name', 'href', 'src' ],
647 for ( var i
= 0; i
< attributeNames
.length
; i
++ ) {
648 savedAttributeName
= 'data-cke-saved-' + attributeNames
[ i
];
649 savedAttributeName
in attribs
&& ( delete attribs
[ attributeNames
[ i
] ] );
656 // The contents of table should be in correct order (#4809).
657 table: function( element
) {
658 // Clone the array as it would become empty during the sort call.
659 var children
= element
.children
.slice( 0 );
661 children
.sort( function( node1
, node2
) {
664 // Compare in the predefined order.
665 if ( node1
.type
== CKEDITOR
.NODE_ELEMENT
&& node2
.type
== node1
.type
) {
666 index1
= CKEDITOR
.tools
.indexOf( tableOrder
, node1
.name
);
667 index2
= CKEDITOR
.tools
.indexOf( tableOrder
, node2
.name
);
670 // Make sure the sort is stable, if no order can be established above.
671 if ( !( index1
> -1 && index2
> -1 && index1
!= index2
) ) {
672 index1
= getNodeIndex( node1
);
673 index2
= getNodeIndex( node2
);
676 return index1
> index2
? 1 : -1;
680 // Restore param elements into self-closing.
681 param: function( param
) {
683 param
.isEmpty
= true;
687 // Remove dummy span in webkit.
688 span: function( element
) {
689 if ( element
.attributes
[ 'class' ] == 'Apple-style-span' )
693 html: function( element
) {
694 delete element
.attributes
.contenteditable
;
695 delete element
.attributes
[ 'class' ];
698 body: function( element
) {
699 delete element
.attributes
.spellcheck
;
700 delete element
.attributes
.contenteditable
;
703 style: function( element
) {
704 var child
= element
.children
[ 0 ];
705 if ( child
&& child
.value
)
706 child
.value
= CKEDITOR
.tools
.trim( child
.value
);
708 if ( !element
.attributes
.type
)
709 element
.attributes
.type
= 'text/css';
712 title: function( element
) {
713 var titleText
= element
.children
[ 0 ];
715 // Append text-node to title tag if not present (i.e. non-IEs) (#9882).
716 !titleText
&& append( element
, titleText
= new CKEDITOR
.htmlParser
.text() );
718 // Transfer data-saved title to title tag.
719 titleText
.value
= element
.attributes
[ 'data-cke-title' ] || '';
722 input: unprotectReadyOnly
,
723 textarea: unprotectReadyOnly
727 'class': function( value
) {
728 // Remove all class names starting with "cke_".
729 return CKEDITOR
.tools
.ltrim( value
.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
734 if ( CKEDITOR
.env
.ie
) {
735 // IE outputs style attribute in capital letters. We should convert
736 // them back to lower case, while not hurting the values (#5930)
737 defaultHtmlFilterRulesForAll
.attributes
.style = function( value
) {
738 return value
.replace( /(^|;)([^\:]+)/g, function( match
) {
739 return match
.toLowerCase();
744 // Disable form elements editing mode provided by some browsers. (#5746)
745 function unprotectReadyOnly( element
) {
746 var attrs
= element
.attributes
;
747 switch ( attrs
[ 'data-cke-editable' ] ) {
749 attrs
.contenteditable
= 'true';
752 delete attrs
.contenteditable
;
758 // Preprocessor filters ---------------------------------------------------
761 var protectElementRegex
= /<(a|area|img|input|source)\b([^>]*)>/gi,
762 // Be greedy while looking for protected attributes. This will let us avoid an unfortunate
763 // situation when "nested attributes", which may appear valid, are also protected.
764 // I.e. if we consider the following HTML:
766 // <img data-x="<a href="X"" />
768 // then the "non-greedy match" returns:
770 // 'href' => '"X"' // It's wrong! Href is not an attribute of <img>.
772 // while greedy match returns:
774 // 'data-x' => '<a href="X"'
776 // which, can be easily filtered out (#11508).
777 protectAttributeRegex
= /([\w
-:]+)\s
*=\s
*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,
778 protectAttributeNameRegex = /^(href|src|name)$/i;
780 // Note: we use lazy star '*?' to prevent eating everything up to the last occurrence of </style> or </textarea>.
781 var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi,
782 protectTextareaRegex = /(<textarea(?=[ >])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi,
783 encodedElementsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi;
785 var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
786 unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
788 var protectSelfClosingRegex = /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi;
790 function protectAttributes( html ) {
791 return html.replace( protectElementRegex, function( element, tag, attributes ) {
792 return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) {
793 // Avoid corrupting the inline event attributes (#7243).
794 // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218)
795 if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data
-cke
-saved
-' + attrName ) == -1 )
796 return ' data
-cke
-saved
-' + fullAttr + ' data
-cke
-' + CKEDITOR.rnd + '-' + fullAttr;
803 function protectElements( html, regex ) {
804 return html.replace( regex, function( match, tag, content ) {
805 // Encode < and > in textarea because this won't be done by a browser
, since
806 // textarea will be protected during passing data through fix bin.
807 if ( match
.indexOf( '<textarea' ) === 0 )
808 match
= tag
+ unprotectRealComments( content
).replace( /</g
, '<' ).replace( />/g
, '>' ) + '</textarea>';
810 return '<cke:encoded>' + encodeURIComponent( match
) + '</cke:encoded>';
814 function unprotectElements( html
) {
815 return html
.replace( encodedElementsRegex
, function( match
, encoded
) {
816 return decodeURIComponent( encoded
);
820 function protectElementsNames( html
) {
821 return html
.replace( protectElementNamesRegex
, '$1cke:$2' );
824 function unprotectElementNames( html
) {
825 return html
.replace( unprotectElementNamesRegex
, '$1$2' );
828 function protectSelfClosingElements( html
) {
829 return html
.replace( protectSelfClosingRegex
, '<cke:$1$2></cke:$1>' );
832 function protectPreFormatted( html
) {
833 return html
.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
836 function protectRealComments( html
) {
837 return html
.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match
) {
838 return '<!--' + protectedSourceMarker
+
840 encodeURIComponent( match
).replace( /--/g, '%2D%2D' ) +
845 // Replace all "on\w{3,}" strings which are not:
846 // * opening tags - e.g. `<onfoo`,
847 // * closing tags - e.g. </onfoo> (tested in "false positive 1"),
848 // * part of other attribute - e.g. `data-onfoo` or `fonfoo`.
849 function protectInsecureAttributes( html
) {
850 return html
.replace( /([^a-z0-9<\-])(on\w{3,})(?!>)/gi, '$1data-cke-' + CKEDITOR
.rnd
+ '-$2' );
853 function unprotectRealComments( html
) {
854 return html
.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match
, data
) {
855 return decodeURIComponent( data
);
859 function unprotectSource( html
, editor
) {
860 var store
= editor
._
.dataStore
;
862 return html
.replace( /<!--\{cke_protected\}([\s\S]+?)-->/g, function( match
, data
) {
863 return decodeURIComponent( data
);
864 } ).replace( /\{cke_protected_(\d+)\}/g, function( match
, id
) {
865 return store
&& store
[ id
] || '';
869 function protectSource( data
, editor
) {
870 var protectedHtml
= [],
871 protectRegexes
= editor
.config
.protectedSource
,
872 store
= editor
._
.dataStore
|| ( editor
._
.dataStore
= { id: 1 } ),
873 tempRegex
= /<\!--\{cke_temp(comment)?\}(\d*?)-->/g;
876 // Script tags will also be forced to be protected, otherwise
877 // IE will execute them.
878 ( /<script[\s\S]*?(<\/script>|$)/gi ),
880 // <noscript> tags (get lost in IE and messed up in FF).
881 /<noscript[\s\S]*?<\/noscript>/gi,
883 // Avoid meta tags being stripped (#8117).
884 /<meta[\s\S]*?\/?>/gi
885 ].concat( protectRegexes
);
887 // First of any other protection, we must protect all comments
888 // to avoid loosing them (of course, IE related).
889 // Note that we use a different tag for comments, as we need to
890 // transform them when applying filters.
891 data
= data
.replace( ( /<!--[\s\S]*?-->/g ), function( match
) {
892 return '<!--{cke_tempcomment}' + ( protectedHtml
.push( match
) - 1 ) + '-->';
895 for ( var i
= 0; i
< regexes
.length
; i
++ ) {
896 data
= data
.replace( regexes
[ i
], function( match
) {
897 match
= match
.replace( tempRegex
, // There could be protected source inside another one. (#3869).
898 function( $, isComment
, id
) {
899 return protectedHtml
[ id
];
902 // Avoid protecting over protected, e.g. /\{.*?\}/
903 return ( /cke_temp(comment)?/ ).test( match
) ? match : '<!--{cke_temp}' + ( protectedHtml
.push( match
) - 1 ) + '-->';
906 data
= data
.replace( tempRegex
, function( $, isComment
, id
) {
907 return '<!--' + protectedSourceMarker
+
908 ( isComment
? '{C}' : '' ) +
909 encodeURIComponent( protectedHtml
[ id
] ).replace( /--/g, '%2D%2D' ) +
913 // Different protection pattern is used for those that
914 // live in attributes to avoid from being HTML encoded.
915 // Why so serious? See #9205, #8216, #7805, #11754, #11846.
916 data
= data
.replace( /<\w+(?:\s+(?:(?:[^\s=>]+\s*=\s*(?:[^'"\s>]+|'[^']*'|"[^"]*"))|[^\s=\/>]+))+\s*\/?>/g, function( match
) {
917 return match
.replace( /<!--\{cke_protected\}([^>]*)-->/g, function( match
, data
) {
918 store
[ store
.id
] = decodeURIComponent( data
);
919 return '{cke_protected_' + ( store
.id
++ ) + '}';
923 // This RegExp searches for innerText in all the title/iframe/textarea elements.
924 // This is because browser doesn't allow HTML in these elements, that's why we can't
925 // nest comments in there. (#11223)
926 data
= data
.replace( /<(title|iframe|textarea)([^>]*)>([\s\S]*?)<\/\1>/g, function( match
, tagName
, tagAttributes
, innerText
) {
927 return '<' + tagName
+ tagAttributes
+ '>' + unprotectSource( unprotectRealComments( innerText
), editor
) + '</' + tagName
+ '>';
933 // Creates a block if the root element is empty.
934 function fixEmptyRoot( root
, fixBodyTag
) {
935 if ( !root
.children
.length
&& CKEDITOR
.dtd
[ root
.name
][ fixBodyTag
] ) {
936 var fixBodyElement
= new CKEDITOR
.htmlParser
.element( fixBodyTag
);
937 root
.add( fixBodyElement
);
943 * Whether a filler text (non-breaking space entity — ` `) will be
944 * inserted into empty block elements in HTML output.
945 * This is used to render block elements properly with `line-height`.
946 * When a function is specified instead, it will be passed a {@link CKEDITOR.htmlParser.element}
947 * to decide whether adding the filler text by expecting a Boolean return value.
949 * config.fillEmptyBlocks = false; // Prevent filler nodes in all empty blocks.
951 * // Prevent filler node only in float cleaners.
952 * config.fillEmptyBlocks = function( element ) {
953 * if ( element.attributes[ 'class' ].indexOf( 'clear-both' ) != -1 )
958 * @cfg {Boolean/Function} [fillEmptyBlocks=true]
959 * @member CKEDITOR.config
963 * This event is fired by the {@link CKEDITOR.htmlDataProcessor} when input HTML
964 * is to be purified by the {@link CKEDITOR.htmlDataProcessor#toHtml} method.
966 * By adding listeners with different priorities it is possible
967 * to process input HTML on different stages:
969 * * 1-4: Data is available in the original string format.
970 * * 5: Data is initially filtered with regexp patterns and parsed to
971 * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}.
972 * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#dataFilter}
973 * is not applied yet.
974 * * 6: Data is filtered with the {CKEDITOR.filter content filter}.
975 * * 10: Data is processed with {@link CKEDITOR.htmlDataProcessor#dataFilter}.
976 * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#dataFilter}
977 * has already been applied.
978 * * 15: Data is written back to an HTML string.
979 * * 15-*: Data is available in an HTML string.
981 * For example to be able to process parsed, but not yet filtered data add listener this way:
983 * editor.on( 'toHtml', function( evt) {
984 * evt.data.dataValue; // -> CKEDITOR.htmlParser.fragment instance
985 * }, null, null, 7 );
989 * @member CKEDITOR.editor
990 * @param {CKEDITOR.editor} editor This editor instance.
992 * @param {String/CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} data.dataValue Input data to be purified.
993 * @param {String} data.context See {@link CKEDITOR.htmlDataProcessor#toHtml} The `context` argument.
994 * @param {Boolean} data.fixForBody See {@link CKEDITOR.htmlDataProcessor#toHtml} The `fixForBody` argument.
995 * @param {Boolean} data.dontFilter See {@link CKEDITOR.htmlDataProcessor#toHtml} The `dontFilter` argument.
996 * @param {Boolean} data.filter See {@link CKEDITOR.htmlDataProcessor#toHtml} The `filter` argument.
997 * @param {Boolean} data.enterMode See {@link CKEDITOR.htmlDataProcessor#toHtml} The `enterMode` argument.
998 * @param {Boolean} [data.protectedWhitespaces] See {@link CKEDITOR.htmlDataProcessor#toHtml} The `protectedWhitespaces` argument.
1002 * This event is fired when {@link CKEDITOR.htmlDataProcessor} is converting
1003 * internal HTML to output data HTML.
1005 * By adding listeners with different priorities it is possible
1006 * to process input HTML on different stages:
1008 * * 1-4: Data is available in the original string format.
1009 * * 5: Data is initially filtered with regexp patterns and parsed to
1010 * {@link CKEDITOR.htmlParser.fragment} {@link CKEDITOR.htmlParser.element}.
1011 * * 5-9: Data is available in the parsed format, but {@link CKEDITOR.htmlDataProcessor#htmlFilter}
1012 * is not applied yet.
1013 * * 10: Data is filtered with {@link CKEDITOR.htmlDataProcessor#htmlFilter}.
1014 * * 11: Data is filtered with the {CKEDITOR.filter content filter} (on output the content filter makes
1015 * only transformations, without filtering).
1016 * * 10-14: Data is available in the parsed format and {@link CKEDITOR.htmlDataProcessor#htmlFilter}
1017 * has already been applied.
1018 * * 15: Data is written back to an HTML string.
1019 * * 15-*: Data is available in an HTML string.
1021 * For example to be able to process parsed and already processed data add listener this way:
1023 * editor.on( 'toDataFormat', function( evt) {
1024 * evt.data.dataValue; // -> CKEDITOR.htmlParser.fragment instance
1025 * }, null, null, 12 );
1028 * @event toDataFormat
1029 * @member CKEDITOR.editor
1030 * @param {CKEDITOR.editor} editor This editor instance.
1032 * @param {String/CKEDITOR.htmlParser.fragment/CKEDITOR.htmlParser.element} data.dataValue Output data to be prepared.
1033 * @param {String} data.context See {@link CKEDITOR.htmlDataProcessor#toDataFormat} The `context` argument.
1034 * @param {Boolean} data.filter See {@link CKEDITOR.htmlDataProcessor#toDataFormat} The `filter` argument.
1035 * @param {Boolean} data.enterMode See {@link CKEDITOR.htmlDataProcessor#toDataFormat} The `enterMode` argument.