]> git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/core/htmldataprocessor.js
Initial commit
[perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git] / sources / core / htmldataprocessor.js
1 /**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 ( function() {
7 /**
8 * Represents an HTML data processor, which is responsible for translating and
9 * transforming the editor data on input and output.
10 *
11 * @class
12 * @extends CKEDITOR.dataProcessor
13 * @constructor Creates an htmlDataProcessor class instance.
14 * @param {CKEDITOR.editor} editor
15 */
16 CKEDITOR.htmlDataProcessor = function( editor ) {
17 var dataFilter, htmlFilter,
18 that = this;
19
20 this.editor = editor;
21
22 /**
23 * Data filter used when processing input by {@link #toHtml}.
24 *
25 * @property {CKEDITOR.htmlParser.filter}
26 */
27 this.dataFilter = dataFilter = new CKEDITOR.htmlParser.filter();
28
29 /**
30 * HTML filter used when processing output by {@link #toDataFormat}.
31 *
32 * @property {CKEDITOR.htmlParser.filter}
33 */
34 this.htmlFilter = htmlFilter = new CKEDITOR.htmlParser.filter();
35
36 /**
37 * The HTML writer used by this data processor to format the output.
38 *
39 * @property {CKEDITOR.htmlParser.basicWriter}
40 */
41 this.writer = new CKEDITOR.htmlParser.basicWriter();
42
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 } );
49
50 editor.on( 'toHtml', function( evt ) {
51 var evtData = evt.data,
52 data = evtData.dataValue,
53 fixBodyTag;
54
55 // The source data is already HTML, but we need to clean
56 // it up and apply the filter.
57 data = protectSource( data, editor );
58
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 );
63
64 // Before anything, we must protect the URL attributes as the
65 // browser may changing them when setting the innerHTML later in
66 // the code.
67 data = protectAttributes( data );
68
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 );
72
73 // Certain elements has problem to go through DOM operation, protect
74 // them by prefixing 'cke' namespace. (#3591)
75 data = protectElementsNames( data );
76
77 // All none-IE browsers ignore self-closed custom elements,
78 // protecting them into open-close. (#3591)
79 data = protectSelfClosingElements( data );
80
81 // Compensate one leading line break after <pre> open as browsers
82 // eat it up. (#5789)
83 data = protectPreFormatted( data );
84
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 );
88
89 var fixBin = evtData.context || editor.editable().getName(),
90 isPre;
91
92 // Old IEs loose formats when load html into <pre>.
93 if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && fixBin == 'pre' ) {
94 fixBin = 'div';
95 data = '<pre>' + data + '</pre>';
96 isPre = 1;
97 }
98
99 // Call the browser to help us fixing a possibly invalid HTML
100 // structure.
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 );
105
106 // Restore shortly protected attribute names.
107 data = data.replace( new RegExp( 'data-cke-' + CKEDITOR.rnd + '-', 'ig' ), '' );
108
109 isPre && ( data = data.replace( /^<pre>|<\/pre>$/gi, '' ) );
110
111 // Unprotect "some" of the protected elements at this point.
112 data = unprotectElementNames( data );
113
114 data = unprotectElements( data );
115
116 // Restore the comments that have been protected, in this way they
117 // can be properly filtered.
118 data = unprotectRealComments( data );
119
120 if ( evtData.fixForBody === false ) {
121 fixBodyTag = false;
122 } else {
123 fixBodyTag = getFixBodyTag( evtData.enterMode, editor.config.autoParagraph );
124 }
125
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 );
129
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).
132 if ( fixBodyTag ) {
133 fixEmptyRoot( data, fixBodyTag );
134 }
135
136 evtData.dataValue = data;
137 }, null, null, 5 );
138
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' );
144 }, null, null, 6 );
145
146 editor.on( 'toHtml', function( evt ) {
147 evt.data.dataValue.filterChildren( that.dataFilter, true );
148 }, null, null, 10 );
149
150 editor.on( 'toHtml', function( evt ) {
151 var evtData = evt.data,
152 data = evtData.dataValue,
153 writer = new CKEDITOR.htmlParser.basicWriter();
154
155 data.writeChildrenHtml( writer );
156 data = writer.getHtml( true );
157
158 // Protect the real comments again.
159 evtData.dataValue = protectRealComments( data );
160 }, null, null, 15 );
161
162
163 editor.on( 'toDataFormat', function( evt ) {
164 var data = evt.data.dataValue;
165
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, '' );
172
173 evt.data.dataValue = CKEDITOR.htmlParser.fragment.fromHtml(
174 data, evt.data.context, getFixBodyTag( evt.data.enterMode, editor.config.autoParagraph ) );
175 }, null, null, 5 );
176
177 editor.on( 'toDataFormat', function( evt ) {
178 evt.data.dataValue.filterChildren( that.htmlFilter, true );
179 }, null, null, 10 );
180
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 );
185 }, null, null, 11 );
186
187 editor.on( 'toDataFormat', function( evt ) {
188 var data = evt.data.dataValue,
189 writer = that.writer;
190
191 writer.reset();
192 data.writeChildrenHtml( writer );
193 data = writer.getHtml( true );
194
195 // Restore those non-HTML protected source. (#4475,#4880)
196 data = unprotectRealComments( data );
197 data = unprotectSource( data, editor );
198
199 evt.data.dataValue = data;
200 }, null, null, 15 );
201 };
202
203 CKEDITOR.htmlDataProcessor.prototype = {
204 /**
205 * Processes the (potentially malformed) input HTML to a purified form which
206 * is suitable for using in the WYSIWYG editable.
207 *
208 * This method fires the {@link CKEDITOR.editor#toHtml} event which makes it possible
209 * to hook into the process at various stages.
210 *
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.
214 *
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.
229 * @returns {String}
230 */
231 toHtml: function( data, options, fixForBody, dontFilter ) {
232 var editor = this.editor,
233 context, filter, enterMode, protectedWhitespaces;
234
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;
243 }
244 // Backward compatibility. Since CKEDITOR 4.3 every option was a separate argument.
245 else {
246 context = options;
247 }
248
249 // Fall back to the editable as context if not specified.
250 if ( !context && context !== null )
251 context = editor.editable().getName();
252
253 return editor.fire( 'toHtml', {
254 dataValue: data,
255 context: context,
256 fixForBody: fixForBody,
257 dontFilter: dontFilter,
258 filter: filter || editor.filter,
259 enterMode: enterMode || editor.enterMode,
260 protectedWhitespaces: protectedWhitespaces
261 } ).dataValue;
262 },
263
264 /**
265 * See {@link CKEDITOR.dataProcessor#toDataFormat}.
266 *
267 * This method fires the {@link CKEDITOR.editor#toDataFormat} event which makes it possible
268 * to hook into the process at various stages.
269 *
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}.
277 * @returns {String}
278 */
279 toDataFormat: function( html, options ) {
280 var context, filter, enterMode;
281
282 // Do not shorten this to `options && options.xxx`, because
283 // falsy `options` will be passed instead of undefined.
284 if ( options ) {
285 context = options.context;
286 filter = options.filter;
287 enterMode = options.enterMode;
288 }
289
290 // Fall back to the editable as context if not specified.
291 if ( !context && context !== null )
292 context = this.editor.editable().getName();
293
294 return this.editor.fire( 'toDataFormat', {
295 dataValue: html,
296 filter: filter || this.editor.filter,
297 context: context,
298 enterMode: enterMode || this.editor.enterMode
299 } ).dataValue;
300 }
301 };
302
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.
309 //
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.
313 //
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 } );
323 }
324
325 // This text block filter, remove any bogus and create the filler on demand.
326 function blockFilter( isOutput, fillEmptyBlock ) {
327
328 return function( block ) {
329 // DO NOT apply the filler if it's a fragment node.
330 if ( block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT )
331 return;
332
333 cleanBogus( block );
334
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;
338
339 if ( shouldFillBlock && isEmptyBlockNeedFiller( block ) ) {
340 block.add( createFiller( isOutput ) );
341 }
342 };
343 }
344
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 )
350 return;
351
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' ];
356 return;
357 }
358
359 // Judge the tail line-break BR, and to insert bogus after it.
360 var next = getNext( br ), previous = getPrevious( br );
361
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 );
366 };
367 }
368
369 // Determinate whether this node is potentially a bogus node.
370 function maybeBogus( node, atBlockEnd ) {
371
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' ] ) {
376 return true;
377 }
378
379 var match;
380
381 // NBSP, possibly.
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.
384 if ( match.index ) {
385 ( new CKEDITOR.htmlParser.text( node.value.substring( 0, match.index ) ) ).insertBefore( node );
386 node.value = match[ 0 ];
387 }
388
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 ) )
391 return true;
392
393 // From the output.
394 if ( !isOutput ) {
395 var previous = node.previous;
396
397 // Following a line-break at the end of block.
398 if ( previous && previous.name == 'br' )
399 return true;
400
401 // Or a single NBSP between two blocks.
402 if ( !previous || isBlockBoundary( previous ) )
403 return true;
404 }
405 }
406
407 return false;
408 }
409
410 // Removes all bogus inside of this block, and to convert fillers into the proper form.
411 function cleanBogus( block ) {
412 var bogus = [];
413 var last = getLast( block ), node, previous;
414
415 if ( last ) {
416 // Check for bogus at the end of this block.
417 // e.g. <p>foo<br /></p>
418 maybeBogus( last, 1 ) && bogus.push( last );
419
420 while ( 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 ) )
426 bogus.push( node );
427 // Convert the filler into appropriate form.
428 else {
429 createFiller( isOutput ).insertAfter( node );
430 node.remove();
431 }
432 }
433
434 last = last.previous;
435 }
436 }
437
438 // Now remove all bogus collected from above.
439 for ( var i = 0 ; i < bogus.length ; i++ )
440 bogus[ i ].remove();
441 }
442
443 // Judge whether it's an empty block that requires a filler node.
444 function isEmptyBlockNeedFiller( block ) {
445
446 // DO NOT fill empty editable in IE<11.
447 if ( !isOutput && !CKEDITOR.env.needsBrFiller && block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT )
448 return false;
449
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 ) ) {
456 return false;
457 }
458
459 var last = getLast( block );
460 return !last || block.name == 'form' && last.name == 'input' ;
461 }
462
463 var rules = { elements: {} },
464 isOutput = type == 'html',
465 textBlockTags = CKEDITOR.tools.extend( {}, blockLikeTags );
466
467 // Build the list of text blocks.
468 for ( var i in textBlockTags ) {
469 if ( !( '#' in dtd[ i ] ) )
470 delete textBlockTags[ i ];
471 }
472
473 for ( i in textBlockTags )
474 rules.elements[ i ] = blockFilter( isOutput, editor.config.fillEmptyBlocks );
475
476 // Editable element has to be checked separately.
477 rules.root = blockFilter( isOutput, false );
478 rules.elements.br = brFilter( isOutput );
479 return rules;
480 }
481
482 function getFixBodyTag( enterMode, autoParagraph ) {
483 return ( enterMode != CKEDITOR.ENTER_BR && autoParagraph !== false ) ? enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false;
484 }
485
486 // Regex to scan for &nbsp; at the end of blocks, which are actually placeholders.
487 // Safari transforms the &nbsp; to \xa0. (#4172)
488 var tailNbspRegex = /(?:&nbsp;|\xa0)$/;
489
490 var protectedSourceMarker = '{cke_protected}';
491
492 function getLast( node ) {
493 var last = node.children[ node.children.length - 1 ];
494 while ( last && isEmpty( last ) )
495 last = last.previous;
496 return last;
497 }
498
499 function getNext( node ) {
500 var next = node.next;
501 while ( next && isEmpty( next ) )
502 next = next.next;
503 return next;
504 }
505
506 function getPrevious( node ) {
507 var previous = node.previous;
508 while ( previous && isEmpty( previous ) )
509 previous = previous.previous;
510 return previous;
511 }
512
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' ];
519 }
520
521 // Judge whether the node is a block-like element.
522 function isBlockBoundary( node ) {
523 return node &&
524 ( node.type == CKEDITOR.NODE_ELEMENT && node.name in blockLikeTags ||
525 node.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT );
526 }
527
528 function append( parent, node ) {
529 var last = parent.children[ parent.children.length - 1 ];
530 parent.children.push( node );
531 node.parent = parent;
532 if ( last ) {
533 last.next = node;
534 node.previous = last;
535 }
536 }
537
538 function getNodeIndex( node ) {
539 return node.parent ? node.getIndex() : -1;
540 }
541
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 );
547
548 //
549 // DATA filter rules ------------------------------------------------------
550 //
551
552 var defaultDataFilterRulesEditableOnly = {
553 elements: {
554 input: protectReadOnly,
555 textarea: protectReadOnly
556 }
557 };
558
559 // These rules will also be applied to non-editable content.
560 var defaultDataFilterRulesForAll = {
561 attributeNames: [
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' ],
565
566 // Don't let some old expando enter editor. Concerns only IE8,
567 // but for consistency remove on all browsers.
568 [ ( /^data-cke-expando$/ ), '' ]
569 ]
570 };
571
572 // Disable form elements editing mode provided by some browsers. (#5746)
573 function protectReadOnly( element ) {
574 var attrs = element.attributes;
575
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;
580
581 attrs.contenteditable = 'false';
582 }
583
584 //
585 // HTML filter rules ------------------------------------------------------
586 //
587
588 var defaultHtmlFilterRulesEditableOnly = {
589 elements: {
590 embed: function( element ) {
591 var parent = element.parent;
592
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;
598 if ( parentWidth )
599 element.attributes.width = parentWidth;
600 if ( parentHeight )
601 element.attributes.height = parentHeight;
602 }
603 },
604
605 // Remove empty link but not empty anchor. (#3829, #13516)
606 a: function( element ) {
607 var attrs = element.attributes;
608
609 if ( !( element.children.length || attrs.name || attrs.id || element.attributes[ 'data-cke-saved-name' ] ) )
610 return false;
611 }
612 }
613 };
614
615 // These rules will also be applied to non-editable content.
616 var defaultHtmlFilterRulesForAll = {
617 elementNames: [
618 // Remove the "cke:" namespace prefix.
619 [ ( /^cke:/ ), '' ],
620
621 // Ignore <?xml:namespace> tags.
622 [ ( /^\?xml:namespace$/ ), '' ]
623 ],
624
625 attributeNames: [
626 // Attributes saved for changes and protected attributes.
627 [ ( /^data-cke-(saved|pa)-/ ), '' ],
628
629 // All "data-cke-" attributes are to be ignored.
630 [ ( /^data-cke-.*/ ), '' ],
631
632 [ 'hidefocus', '' ]
633 ],
634
635 elements: {
636 $: function( element ) {
637 var attribs = element.attributes;
638
639 if ( attribs ) {
640 // Elements marked as temporary are to be ignored.
641 if ( attribs[ 'data-cke-temp' ] )
642 return false;
643
644 // Remove duplicated attributes - #3789.
645 var attributeNames = [ 'name', 'href', 'src' ],
646 savedAttributeName;
647 for ( var i = 0; i < attributeNames.length; i++ ) {
648 savedAttributeName = 'data-cke-saved-' + attributeNames[ i ];
649 savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] );
650 }
651 }
652
653 return element;
654 },
655
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 );
660
661 children.sort( function( node1, node2 ) {
662 var index1, index2;
663
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 );
668 }
669
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 );
674 }
675
676 return index1 > index2 ? 1 : -1;
677 } );
678 },
679
680 // Restore param elements into self-closing.
681 param: function( param ) {
682 param.children = [];
683 param.isEmpty = true;
684 return param;
685 },
686
687 // Remove dummy span in webkit.
688 span: function( element ) {
689 if ( element.attributes[ 'class' ] == 'Apple-style-span' )
690 delete element.name;
691 },
692
693 html: function( element ) {
694 delete element.attributes.contenteditable;
695 delete element.attributes[ 'class' ];
696 },
697
698 body: function( element ) {
699 delete element.attributes.spellcheck;
700 delete element.attributes.contenteditable;
701 },
702
703 style: function( element ) {
704 var child = element.children[ 0 ];
705 if ( child && child.value )
706 child.value = CKEDITOR.tools.trim( child.value );
707
708 if ( !element.attributes.type )
709 element.attributes.type = 'text/css';
710 },
711
712 title: function( element ) {
713 var titleText = element.children[ 0 ];
714
715 // Append text-node to title tag if not present (i.e. non-IEs) (#9882).
716 !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() );
717
718 // Transfer data-saved title to title tag.
719 titleText.value = element.attributes[ 'data-cke-title' ] || '';
720 },
721
722 input: unprotectReadyOnly,
723 textarea: unprotectReadyOnly
724 },
725
726 attributes: {
727 'class': function( value ) {
728 // Remove all class names starting with "cke_".
729 return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
730 }
731 }
732 };
733
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();
740 } );
741 };
742 }
743
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' ] ) {
748 case 'true':
749 attrs.contenteditable = 'true';
750 break;
751 case '1':
752 delete attrs.contenteditable;
753 break;
754 }
755 }
756
757 //
758 // Preprocessor filters ---------------------------------------------------
759 //
760
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:
765 //
766 // <img data-x="&lt;a href=&quot;X&quot;" />
767 //
768 // then the "non-greedy match" returns:
769 //
770 // 'href' => '&quot;X&quot;' // It's wrong! Href is not an attribute of <img>.
771 //
772 // while greedy match returns:
773 //
774 // 'data-x' => '&lt;a href=&quot;X&quot;'
775 //
776 // which, can be easily filtered out (#11508).
777 protectAttributeRegex = /([\w-:]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi,
778 protectAttributeNameRegex = /^(href|src|name)$/i;
779
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;
784
785 var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi,
786 unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi;
787
788 var protectSelfClosingRegex = /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi;
789
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;
797
798 return fullAttr;
799 } ) + '>';
800 } );
801 }
802
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, '&lt;' ).replace( />/g, '&gt;' ) + '</textarea>';
809
810 return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
811 } );
812 }
813
814 function unprotectElements( html ) {
815 return html.replace( encodedElementsRegex, function( match, encoded ) {
816 return decodeURIComponent( encoded );
817 } );
818 }
819
820 function protectElementsNames( html ) {
821 return html.replace( protectElementNamesRegex, '$1cke:$2' );
822 }
823
824 function unprotectElementNames( html ) {
825 return html.replace( unprotectElementNamesRegex, '$1$2' );
826 }
827
828 function protectSelfClosingElements( html ) {
829 return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' );
830 }
831
832 function protectPreFormatted( html ) {
833 return html.replace( /(<pre\b[^>]*>)(\r\n|\n)/g, '$1$2$2' );
834 }
835
836 function protectRealComments( html ) {
837 return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match ) {
838 return '<!--' + protectedSourceMarker +
839 '{C}' +
840 encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
841 '-->';
842 } );
843 }
844
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' );
851 }
852
853 function unprotectRealComments( html ) {
854 return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data ) {
855 return decodeURIComponent( data );
856 } );
857 }
858
859 function unprotectSource( html, editor ) {
860 var store = editor._.dataStore;
861
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 ] || '';
866 } );
867 }
868
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;
874
875 var regexes = [
876 // Script tags will also be forced to be protected, otherwise
877 // IE will execute them.
878 ( /<script[\s\S]*?(<\/script>|$)/gi ),
879
880 // <noscript> tags (get lost in IE and messed up in FF).
881 /<noscript[\s\S]*?<\/noscript>/gi,
882
883 // Avoid meta tags being stripped (#8117).
884 /<meta[\s\S]*?\/?>/gi
885 ].concat( protectRegexes );
886
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 ) + '-->';
893 } );
894
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 ];
900 } );
901
902 // Avoid protecting over protected, e.g. /\{.*?\}/
903 return ( /cke_temp(comment)?/ ).test( match ) ? match : '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->';
904 } );
905 }
906 data = data.replace( tempRegex, function( $, isComment, id ) {
907 return '<!--' + protectedSourceMarker +
908 ( isComment ? '{C}' : '' ) +
909 encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) +
910 '-->';
911 } );
912
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++ ) + '}';
920 } );
921 } );
922
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 + '>';
928 } );
929
930 return data;
931 }
932
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 );
938 }
939 }
940 } )();
941
942 /**
943 * Whether a filler text (non-breaking space entity &mdash; `&nbsp;`) 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.
948 *
949 * config.fillEmptyBlocks = false; // Prevent filler nodes in all empty blocks.
950 *
951 * // Prevent filler node only in float cleaners.
952 * config.fillEmptyBlocks = function( element ) {
953 * if ( element.attributes[ 'class' ].indexOf( 'clear-both' ) != -1 )
954 * return false;
955 * };
956 *
957 * @since 3.5
958 * @cfg {Boolean/Function} [fillEmptyBlocks=true]
959 * @member CKEDITOR.config
960 */
961
962 /**
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.
965 *
966 * By adding listeners with different priorities it is possible
967 * to process input HTML on different stages:
968 *
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.
980 *
981 * For example to be able to process parsed, but not yet filtered data add listener this way:
982 *
983 * editor.on( 'toHtml', function( evt) {
984 * evt.data.dataValue; // -> CKEDITOR.htmlParser.fragment instance
985 * }, null, null, 7 );
986 *
987 * @since 4.1
988 * @event toHtml
989 * @member CKEDITOR.editor
990 * @param {CKEDITOR.editor} editor This editor instance.
991 * @param data
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.
999 */
1000
1001 /**
1002 * This event is fired when {@link CKEDITOR.htmlDataProcessor} is converting
1003 * internal HTML to output data HTML.
1004 *
1005 * By adding listeners with different priorities it is possible
1006 * to process input HTML on different stages:
1007 *
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.
1020 *
1021 * For example to be able to process parsed and already processed data add listener this way:
1022 *
1023 * editor.on( 'toDataFormat', function( evt) {
1024 * evt.data.dataValue; // -> CKEDITOR.htmlParser.fragment instance
1025 * }, null, null, 12 );
1026 *
1027 * @since 4.1
1028 * @event toDataFormat
1029 * @member CKEDITOR.editor
1030 * @param {CKEDITOR.editor} editor This editor instance.
1031 * @param data
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.
1036 */