diff options
Diffstat (limited to 'sources/core/htmldataprocessor.js')
-rw-r--r-- | sources/core/htmldataprocessor.js | 1036 |
1 files changed, 1036 insertions, 0 deletions
diff --git a/sources/core/htmldataprocessor.js b/sources/core/htmldataprocessor.js new file mode 100644 index 00000000..9bc9ef0b --- /dev/null +++ b/sources/core/htmldataprocessor.js | |||
@@ -0,0 +1,1036 @@ | |||
1 | /** | ||
2 | * @license Copyright (c) 2003-2015, 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 at the end of blocks, which are actually placeholders. | ||
487 | // Safari transforms the to \xa0. (#4172) | ||
488 | var tailNbspRegex = /(?: |\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="<a href="X"" /> | ||
767 | // | ||
768 | // then the "non-greedy match" returns: | ||
769 | // | ||
770 | // 'href' => '"X"' // It's wrong! Href is not an attribute of <img>. | ||
771 | // | ||
772 | // while greedy match returns: | ||
773 | // | ||
774 | // 'data-x' => '<a href="X"' | ||
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, '<' ).replace( />/g, '>' ) + '</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 — ` `) 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 | */ | ||