]>
Commit | Line | Data |
---|---|---|
c63493c8 IB |
1 | /** |
2 | * @license Copyright (c) 2003-2017, 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 | */ |