]> git.immae.eu Git - perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git/blob - sources/plugins/pastefromword/filter/default.js
Upgrade to 4.5.7 and add some plugin
[perso/Immae/Projets/packagist/connexionswing-ckeditor-component.git] / sources / plugins / pastefromword / filter / default.js
1 /**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 ( function() {
7 var fragmentPrototype = CKEDITOR.htmlParser.fragment.prototype,
8 elementPrototype = CKEDITOR.htmlParser.element.prototype;
9
10 fragmentPrototype.onlyChild = elementPrototype.onlyChild = function() {
11 var children = this.children,
12 count = children.length,
13 firstChild = ( count == 1 ) && children[ 0 ];
14 return firstChild || null;
15 };
16
17 elementPrototype.removeAnyChildWithName = function( tagName ) {
18 var children = this.children,
19 childs = [],
20 child;
21
22 for ( var i = 0; i < children.length; i++ ) {
23 child = children[ i ];
24 if ( !child.name )
25 continue;
26
27 if ( child.name == tagName ) {
28 childs.push( child );
29 children.splice( i--, 1 );
30 }
31 childs = childs.concat( child.removeAnyChildWithName( tagName ) );
32 }
33 return childs;
34 };
35
36 elementPrototype.getAncestor = function( tagNameRegex ) {
37 var parent = this.parent;
38 while ( parent && !( parent.name && parent.name.match( tagNameRegex ) ) )
39 parent = parent.parent;
40 return parent;
41 };
42
43 fragmentPrototype.firstChild = elementPrototype.firstChild = function( evaluator ) {
44 var child;
45
46 for ( var i = 0; i < this.children.length; i++ ) {
47 child = this.children[ i ];
48 if ( evaluator( child ) )
49 return child;
50 else if ( child.name ) {
51 child = child.firstChild( evaluator );
52 if ( child )
53 return child;
54 }
55 }
56
57 return null;
58 };
59
60 // Adding a (set) of styles to the element's 'style' attributes.
61 elementPrototype.addStyle = function( name, value, isPrepend ) {
62 var styleText,
63 addingStyleText = '';
64 // name/value pair.
65 if ( typeof value == 'string' )
66 addingStyleText += name + ':' + value + ';';
67 else {
68 // style literal.
69 if ( typeof name == 'object' ) {
70 for ( var style in name ) {
71 if ( name.hasOwnProperty( style ) )
72 addingStyleText += style + ':' + name[ style ] + ';';
73 }
74 }
75 // raw style text form.
76 else {
77 addingStyleText += name;
78 }
79
80 isPrepend = value;
81 }
82
83 if ( !this.attributes )
84 this.attributes = {};
85
86 styleText = this.attributes.style || '';
87
88 styleText = ( isPrepend ? [ addingStyleText, styleText ] : [ styleText, addingStyleText ] ).join( ';' );
89
90 this.attributes.style = styleText.replace( /^;+|;(?=;)/g, '' );
91 };
92
93 // Retrieve a style property value of the element.
94 elementPrototype.getStyle = function( name ) {
95 var styles = this.attributes.style;
96 if ( styles ) {
97 styles = CKEDITOR.tools.parseCssText( styles, 1 );
98 return styles[ name ];
99 }
100 };
101
102 /**
103 * Return the DTD-valid parent tag names of the specified one.
104 *
105 * @member CKEDITOR.dtd
106 * @param {String} tagName
107 * @returns {Object}
108 */
109 CKEDITOR.dtd.parentOf = function( tagName ) {
110 var result = {};
111 for ( var tag in this ) {
112 if ( tag.indexOf( '$' ) == -1 && this[ tag ][ tagName ] )
113 result[ tag ] = 1;
114 }
115 return result;
116 };
117
118 // 1. move consistent list item styles up to list root.
119 // 2. clear out unnecessary list item numbering.
120 function postProcessList( list ) {
121 var children = list.children,
122 child, attrs,
123 count = list.children.length,
124 match, mergeStyle,
125 styleTypeRegexp = /list-style-type:(.*?)(?:;|$)/,
126 stylesFilter = CKEDITOR.plugins.pastefromword.filters.stylesFilter;
127
128 attrs = list.attributes;
129 if ( styleTypeRegexp.exec( attrs.style ) )
130 return;
131
132 for ( var i = 0; i < count; i++ ) {
133 child = children[ i ];
134
135 if ( child.attributes.value && Number( child.attributes.value ) == i + 1 )
136 delete child.attributes.value;
137
138 match = styleTypeRegexp.exec( child.attributes.style );
139
140 if ( match ) {
141 if ( match[ 1 ] == mergeStyle || !mergeStyle )
142 mergeStyle = match[ 1 ];
143 else {
144 mergeStyle = null;
145 break;
146 }
147 }
148 }
149
150 if ( mergeStyle ) {
151 for ( i = 0; i < count; i++ ) {
152 attrs = children[ i ].attributes;
153 attrs.style && ( attrs.style = stylesFilter( [ [ 'list-style-type' ] ] )( attrs.style ) || '' );
154 }
155
156 list.addStyle( 'list-style-type', mergeStyle );
157 }
158 }
159
160 var emptyMarginRegex = /^(?:\b0[^\s]*\s*){1,4}$/; // e.g. 0px 0pt 0px
161 var romanLiternalPattern = '^m{0,4}(cm|cd|d?c{0,3})(xc|xl|l?x{0,3})(ix|iv|v?i{0,3})$',
162 lowerRomanLiteralRegex = new RegExp( romanLiternalPattern ),
163 upperRomanLiteralRegex = new RegExp( romanLiternalPattern.toUpperCase() );
164
165 var orderedPatterns = { 'decimal': /\d+/, 'lower-roman': lowerRomanLiteralRegex, 'upper-roman': upperRomanLiteralRegex, 'lower-alpha': /^[a-z]+$/, 'upper-alpha': /^[A-Z]+$/ },
166 unorderedPatterns = { 'disc': /[l\u00B7\u2002]/, 'circle': /[\u006F\u00D8]/, 'square': /[\u006E\u25C6]/ },
167 listMarkerPatterns = { 'ol': orderedPatterns, 'ul': unorderedPatterns },
168 romans = [ [ 1000, 'M' ], [ 900, 'CM' ], [ 500, 'D' ], [ 400, 'CD' ], [ 100, 'C' ], [ 90, 'XC' ], [ 50, 'L' ], [ 40, 'XL' ], [ 10, 'X' ], [ 9, 'IX' ], [ 5, 'V' ], [ 4, 'IV' ], [ 1, 'I' ] ],
169 alpahbets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
170
171 // Convert roman numbering back to decimal.
172 function fromRoman( str ) {
173 str = str.toUpperCase();
174 var l = romans.length,
175 retVal = 0;
176 for ( var i = 0; i < l; ++i ) {
177 for ( var j = romans[ i ], k = j[ 1 ].length; str.substr( 0, k ) == j[ 1 ]; str = str.substr( k ) )
178 retVal += j[ 0 ];
179 }
180 return retVal;
181 }
182
183 // Convert alphabet numbering back to decimal.
184 function fromAlphabet( str ) {
185 str = str.toUpperCase();
186 var l = alpahbets.length,
187 retVal = 1;
188 for ( var x = 1; str.length > 0; x *= l ) {
189 retVal += alpahbets.indexOf( str.charAt( str.length - 1 ) ) * x;
190 str = str.substr( 0, str.length - 1 );
191 }
192 return retVal;
193 }
194
195 var listBaseIndent = 0,
196 previousListItemMargin = null,
197 previousListId;
198
199 var plugin = ( CKEDITOR.plugins.pastefromword = {
200 utils: {
201 // Create a <cke:listbullet> which indicate an list item type.
202 createListBulletMarker: function( bullet, bulletText ) {
203 var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' );
204 marker.attributes = { 'cke:listsymbol': bullet[ 0 ] };
205 marker.add( new CKEDITOR.htmlParser.text( bulletText ) );
206 return marker;
207 },
208
209 isListBulletIndicator: function( element ) {
210 var styleText = element.attributes && element.attributes.style;
211 if ( /mso-list\s*:\s*Ignore/i.test( styleText ) )
212 return true;
213 },
214
215 isContainingOnlySpaces: function( element ) {
216 var text;
217 return ( ( text = element.onlyChild() ) && ( /^(:?\s|&nbsp;)+$/ ).test( text.value ) );
218 },
219
220 resolveList: function( element ) {
221 // <cke:listbullet> indicate a list item.
222 var attrs = element.attributes,
223 listMarker;
224
225 if ( ( listMarker = element.removeAnyChildWithName( 'cke:listbullet' ) ) && listMarker.length && ( listMarker = listMarker[ 0 ] ) ) {
226 element.name = 'cke:li';
227
228 if ( attrs.style ) {
229 attrs.style = plugin.filters.stylesFilter( [
230 // Text-indent is not representing list item level any more.
231 [ 'text-indent' ],
232 [ 'line-height' ],
233 // First attempt is to resolve indent level from on a constant margin increment.
234 [ ( /^margin(:?-left)?$/ ), null, function( margin ) {
235 // Deal with component/short-hand form.
236 var values = margin.split( ' ' );
237 margin = CKEDITOR.tools.convertToPx( values[ 3 ] || values[ 1 ] || values[ 0 ] );
238
239 // Figure out the indent unit by checking the first time of incrementation.
240 if ( !listBaseIndent && previousListItemMargin !== null && margin > previousListItemMargin )
241 listBaseIndent = margin - previousListItemMargin;
242
243 previousListItemMargin = margin;
244
245 attrs[ 'cke:indent' ] = listBaseIndent && ( Math.ceil( margin / listBaseIndent ) + 1 ) || 1;
246 } ],
247 // The best situation: "mso-list:l0 level1 lfo2" tells the belonged list root, list item indentation, etc.
248 [ ( /^mso-list$/ ), null, function( val ) {
249 val = val.split( ' ' );
250 // Ignore values like "mso-list:Ignore". (FF #11976)
251 if ( val.length < 2 ) {
252 return;
253 }
254
255 var listId = Number( val[ 0 ].match( /\d+/ ) ),
256 indent = Number( val[ 1 ].match( /\d+/ ) );
257
258 if ( indent == 1 ) {
259 listId !== previousListId && ( attrs[ 'cke:reset' ] = 1 );
260 previousListId = listId;
261 }
262 attrs[ 'cke:indent' ] = indent;
263 } ]
264 ] )( attrs.style, element ) || '';
265 }
266
267 // First level list item might be presented without a margin.
268
269
270 // In case all above doesn't apply.
271 if ( !attrs[ 'cke:indent' ] ) {
272 previousListItemMargin = 0;
273 attrs[ 'cke:indent' ] = 1;
274 }
275
276 // Inherit attributes from bullet.
277 CKEDITOR.tools.extend( attrs, listMarker.attributes );
278 return true;
279 }
280 // Current list disconnected.
281 else {
282 previousListId = previousListItemMargin = listBaseIndent = null;
283 }
284
285 return false;
286 },
287
288 // Providing a shorthand style then retrieve one or more style component values.
289 getStyleComponents: ( function() {
290 var calculator = CKEDITOR.dom.element.createFromHtml( '<div style="position:absolute;left:-9999px;top:-9999px;"></div>', CKEDITOR.document );
291 CKEDITOR.document.getBody().append( calculator );
292
293 return function( name, styleValue, fetchList ) {
294 calculator.setStyle( name, styleValue );
295 var styles = {},
296 count = fetchList.length;
297 for ( var i = 0; i < count; i++ )
298 styles[ fetchList[ i ] ] = calculator.getStyle( fetchList[ i ] );
299
300 return styles;
301 };
302 } )(),
303
304 listDtdParents: CKEDITOR.dtd.parentOf( 'ol' )
305 },
306
307 filters: {
308 // Transform a normal list into flat list items only presentation.
309 // E.g. <ul><li>level1<ol><li>level2</li></ol></li> =>
310 // <cke:li cke:listtype="ul" cke:indent="1">level1</cke:li>
311 // <cke:li cke:listtype="ol" cke:indent="2">level2</cke:li>
312 flattenList: function( element, level ) {
313 level = typeof level == 'number' ? level : 1;
314
315 var attrs = element.attributes,
316 listStyleType;
317
318 // All list items are of the same type.
319 switch ( attrs.type ) {
320 case 'a':
321 listStyleType = 'lower-alpha';
322 break;
323 case '1':
324 listStyleType = 'decimal';
325 break;
326 // TODO: Support more list style type from MS-Word.
327 }
328
329 var children = element.children,
330 child;
331
332 for ( var i = 0; i < children.length; i++ ) {
333 child = children[ i ];
334
335 if ( child.name in CKEDITOR.dtd.$listItem ) {
336 var attributes = child.attributes,
337 listItemChildren = child.children,
338 count = listItemChildren.length,
339 first = listItemChildren[ 0 ],
340 last = listItemChildren[ count - 1 ];
341
342 // Converts <li><p style="_MSO_LIST_STYLES_">{...}</p></li> -> <li style="_MSO_LIST_STYLES_">{...}</li>.
343 // The above format is what we got when pasting from Word 2010 to IE11 and possibly some others.
344 // Existence of extra <p> tag that can be later recognized as list item (see #getRules.return.elements.p)
345 // creates incorrect and problematic structures similar to <cke:li><cke:li>{...}</cke:li></cke:li>. (#11376)
346 if ( first.attributes && first.attributes.style && first.attributes.style.indexOf( 'mso-list' ) > -1 ) {
347 child.attributes.style = first.attributes.style;
348 first.replaceWithChildren();
349 }
350
351 // Move out nested list.
352 if ( last.name in CKEDITOR.dtd.$list ) {
353 element.add( last, i + 1 );
354
355 // Remove the parent list item if it's just a holder.
356 if ( !--listItemChildren.length )
357 children.splice( i--, 1 );
358 }
359
360 child.name = 'cke:li';
361
362 // Inherit numbering from list root on the first list item.
363 attrs.start && !i && ( attributes.value = attrs.start );
364
365 plugin.filters.stylesFilter( [
366 [ 'tab-stops', null, function( val ) {
367 // val = [left|center|right|decimal] <value><unit> Source: W3C, WD-tabs-970117.
368 // In some cases the first word is missing - hence the square brackets.
369 var margin = val.match( /0$|\d+\.?\d*\w+/ );
370 margin && ( previousListItemMargin = CKEDITOR.tools.convertToPx( margin[ 0 ] ) );
371 } ],
372 ( level == 1 ? [ 'mso-list', null, function( val ) {
373 val = val.split( ' ' );
374 var listId = Number( val[ 0 ].match( /\d+/ ) );
375 listId !== previousListId && ( attributes[ 'cke:reset' ] = 1 );
376 previousListId = listId;
377 } ] : null )
378 ] )( attributes.style );
379
380 attributes[ 'cke:indent' ] = level;
381 attributes[ 'cke:listtype' ] = element.name;
382 attributes[ 'cke:list-style-type' ] = listStyleType;
383 }
384 // Flatten sub list.
385 else if ( child.name in CKEDITOR.dtd.$list ) {
386 // Absorb sub list children.
387 arguments.callee.apply( this, [ child, level + 1 ] );
388 children = children.slice( 0, i ).concat( child.children ).concat( children.slice( i + 1 ) );
389 element.children = [];
390 for ( var j = 0, num = children.length; j < num; j++ )
391 element.add( children[ j ] );
392
393 children = element.children;
394 }
395 }
396
397 delete element.name;
398
399 // We're loosing tag name here, signalize this element as a list.
400 attrs[ 'cke:list' ] = 1;
401 },
402
403 // Try to collect all list items among the children and establish one
404 // or more HTML list structures for them.
405 // @param element
406 assembleList: function( element ) {
407 var children = element.children,
408 child, listItem, // The current processing cke:li element.
409 listItemAttrs, listItemIndent, // Indent level of current list item.
410 lastIndent, lastListItem, // The previous one just been added to the list.
411 list, // Current staging list and it's parent list if any.
412 openedLists = [],
413 previousListStyleType, previousListType;
414
415 // Properties of the list item are to be resolved from the list bullet.
416 var bullet, listType, listStyleType, itemNumeric;
417
418 for ( var i = 0; i < children.length; i++ ) {
419 child = children[ i ];
420
421 if ( child.name == 'cke:li' ) {
422 child.name = 'li';
423 listItem = child;
424 listItemAttrs = listItem.attributes;
425 bullet = listItemAttrs[ 'cke:listsymbol' ];
426 bullet = bullet && bullet.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
427 listType = listStyleType = itemNumeric = null;
428
429 if ( listItemAttrs[ 'cke:ignored' ] ) {
430 children.splice( i--, 1 );
431 continue;
432 }
433
434
435 // This's from a new list root.
436 listItemAttrs[ 'cke:reset' ] && ( list = lastIndent = lastListItem = null );
437
438 // List item indent level might come from a real list indentation or
439 // been resolved from a pseudo list item's margin value, even get
440 // no indentation at all.
441 listItemIndent = Number( listItemAttrs[ 'cke:indent' ] );
442
443 // We're moving out of the current list, cleaning up.
444 if ( listItemIndent != lastIndent )
445 previousListType = previousListStyleType = null;
446
447 // List type and item style are already resolved.
448 if ( !bullet ) {
449 listType = listItemAttrs[ 'cke:listtype' ] || 'ol';
450 listStyleType = listItemAttrs[ 'cke:list-style-type' ];
451 } else {
452 // Probably share the same list style type with previous list item,
453 // give it priority to avoid ambiguous between C(Alpha) and C.(Roman).
454 if ( previousListType && listMarkerPatterns[ previousListType ][ previousListStyleType ].test( bullet[ 1 ] ) ) {
455 listType = previousListType;
456 listStyleType = previousListStyleType;
457 } else {
458 for ( var type in listMarkerPatterns ) {
459 for ( var style in listMarkerPatterns[ type ] ) {
460 if ( listMarkerPatterns[ type ][ style ].test( bullet[ 1 ] ) ) {
461 // Small numbering has higher priority, when dealing with ambiguous
462 // between C(Alpha) and C.(Roman).
463 if ( type == 'ol' && ( /alpha|roman/ ).test( style ) ) {
464 var num = /roman/.test( style ) ? fromRoman( bullet[ 1 ] ) : fromAlphabet( bullet[ 1 ] );
465 if ( !itemNumeric || num < itemNumeric ) {
466 itemNumeric = num;
467 listType = type;
468 listStyleType = style;
469 }
470 } else {
471 listType = type;
472 listStyleType = style;
473 break;
474 }
475 }
476 }
477 }
478 }
479
480 // Simply use decimal/disc for the rest forms of unrepresentable
481 // numerals, e.g. Chinese..., but as long as there a second part
482 // included, it has a bigger chance of being a order list ;)
483 !listType && ( listType = bullet[ 2 ] ? 'ol' : 'ul' );
484 }
485
486 previousListType = listType;
487 previousListStyleType = listStyleType || ( listType == 'ol' ? 'decimal' : 'disc' );
488 if ( listStyleType && listStyleType != ( listType == 'ol' ? 'decimal' : 'disc' ) )
489 listItem.addStyle( 'list-style-type', listStyleType );
490
491 // Figure out start numbering.
492 if ( listType == 'ol' && bullet ) {
493 switch ( listStyleType ) {
494 case 'decimal':
495 itemNumeric = Number( bullet[ 1 ] );
496 break;
497 case 'lower-roman':
498 case 'upper-roman':
499 itemNumeric = fromRoman( bullet[ 1 ] );
500 break;
501 case 'lower-alpha':
502 case 'upper-alpha':
503 itemNumeric = fromAlphabet( bullet[ 1 ] );
504 break;
505 }
506
507 // Always create the numbering, swipe out unnecessary ones later.
508 listItem.attributes.value = itemNumeric;
509 }
510
511 // Start the list construction.
512 if ( !list ) {
513 openedLists.push( list = new CKEDITOR.htmlParser.element( listType ) );
514 list.add( listItem );
515 children[ i ] = list;
516 } else {
517 if ( listItemIndent > lastIndent ) {
518 openedLists.push( list = new CKEDITOR.htmlParser.element( listType ) );
519 list.add( listItem );
520 lastListItem.add( list );
521 } else if ( listItemIndent < lastIndent ) {
522 // There might be a negative gap between two list levels. (#4944)
523 var diff = lastIndent - listItemIndent,
524 parent;
525 while ( diff-- && ( parent = list.parent ) )
526 list = parent.parent;
527
528 list.add( listItem );
529 } else {
530 list.add( listItem );
531 }
532
533 children.splice( i--, 1 );
534 }
535
536 lastListItem = listItem;
537 lastIndent = listItemIndent;
538 } else if ( list ) {
539 list = lastIndent = lastListItem = null;
540 }
541 }
542
543 for ( i = 0; i < openedLists.length; i++ )
544 postProcessList( openedLists[ i ] );
545
546 list = lastIndent = lastListItem = previousListId = previousListItemMargin = listBaseIndent = null;
547 },
548
549 // A simple filter which always rejecting.
550 falsyFilter: function() {
551 return false;
552 },
553
554 // A filter dedicated on the 'style' attribute filtering, e.g. dropping/replacing style properties.
555 // @param styles {Array} in form of [ styleNameRegexp, styleValueRegexp,
556 // newStyleValue/newStyleGenerator, newStyleName ] where only the first
557 // parameter is mandatory.
558 // @param whitelist {Boolean} Whether the {@param styles} will be considered as a white-list.
559 stylesFilter: function( styles, whitelist ) {
560 return function( styleText, element ) {
561 var rules = [];
562 // html-encoded quote might be introduced by 'font-family'
563 // from MS-Word which confused the following regexp. e.g.
564 //'font-family: &quot;Lucida, Console&quot;'
565 ( styleText || '' ).replace( /&quot;/g, '"' ).replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
566 name = name.toLowerCase();
567 name == 'font-family' && ( value = value.replace( /["']/g, '' ) );
568
569 var namePattern, valuePattern, newValue, newName;
570 for ( var i = 0; i < styles.length; i++ ) {
571 if ( styles[ i ] ) {
572 namePattern = styles[ i ][ 0 ];
573 valuePattern = styles[ i ][ 1 ];
574 newValue = styles[ i ][ 2 ];
575 newName = styles[ i ][ 3 ];
576
577 if ( name.match( namePattern ) && ( !valuePattern || value.match( valuePattern ) ) ) {
578 name = newName || name;
579 whitelist && ( newValue = newValue || value );
580
581 if ( typeof newValue == 'function' )
582 newValue = newValue( value, element, name );
583
584 // Return an couple indicate both name and value
585 // changed.
586 if ( newValue && newValue.push )
587 name = newValue[ 0 ], newValue = newValue[ 1 ];
588
589 if ( typeof newValue == 'string' )
590 rules.push( [ name, newValue ] );
591 return;
592 }
593 }
594 }
595
596 !whitelist && rules.push( [ name, value ] );
597
598 } );
599
600 for ( var i = 0; i < rules.length; i++ )
601 rules[ i ] = rules[ i ].join( ':' );
602 return rules.length ? ( rules.join( ';' ) + ';' ) : false;
603 };
604 },
605
606 // Migrate the element by decorate styles on it.
607 // @param styleDefinition
608 // @param variables
609 elementMigrateFilter: function( styleDefinition, variables ) {
610 return styleDefinition ? function( element ) {
611 var styleDef = variables ? new CKEDITOR.style( styleDefinition, variables )._.definition : styleDefinition;
612 element.name = styleDef.element;
613 CKEDITOR.tools.extend( element.attributes, CKEDITOR.tools.clone( styleDef.attributes ) );
614 element.addStyle( CKEDITOR.style.getStyleText( styleDef ) );
615 // Mark style classes as allowed so they will not be filtered out (#12256).
616 if ( styleDef.attributes && styleDef.attributes[ 'class' ] ) {
617 element.classWhiteList = ' ' + styleDef.attributes[ 'class' ] + ' ';
618 }
619 } : function() {};
620 },
621
622 // Migrate styles by creating a new nested stylish element.
623 // @param styleDefinition
624 styleMigrateFilter: function( styleDefinition, variableName ) {
625
626 var elementMigrateFilter = this.elementMigrateFilter;
627 return styleDefinition ? function( value, element ) {
628 // Build an stylish element first.
629 var styleElement = new CKEDITOR.htmlParser.element( null ),
630 variables = {};
631
632 variables[ variableName ] = value;
633 elementMigrateFilter( styleDefinition, variables )( styleElement );
634 // Place the new element inside the existing span.
635 styleElement.children = element.children;
636 element.children = [ styleElement ];
637
638 // #10285 - later on styleElement will replace element if element won't have any attributes.
639 // However, in some cases styleElement is identical to element and therefore should not be filtered
640 // to avoid inf loop. Unfortunately calling element.filterChildren() does not prevent from that (#10327).
641 // However, we can assume that we don't need to filter styleElement at all, so it is safe to replace
642 // its filter method.
643 styleElement.filter = function() {};
644 styleElement.parent = element;
645 } : function() {};
646 },
647
648 // A filter which remove cke-namespaced-attribute on
649 // all none-cke-namespaced elements.
650 // @param value
651 // @param element
652 bogusAttrFilter: function( value, element ) {
653 if ( element.name.indexOf( 'cke:' ) == -1 )
654 return false;
655 },
656
657 // A filter which will be used to apply inline css style according the stylesheet
658 // definition rules, is generated lazily when filtering.
659 applyStyleFilter: null
660
661 },
662
663 getRules: function( editor, filter ) {
664 var dtd = CKEDITOR.dtd,
665 blockLike = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ),
666 config = editor.config,
667 filters = this.filters,
668 falsyFilter = filters.falsyFilter,
669 stylesFilter = filters.stylesFilter,
670 elementMigrateFilter = filters.elementMigrateFilter,
671 styleMigrateFilter = CKEDITOR.tools.bind( this.filters.styleMigrateFilter, this.filters ),
672 createListBulletMarker = this.utils.createListBulletMarker,
673 flattenList = filters.flattenList,
674 assembleList = filters.assembleList,
675 isListBulletIndicator = this.utils.isListBulletIndicator,
676 containsNothingButSpaces = this.utils.isContainingOnlySpaces,
677 resolveListItem = this.utils.resolveList,
678 convertToPx = function( value ) {
679 value = CKEDITOR.tools.convertToPx( value );
680 return isNaN( value ) ? value : value + 'px';
681 },
682 getStyleComponents = this.utils.getStyleComponents,
683 listDtdParents = this.utils.listDtdParents,
684 removeFontStyles = config.pasteFromWordRemoveFontStyles !== false,
685 removeStyles = config.pasteFromWordRemoveStyles !== false;
686
687 return {
688
689 elementNames: [
690 // Remove script, meta and link elements.
691 [ ( /meta|link|script/ ), '' ]
692 ],
693
694 root: function( element ) {
695 element.filterChildren( filter );
696 assembleList( element );
697 },
698
699 elements: {
700 '^': function( element ) {
701 // Transform CSS style declaration to inline style.
702 var applyStyleFilter;
703 if ( CKEDITOR.env.gecko && ( applyStyleFilter = filters.applyStyleFilter ) )
704 applyStyleFilter( element );
705 },
706
707 $: function( element ) {
708 var tagName = element.name || '',
709 attrs = element.attributes;
710
711 // Convert length unit of width/height on blocks to
712 // a more editor-friendly way (px).
713 if ( tagName in blockLike && attrs.style )
714 attrs.style = stylesFilter( [ [ ( /^(:?width|height)$/ ), null, convertToPx ] ] )( attrs.style ) || '';
715
716 // Processing headings.
717 if ( tagName.match( /h\d/ ) ) {
718 element.filterChildren( filter );
719 // Is the heading actually a list item?
720 if ( resolveListItem( element ) )
721 return;
722
723 // Adapt heading styles to editor's convention.
724 elementMigrateFilter( config[ 'format_' + tagName ] )( element );
725 }
726 // Remove inline elements which contain only empty spaces.
727 else if ( tagName in dtd.$inline ) {
728 element.filterChildren( filter );
729 if ( containsNothingButSpaces( element ) )
730 delete element.name;
731 }
732 // Remove element with ms-office namespace,
733 // with it's content preserved, e.g. 'o:p'.
734 else if ( tagName.indexOf( ':' ) != -1 && tagName.indexOf( 'cke' ) == -1 ) {
735 element.filterChildren( filter );
736
737 // Restore image real link from vml.
738 if ( tagName == 'v:imagedata' ) {
739 var href = element.attributes[ 'o:href' ];
740 if ( href )
741 element.attributes.src = href;
742 element.name = 'img';
743 return;
744 }
745 delete element.name;
746 }
747
748 // Assembling list items into a whole list.
749 if ( tagName in listDtdParents ) {
750 element.filterChildren( filter );
751 assembleList( element );
752 }
753 },
754
755 // We'll drop any style sheet, but Firefox conclude
756 // certain styles in a single style element, which are
757 // required to be changed into inline ones.
758 'style': function( element ) {
759 if ( CKEDITOR.env.gecko ) {
760 // Grab only the style definition section.
761 var styleDefSection = element.onlyChild().value.match( /\/\* Style Definitions \*\/([\s\S]*?)\/\*/ ),
762 styleDefText = styleDefSection && styleDefSection[ 1 ],
763 rules = {}; // Storing the parsed result.
764
765 if ( styleDefText ) {
766 styleDefText
767 // Remove line-breaks.
768 .replace( /[\n\r]/g, '' )
769 // Extract selectors and style properties.
770 .replace( /(.+?)\{(.+?)\}/g, function( rule, selectors, styleBlock ) {
771 selectors = selectors.split( ',' );
772 var length = selectors.length;
773 for ( var i = 0; i < length; i++ ) {
774 // Assume MS-Word mostly generate only simple
775 // selector( [Type selector][Class selector]).
776 CKEDITOR.tools.trim( selectors[ i ] ).replace( /^(\w+)(\.[\w-]+)?$/g, function( match, tagName, className ) {
777 tagName = tagName || '*';
778 className = className.substring( 1, className.length );
779
780 // Reject MS-Word Normal styles.
781 if ( className.match( /MsoNormal/ ) )
782 return;
783
784 if ( !rules[ tagName ] )
785 rules[ tagName ] = {};
786 if ( className )
787 rules[ tagName ][ className ] = styleBlock;
788 else
789 rules[ tagName ] = styleBlock;
790 } );
791 }
792 } );
793
794 filters.applyStyleFilter = function( element ) {
795 var name = rules[ '*' ] ? '*' : element.name,
796 className = element.attributes && element.attributes[ 'class' ],
797 style;
798 if ( name in rules ) {
799 style = rules[ name ];
800 if ( typeof style == 'object' )
801 style = style[ className ];
802 // Maintain style rules priorities.
803 style && element.addStyle( style, true );
804 }
805 };
806 }
807 }
808 return false;
809 },
810
811 'p': function( element ) {
812 // A a fall-back approach to resolve list item in browsers
813 // that doesn't include "mso-list:Ignore" on list bullets,
814 // note it's not perfect as not all list style (e.g. "heading list") is shipped
815 // with this pattern. (#6662)
816 if ( ( /MsoListParagraph/i ).exec( element.attributes[ 'class' ] ) ||
817 ( element.getStyle( 'mso-list' ) && !element.getStyle( 'mso-list' ).match( /^(none|skip)$/i ) ) ) {
818 var bulletText = element.firstChild( function( node ) {
819 return node.type == CKEDITOR.NODE_TEXT && !containsNothingButSpaces( node.parent );
820 } );
821
822 var bullet = bulletText && bulletText.parent;
823 if ( bullet )
824 bullet.addStyle( 'mso-list', 'Ignore' );
825
826 }
827
828 element.filterChildren( filter );
829
830 // Is the paragraph actually a list item?
831 if ( resolveListItem( element ) )
832 return;
833
834 // Adapt paragraph formatting to editor's convention
835 // according to enter-mode.
836 if ( config.enterMode == CKEDITOR.ENTER_BR ) {
837 // We suffer from attribute/style lost in this situation.
838 delete element.name;
839 element.add( new CKEDITOR.htmlParser.element( 'br' ) );
840 } else {
841 elementMigrateFilter( config[ 'format_' + ( config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ] )( element );
842 }
843 },
844
845 'div': function( element ) {
846 // Aligned table with no text surrounded is represented by a wrapper div, from which
847 // table cells inherit as text-align styles, which is wrong.
848 // Instead we use a clear-float div after the table to properly achieve the same layout.
849 var singleChild = element.onlyChild();
850 if ( singleChild && singleChild.name == 'table' ) {
851 var attrs = element.attributes;
852 singleChild.attributes = CKEDITOR.tools.extend( singleChild.attributes, attrs );
853 attrs.style && singleChild.addStyle( attrs.style );
854
855 var clearFloatDiv = new CKEDITOR.htmlParser.element( 'div' );
856 clearFloatDiv.addStyle( 'clear', 'both' );
857 element.add( clearFloatDiv );
858 delete element.name;
859 }
860 },
861
862 'td': function( element ) {
863 // 'td' in 'thead' is actually <th>.
864 if ( element.getAncestor( 'thead' ) )
865 element.name = 'th';
866 },
867
868 // MS-Word sometimes present list as a mixing of normal list
869 // and pseudo-list, normalize the previous ones into pseudo form.
870 'ol': flattenList,
871 'ul': flattenList,
872 'dl': flattenList,
873
874 'font': function( element ) {
875 // Drop the font tag if it comes from list bullet text.
876 if ( isListBulletIndicator( element.parent ) ) {
877 delete element.name;
878 return;
879 }
880
881 element.filterChildren( filter );
882
883 var attrs = element.attributes,
884 styleText = attrs.style,
885 parent = element.parent;
886
887 if ( parent.name == 'font' ) { // Merge nested <font> tags.
888 CKEDITOR.tools.extend( parent.attributes, element.attributes );
889 styleText && parent.addStyle( styleText );
890 delete element.name;
891 }
892 // Convert the merged into a span with all attributes preserved.
893 else {
894 // Use array to avoid string concatenation and get rid of problems with trailing ";" (#12243).
895 styleText = ( styleText || '' ).split( ';' );
896
897 // IE's having those deprecated attributes, normalize them.
898 if ( attrs.color ) {
899 if ( attrs.color != '#000000' )
900 styleText.push( 'color:' + attrs.color );
901 delete attrs.color;
902 }
903 if ( attrs.face ) {
904 styleText.push( 'font-family:' + attrs.face );
905 delete attrs.face;
906 }
907 // TODO: Mapping size in ranges of xx-small,
908 // x-small, small, medium, large, x-large, xx-large.
909 if ( attrs.size ) {
910 styleText.push( 'font-size:' +
911 ( attrs.size > 3 ? 'large' : ( attrs.size < 3 ? 'small' : 'medium' ) ) );
912 delete attrs.size;
913 }
914
915 element.name = 'span';
916 element.addStyle( styleText.join( ';' ) );
917 }
918 },
919
920 'span': function( element ) {
921 // Remove the span if it comes from list bullet text.
922 if ( isListBulletIndicator( element.parent ) )
923 return false;
924
925 element.filterChildren( filter );
926 if ( containsNothingButSpaces( element ) ) {
927 delete element.name;
928 return null;
929 }
930
931 // List item bullet type is supposed to be indicated by
932 // the text of a span with style 'mso-list : Ignore' or an image.
933 if ( isListBulletIndicator( element ) ) {
934 var listSymbolNode = element.firstChild( function( node ) {
935 return node.value || node.name == 'img';
936 } );
937
938 var listSymbol = listSymbolNode && ( listSymbolNode.value || 'l.' ),
939 listType = listSymbol && listSymbol.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
940
941 if ( listType ) {
942 var marker = createListBulletMarker( listType, listSymbol );
943 // Some non-existed list items might be carried by an inconsequential list, indicate by "mso-hide:all/display:none",
944 // those are to be removed later, now mark it with "cke:ignored".
945 var ancestor = element.getAncestor( 'span' );
946 if ( ancestor && ( / mso-hide:\s*all|display:\s*none / ).test( ancestor.attributes.style ) )
947 marker.attributes[ 'cke:ignored' ] = 1;
948 return marker;
949 }
950 }
951
952 // Update the src attribute of image element with href.
953 var attrs = element.attributes,
954 styleText = attrs && attrs.style;
955
956 // Assume MS-Word mostly carry font related styles on <span>,
957 // adapting them to editor's convention.
958 if ( styleText ) {
959 attrs.style = stylesFilter( [
960 // Drop 'inline-height' style which make lines overlapping.
961 [ 'line-height' ],
962 [ ( /^font-family$/ ), null, !removeFontStyles ? styleMigrateFilter( config.font_style, 'family' ) : null ],
963 [ ( /^font-size$/ ), null, !removeFontStyles ? styleMigrateFilter( config.fontSize_style, 'size' ) : null ],
964 [ ( /^color$/ ), null, !removeFontStyles ? styleMigrateFilter( config.colorButton_foreStyle, 'color' ) : null ],
965 [ ( /^background-color$/ ), null, !removeFontStyles ? styleMigrateFilter( config.colorButton_backStyle, 'color' ) : null ]
966 ] )( styleText, element ) || '';
967 }
968
969 if ( !attrs.style )
970 delete attrs.style;
971
972 if ( CKEDITOR.tools.isEmpty( attrs ) )
973 delete element.name;
974
975 return null;
976 },
977
978 // Migrate basic style formats to editor configured ones.
979 b: elementMigrateFilter( config.coreStyles_bold ),
980 i: elementMigrateFilter( config.coreStyles_italic ),
981 u: elementMigrateFilter( config.coreStyles_underline ),
982 s: elementMigrateFilter( config.coreStyles_strike ),
983 sup: elementMigrateFilter( config.coreStyles_superscript ),
984 sub: elementMigrateFilter( config.coreStyles_subscript ),
985
986 // Remove full paths from links to anchors.
987 a: function( element ) {
988 var attrs = element.attributes;
989
990 if ( attrs.name && attrs.name.match( /ole_link\d+/i ) ) {
991 delete element.name;
992 return;
993 }
994
995 if ( attrs.href && attrs.href.match( /^file:\/\/\/[\S]+#/i ) )
996 attrs.href = attrs.href.replace( /^file:\/\/\/[^#]+/i, '' );
997 },
998
999 'cke:listbullet': function( element ) {
1000 if ( element.getAncestor( /h\d/ ) && !config.pasteFromWordNumberedHeadingToList )
1001 delete element.name;
1002 }
1003 },
1004
1005 attributeNames: [
1006 // Remove onmouseover and onmouseout events (from MS Word comments effect)
1007 [ ( /^onmouse(:?out|over)/ ), '' ],
1008 // Onload on image element.
1009 [ ( /^onload$/ ), '' ],
1010 // Remove office and vml attribute from elements.
1011 [ ( /(?:v|o):\w+/ ), '' ],
1012 // Remove lang/language attributes.
1013 [ ( /^lang/ ), '' ]
1014 ],
1015
1016 attributes: {
1017 'style': stylesFilter( removeStyles ?
1018 // Provide a white-list of styles that we preserve, those should
1019 // be the ones that could later be altered with editor tools.
1020 [
1021 // Leave list-style-type
1022 [ ( /^list-style-type$/ ), null ],
1023
1024 // Preserve margin-left/right which used as default indent style in the editor.
1025 [ ( /^margin$|^margin-(?!bottom|top)/ ), null, function( value, element, name ) {
1026 if ( element.name in { p: 1, div: 1 } ) {
1027 var indentStyleName = config.contentsLangDirection == 'ltr' ? 'margin-left' : 'margin-right';
1028
1029 // Extract component value from 'margin' shorthand.
1030 if ( name == 'margin' )
1031 value = getStyleComponents( name, value, [ indentStyleName ] )[ indentStyleName ];
1032 else if ( name != indentStyleName )
1033 return null;
1034
1035 if ( value && !emptyMarginRegex.test( value ) )
1036 return [ indentStyleName, value ];
1037 }
1038
1039 return null;
1040 } ],
1041
1042 // Preserve clear float style.
1043 [ ( /^clear$/ ) ],
1044
1045 [ ( /^border.*|margin.*|vertical-align|float$/ ), null, function( value, element ) {
1046 if ( element.name == 'img' )
1047 return value;
1048 } ],
1049
1050 [ ( /^width|height$/ ), null, function( value, element ) {
1051 if ( element.name in { table: 1, td: 1, th: 1, img: 1 } )
1052 return value;
1053 } ]
1054 ] :
1055 // Otherwise provide a black-list of styles that we remove.
1056 [
1057 [ ( /^mso-/ ) ],
1058 // Fixing color values.
1059 [ ( /-color$/ ), null, function( value ) {
1060 if ( value == 'transparent' )
1061 return false;
1062 if ( CKEDITOR.env.gecko )
1063 return value.replace( /-moz-use-text-color/g, 'transparent' );
1064 } ],
1065 // Remove empty margin values, e.g. 0.00001pt 0em 0pt
1066 [ ( /^margin$/ ), emptyMarginRegex ],
1067 [ 'text-indent', '0cm' ],
1068 [ 'page-break-before' ],
1069 [ 'tab-stops' ],
1070 [ 'display', 'none' ],
1071 removeFontStyles ? [ ( /font-?/ ) ] : null
1072 ], removeStyles ),
1073
1074 // Prefer width styles over 'width' attributes.
1075 'width': function( value, element ) {
1076 if ( element.name in dtd.$tableContent )
1077 return false;
1078 },
1079 // Prefer border styles over table 'border' attributes.
1080 'border': function( value, element ) {
1081 if ( element.name in dtd.$tableContent )
1082 return false;
1083 },
1084
1085 // Only Firefox carry style sheet from MS-Word, which
1086 // will be applied by us manually. For other browsers
1087 // the css className is useless.
1088 // We need to keep classes added as a style (#12256).
1089 'class': function( value, element ) {
1090 if ( element.classWhiteList && element.classWhiteList.indexOf( ' ' + value + ' ' ) != -1 ) {
1091 return value;
1092 }
1093 return false;
1094 },
1095
1096 // MS-Word always generate 'background-color' along with 'bgcolor',
1097 // simply drop the deprecated attributes.
1098 'bgcolor': falsyFilter,
1099
1100 // Deprecate 'valign' attribute in favor of 'vertical-align'.
1101 'valign': removeStyles ? falsyFilter : function( value, element ) {
1102 element.addStyle( 'vertical-align', value );
1103 return false;
1104 }
1105 },
1106
1107 // Fore none-IE, some useful data might be buried under these IE-conditional
1108 // comments where RegExp were the right approach to dig them out where usual approach
1109 // is transform it into a fake element node which hold the desired data.
1110 comment: !CKEDITOR.env.ie ? function( value, node ) {
1111 var imageInfo = value.match( /<img.*?>/ ),
1112 listInfo = value.match( /^\[if !supportLists\]([\s\S]*?)\[endif\]$/ );
1113
1114 // Seek for list bullet indicator.
1115 if ( listInfo ) {
1116 // Bullet symbol could be either text or an image.
1117 var listSymbol = listInfo[ 1 ] || ( imageInfo && 'l.' ),
1118 listType = listSymbol && listSymbol.match( />(?:[(]?)([^\s]+?)([.)]?)</ );
1119 return createListBulletMarker( listType, listSymbol );
1120 }
1121
1122 // Reveal the <img> element in conditional comments for Firefox.
1123 if ( CKEDITOR.env.gecko && imageInfo ) {
1124 var img = CKEDITOR.htmlParser.fragment.fromHtml( imageInfo[ 0 ] ).children[ 0 ],
1125 previousComment = node.previous,
1126 // Try to dig the real image link from vml markup from previous comment text.
1127 imgSrcInfo = previousComment && previousComment.value.match( /<v:imagedata[^>]*o:href=['"](.*?)['"]/ ),
1128 imgSrc = imgSrcInfo && imgSrcInfo[ 1 ];
1129
1130 // Is there a real 'src' url to be used?
1131 imgSrc && ( img.attributes.src = imgSrc );
1132 return img;
1133 }
1134
1135 return false;
1136 } : falsyFilter
1137 };
1138 }
1139 } );
1140
1141 // The paste processor here is just a reduced copy of html data processor.
1142 var pasteProcessor = function() {
1143 this.dataFilter = new CKEDITOR.htmlParser.filter();
1144 };
1145
1146 pasteProcessor.prototype = {
1147 toHtml: function( data ) {
1148 var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data ),
1149 writer = new CKEDITOR.htmlParser.basicWriter();
1150
1151 fragment.writeHtml( writer, this.dataFilter );
1152 return writer.getHtml( true );
1153 }
1154 };
1155
1156 CKEDITOR.cleanWord = function( data, editor ) {
1157 // We get <![if !supportLists]> and <![endif]> when we started using `dataTransfer` instead of pasteBin, so we need to
1158 // change <![if !supportLists]> to <!--[if !supportLists]--> and <![endif]> to <!--[endif]-->.
1159 data = data.replace( /<!\[([^\]]*?)\]>/g, '<!--[$1]-->' );
1160
1161 // Firefox will be confused by those downlevel-revealed IE conditional
1162 // comments, fixing them first( convert it to upperlevel-revealed one ).
1163 // e.g. <![if !vml]>...<![endif]>
1164 if ( CKEDITOR.env.gecko )
1165 data = data.replace( /(<!--\[if[^<]*?\])-->([\S\s]*?)<!--(\[endif\]-->)/gi, '$1$2$3' );
1166
1167 // #9456 - Webkit doesn't wrap list number with span, which is crucial for filter to recognize list.
1168 //
1169 // <p class="MsoListParagraphCxSpLast" style="text-indent:-18.0pt;mso-list:l0 level1 lfo2">
1170 // <!--[if !supportLists]-->
1171 // 3.<span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
1172 // <!--[endif]-->Test3<o:p></o:p>
1173 // </p>
1174 //
1175 // Transform to:
1176 //
1177 // <p class="MsoListParagraphCxSpLast" style="text-indent:-18.0pt;mso-list:l0 level1 lfo2">
1178 // <!--[if !supportLists]-->
1179 // <span>
1180 // 3.<span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman';">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
1181 // </span>
1182 // <!--[endif]-->Test3<o:p></o:p>
1183 // </p>
1184 if ( CKEDITOR.env.webkit )
1185 data = data.replace( /(class="MsoListParagraph[^>]+><!--\[if !supportLists\]-->)([^<]+<span[^<]+<\/span>)(<!--\[endif\]-->)/gi, '$1<span>$2</span>$3' );
1186
1187 var dataProcessor = new pasteProcessor(),
1188 dataFilter = dataProcessor.dataFilter;
1189
1190 // These rules will have higher priorities than default ones.
1191 dataFilter.addRules( CKEDITOR.plugins.pastefromword.getRules( editor, dataFilter ) );
1192
1193 // Allow extending data filter rules.
1194 editor.fire( 'beforeCleanWord', { filter: dataFilter } );
1195
1196 try {
1197 data = dataProcessor.toHtml( data );
1198 } catch ( e ) {
1199 editor.showNotification( editor.lang.pastefromword.error );
1200 }
1201
1202 // Below post processing those things that are unable to delivered by filter rules.
1203
1204 // Remove 'cke' namespaced attribute used in filter rules as marker.
1205 data = data.replace( /cke:.*?".*?"/g, '' );
1206
1207 // Remove empty style attribute.
1208 data = data.replace( /style=""/g, '' );
1209
1210 // Remove the dummy spans ( having no inline style ).
1211 data = data.replace( /<span>/g, '' );
1212
1213 return data;
1214 };
1215 } )();
1216
1217 /**
1218 * Whether to ignore all font related formatting styles, including:
1219 *
1220 * * font size;
1221 * * font family;
1222 * * font foreground/background color.
1223 *
1224 * config.pasteFromWordRemoveFontStyles = false;
1225 *
1226 * @since 3.1
1227 * @cfg {Boolean} [pasteFromWordRemoveFontStyles=true]
1228 * @member CKEDITOR.config
1229 */
1230
1231 /**
1232 * Whether to transform MS Word outline numbered headings into lists.
1233 *
1234 * config.pasteFromWordNumberedHeadingToList = true;
1235 *
1236 * @since 3.1
1237 * @cfg {Boolean} [pasteFromWordNumberedHeadingToList=false]
1238 * @member CKEDITOR.config
1239 */
1240
1241 /**
1242 * Whether to remove element styles that can't be managed with the editor. Note
1243 * that this doesn't handle the font specific styles, which depends on the
1244 * {@link #pasteFromWordRemoveFontStyles} setting instead.
1245 *
1246 * config.pasteFromWordRemoveStyles = false;
1247 *
1248 * @since 3.1
1249 * @cfg {Boolean} [pasteFromWordRemoveStyles=true]
1250 * @member CKEDITOR.config
1251 */