2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 var fragmentPrototype
= CKEDITOR
.htmlParser
.fragment
.prototype,
8 elementPrototype
= CKEDITOR
.htmlParser
.element
.prototype;
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;
17 elementPrototype
.removeAnyChildWithName = function( tagName
) {
18 var children
= this.children
,
22 for ( var i
= 0; i
< children
.length
; i
++ ) {
23 child
= children
[ i
];
27 if ( child
.name
== tagName
) {
29 children
.splice( i
--, 1 );
31 childs
= childs
.concat( child
.removeAnyChildWithName( tagName
) );
36 elementPrototype
.getAncestor = function( tagNameRegex
) {
37 var parent
= this.parent
;
38 while ( parent
&& !( parent
.name
&& parent
.name
.match( tagNameRegex
) ) )
39 parent
= parent
.parent
;
43 fragmentPrototype
.firstChild
= elementPrototype
.firstChild = function( evaluator
) {
46 for ( var i
= 0; i
< this.children
.length
; i
++ ) {
47 child
= this.children
[ i
];
48 if ( evaluator( child
) )
50 else if ( child
.name
) {
51 child
= child
.firstChild( evaluator
);
60 // Adding a (set) of styles to the element's 'style' attributes.
61 elementPrototype
.addStyle = function( name
, value
, isPrepend
) {
65 if ( typeof value
== 'string' )
66 addingStyleText
+= name
+ ':' + value
+ ';';
69 if ( typeof name
== 'object' ) {
70 for ( var style
in name
) {
71 if ( name
.hasOwnProperty( style
) )
72 addingStyleText
+= style
+ ':' + name
[ style
] + ';';
75 // raw style text form.
77 addingStyleText
+= name
;
83 if ( !this.attributes
)
86 styleText
= this.attributes
.style
|| '';
88 styleText
= ( isPrepend
? [ addingStyleText
, styleText
] : [ styleText
, addingStyleText
] ).join( ';' );
90 this.attributes
.style
= styleText
.replace( /^;+|;(?=;)/g, '' );
93 // Retrieve a style property value of the element.
94 elementPrototype
.getStyle = function( name
) {
95 var styles
= this.attributes
.style
;
97 styles
= CKEDITOR
.tools
.parseCssText( styles
, 1 );
98 return styles
[ name
];
103 * Return the DTD-valid parent tag names of the specified one.
105 * @member CKEDITOR.dtd
106 * @param {String} tagName
109 CKEDITOR
.dtd
.parentOf = function( tagName
) {
111 for ( var tag
in this ) {
112 if ( tag
.indexOf( '$' ) == -1 && this[ tag
][ tagName
] )
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
,
123 count
= list
.children
.length
,
125 styleTypeRegexp
= /list-style-type:(.*?)(?:;|$)/,
126 stylesFilter
= CKEDITOR
.plugins
.pastefromword
.filters
.stylesFilter
;
128 attrs
= list
.attributes
;
129 if ( styleTypeRegexp
.exec( attrs
.style
) )
132 for ( var i
= 0; i
< count
; i
++ ) {
133 child
= children
[ i
];
135 if ( child
.attributes
.value
&& Number( child
.attributes
.value
) == i
+ 1 )
136 delete child
.attributes
.value
;
138 match
= styleTypeRegexp
.exec( child
.attributes
.style
);
141 if ( match
[ 1 ] == mergeStyle
|| !mergeStyle
)
142 mergeStyle
= match
[ 1 ];
151 for ( i
= 0; i
< count
; i
++ ) {
152 attrs
= children
[ i
].attributes
;
153 attrs
.style
&& ( attrs
.style
= stylesFilter( [ [ 'list-style-type' ] ] )( attrs
.style
) || '' );
156 list
.addStyle( 'list-style-type', mergeStyle
);
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() );
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';
171 // Convert roman numbering back to decimal.
172 function fromRoman( str
) {
173 str
= str
.toUpperCase();
174 var l
= romans
.length
,
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
) )
183 // Convert alphabet numbering back to decimal.
184 function fromAlphabet( str
) {
185 str
= str
.toUpperCase();
186 var l
= alpahbets
.length
,
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 );
195 var listBaseIndent
= 0,
196 previousListItemMargin
= null,
199 var plugin
= ( CKEDITOR
.plugins
.pastefromword
= {
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
) );
209 isListBulletIndicator: function( element
) {
210 var styleText
= element
.attributes
&& element
.attributes
.style
;
211 if ( /mso-list\s*:\s*Ignore/i.test( styleText
) )
215 isContainingOnlySpaces: function( element
) {
217 return ( ( text
= element
.onlyChild() ) && ( /^(:?\s| )+$/ ).test( text
.value
) );
220 resolveList: function( element
) {
221 // <cke:listbullet> indicate a list item.
222 var attrs
= element
.attributes
,
225 if ( ( listMarker
= element
.removeAnyChildWithName( 'cke:listbullet' ) ) && listMarker
.length
&& ( listMarker
= listMarker
[ 0 ] ) ) {
226 element
.name
= 'cke:li';
229 attrs
.style
= plugin
.filters
.stylesFilter( [
230 // Text-indent is not representing list item level any more.
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 ] );
239 // Figure out the indent unit by checking the first time of incrementation.
240 if ( !listBaseIndent
&& previousListItemMargin
!== null && margin
> previousListItemMargin
)
241 listBaseIndent
= margin
- previousListItemMargin
;
243 previousListItemMargin
= margin
;
245 attrs
[ 'cke:indent' ] = listBaseIndent
&& ( Math
.ceil( margin
/ listBaseIndent
) + 1 ) || 1;
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 ) {
255 var listId
= Number( val
[ 0 ].match( /\d+/ ) ),
256 indent
= Number( val
[ 1 ].match( /\d+/ ) );
259 listId
!== previousListId
&& ( attrs
[ 'cke:reset' ] = 1 );
260 previousListId
= listId
;
262 attrs
[ 'cke:indent' ] = indent
;
264 ] )( attrs
.style
, element
) || '';
267 // First level list item might be presented without a margin.
270 // In case all above doesn't apply.
271 if ( !attrs
[ 'cke:indent' ] ) {
272 previousListItemMargin
= 0;
273 attrs
[ 'cke:indent' ] = 1;
276 // Inherit attributes from bullet.
277 CKEDITOR
.tools
.extend( attrs
, listMarker
.attributes
);
280 // Current list disconnected.
282 previousListId
= previousListItemMargin
= listBaseIndent
= null;
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
);
293 return function( name
, styleValue
, fetchList
) {
294 calculator
.setStyle( name
, styleValue
);
296 count
= fetchList
.length
;
297 for ( var i
= 0; i
< count
; i
++ )
298 styles
[ fetchList
[ i
] ] = calculator
.getStyle( fetchList
[ i
] );
304 listDtdParents: CKEDITOR
.dtd
.parentOf( 'ol' )
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;
315 var attrs
= element
.attributes
,
318 // All list items are of the same type.
319 switch ( attrs
.type
) {
321 listStyleType
= 'lower-alpha';
324 listStyleType
= 'decimal';
326 // TODO: Support more list style type from MS-Word.
329 var children
= element
.children
,
332 for ( var i
= 0; i
< children
.length
; i
++ ) {
333 child
= children
[ i
];
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 ];
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();
351 // Move out nested list.
352 if ( last
.name
in CKEDITOR
.dtd
.$list
) {
353 element
.add( last
, i
+ 1 );
355 // Remove the parent list item if it's just a holder.
356 if ( !--listItemChildren
.length
)
357 children
.splice( i
--, 1 );
360 child
.name
= 'cke:li';
362 // Inherit numbering from list root on the first list item.
363 attrs
.start
&& !i
&& ( attributes
.value
= attrs
.start
);
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 ] ) );
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
;
378 ] )( attributes
.style
);
380 attributes
[ 'cke:indent' ] = level
;
381 attributes
[ 'cke:listtype' ] = element
.name
;
382 attributes
[ 'cke:list-style-type' ] = listStyleType
;
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
] );
393 children
= element
.children
;
399 // We're loosing tag name here, signalize this element as a list.
400 attrs
[ 'cke:list' ] = 1;
403 // Try to collect all list items among the children and establish one
404 // or more HTML list structures for them.
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.
413 previousListStyleType
, previousListType
;
415 // Properties of the list item are to be resolved from the list bullet.
416 var bullet
, listType
, listStyleType
, itemNumeric
;
418 for ( var i
= 0; i
< children
.length
; i
++ ) {
419 child
= children
[ i
];
421 if ( child
.name
== 'cke:li' ) {
424 listItemAttrs
= listItem
.attributes
;
425 bullet
= listItemAttrs
[ 'cke:listsymbol' ];
426 bullet
= bullet
&& bullet
.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
427 listType
= listStyleType
= itemNumeric
= null;
429 if ( listItemAttrs
[ 'cke:ignored' ] ) {
430 children
.splice( i
--, 1 );
435 // This's from a new list root.
436 listItemAttrs
[ 'cke:reset' ] && ( list
= lastIndent
= lastListItem
= null );
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' ] );
443 // We're moving out of the current list, cleaning up.
444 if ( listItemIndent
!= lastIndent
)
445 previousListType
= previousListStyleType
= null;
447 // List type and item style are already resolved.
449 listType
= listItemAttrs
[ 'cke:listtype' ] || 'ol';
450 listStyleType
= listItemAttrs
[ 'cke:list-style-type' ];
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
;
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
) {
468 listStyleType
= style
;
472 listStyleType
= style
;
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' );
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
);
491 // Figure out start numbering.
492 if ( listType
== 'ol' && bullet
) {
493 switch ( listStyleType
) {
495 itemNumeric
= Number( bullet
[ 1 ] );
499 itemNumeric
= fromRoman( bullet
[ 1 ] );
503 itemNumeric
= fromAlphabet( bullet
[ 1 ] );
507 // Always create the numbering, swipe out unnecessary ones later.
508 listItem
.attributes
.value
= itemNumeric
;
511 // Start the list construction.
513 openedLists
.push( list
= new CKEDITOR
.htmlParser
.element( listType
) );
514 list
.add( listItem
);
515 children
[ i
] = list
;
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
,
525 while ( diff
-- && ( parent
= list
.parent
) )
526 list
= parent
.parent
;
528 list
.add( listItem
);
530 list
.add( listItem
);
533 children
.splice( i
--, 1 );
536 lastListItem
= listItem
;
537 lastIndent
= listItemIndent
;
539 list
= lastIndent
= lastListItem
= null;
543 for ( i
= 0; i
< openedLists
.length
; i
++ )
544 postProcessList( openedLists
[ i
] );
546 list
= lastIndent
= lastListItem
= previousListId
= previousListItemMargin
= listBaseIndent
= null;
549 // A simple filter which always rejecting.
550 falsyFilter: function() {
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
) {
562 // html-encoded quote might be introduced by 'font-family'
563 // from MS-Word which confused the following regexp. e.g.
564 //'font-family: "Lucida, Console"'
565 ( styleText
|| '' ).replace( /"/g, '"' ).replace( /\s
*([^ :;]+)\s
*:\s
*([^;]+)\s
*(?=;|$)/g
, function( match
, name
, value
) {
566 name
= name
.toLowerCase();
567 name
== 'font-family' && ( value
= value
.replace( /["']/g, '' ) );
569 var namePattern
, valuePattern
, newValue
, newName
;
570 for ( var i
= 0; i
< styles
.length
; i
++ ) {
572 namePattern
= styles
[ i
][ 0 ];
573 valuePattern
= styles
[ i
][ 1 ];
574 newValue
= styles
[ i
][ 2 ];
575 newName
= styles
[ i
][ 3 ];
577 if ( name
.match( namePattern
) && ( !valuePattern
|| value
.match( valuePattern
) ) ) {
578 name
= newName
|| name
;
579 whitelist
&& ( newValue
= newValue
|| value
);
581 if ( typeof newValue
== 'function' )
582 newValue
= newValue( value
, element
, name
);
584 // Return an couple indicate both name and value
586 if ( newValue
&& newValue
.push
)
587 name
= newValue
[ 0 ], newValue
= newValue
[ 1 ];
589 if ( typeof newValue
== 'string' )
590 rules
.push( [ name
, newValue
] );
596 !whitelist
&& rules
.push( [ name
, value
] );
600 for ( var i
= 0; i
< rules
.length
; i
++ )
601 rules
[ i
] = rules
[ i
].join( ':' );
602 return rules
.length
? ( rules
.join( ';' ) + ';' ) : false;
606 // Migrate the element by decorate styles on it.
607 // @param styleDefinition
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' ] + ' ';
622 // Migrate styles by creating a new nested stylish element.
623 // @param styleDefinition
624 styleMigrateFilter: function( styleDefinition
, variableName
) {
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 ),
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
];
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
;
648 // A filter which remove cke-namespaced-attribute on
649 // all none-cke-namespaced elements.
652 bogusAttrFilter: function( value
, element
) {
653 if ( element
.name
.indexOf( 'cke:' ) == -1 )
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
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';
682 getStyleComponents
= this.utils
.getStyleComponents
,
683 listDtdParents
= this.utils
.listDtdParents
,
684 removeFontStyles
= config
.pasteFromWordRemoveFontStyles
!== false,
685 removeStyles
= config
.pasteFromWordRemoveStyles
!== false;
690 // Remove script, meta and link elements.
691 [ ( /meta|link|script/ ), '' ]
694 root: function( element
) {
695 element
.filterChildren( filter
);
696 assembleList( element
);
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
);
707 $: function( element
) {
708 var tagName
= element
.name
|| '',
709 attrs
= element
.attributes
;
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
) || '';
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
) )
723 // Adapt heading styles to editor's convention.
724 elementMigrateFilter( config
[ 'format_' + tagName
] )( element
);
726 // Remove inline elements which contain only empty spaces.
727 else if ( tagName
in dtd
.$inline
) {
728 element
.filterChildren( filter
);
729 if ( containsNothingButSpaces( element
) )
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
);
737 // Restore image real link from vml.
738 if ( tagName
== 'v:imagedata' ) {
739 var href
= element
.attributes
[ 'o:href' ];
741 element
.attributes
.src
= href
;
742 element
.name
= 'img';
748 // Assembling list items into a whole list.
749 if ( tagName
in listDtdParents
) {
750 element
.filterChildren( filter
);
751 assembleList( element
);
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.
765 if ( 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
);
780 // Reject MS-Word Normal styles.
781 if ( className
.match( /MsoNormal/ ) )
784 if ( !rules
[ tagName
] )
785 rules
[ tagName
] = {};
787 rules
[ tagName
][ className
] = styleBlock
;
789 rules
[ tagName
] = styleBlock
;
794 filters
.applyStyleFilter = function( element
) {
795 var name
= rules
[ '*' ] ? '*' : element
.name
,
796 className
= element
.attributes
&& element
.attributes
[ 'class' ],
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 );
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
);
822 var bullet
= bulletText
&& bulletText
.parent
;
824 bullet
.addStyle( 'mso-list', 'Ignore' );
828 element
.filterChildren( filter
);
830 // Is the paragraph actually a list item?
831 if ( resolveListItem( element
) )
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.
839 element
.add( new CKEDITOR
.htmlParser
.element( 'br' ) );
841 elementMigrateFilter( config
[ 'format_' + ( config
.enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' ) ] )( element
);
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
);
855 var clearFloatDiv
= new CKEDITOR
.htmlParser
.element( 'div' );
856 clearFloatDiv
.addStyle( 'clear', 'both' );
857 element
.add( clearFloatDiv
);
862 'td': function( element
) {
863 // 'td' in 'thead' is actually <th>.
864 if ( element
.getAncestor( 'thead' ) )
868 // MS-Word sometimes present list as a mixing of normal list
869 // and pseudo-list, normalize the previous ones into pseudo form.
874 'font': function( element
) {
875 // Drop the font tag if it comes from list bullet text.
876 if ( isListBulletIndicator( element
.parent
) ) {
881 element
.filterChildren( filter
);
883 var attrs
= element
.attributes
,
884 styleText
= attrs
.style
,
885 parent
= element
.parent
;
887 if ( parent
.name
== 'font' ) { // Merge nested <font> tags.
888 CKEDITOR
.tools
.extend( parent
.attributes
, element
.attributes
);
889 styleText
&& parent
.addStyle( styleText
);
892 // Convert the merged into a span with all attributes preserved.
894 // Use array to avoid string concatenation and get rid of problems with trailing ";" (#12243).
895 styleText
= ( styleText
|| '' ).split( ';' );
897 // IE's having those deprecated attributes, normalize them.
899 if ( attrs
.color
!= '#000000' )
900 styleText
.push( 'color:' + attrs
.color
);
904 styleText
.push( 'font-family:' + attrs
.face
);
907 // TODO: Mapping size in ranges of xx-small,
908 // x-small, small, medium, large, x-large, xx-large.
910 styleText
.push( 'font-size:' +
911 ( attrs
.size
> 3 ? 'large' : ( attrs
.size
< 3 ? 'small' : 'medium' ) ) );
915 element
.name
= 'span';
916 element
.addStyle( styleText
.join( ';' ) );
920 'span': function( element
) {
921 // Remove the span if it comes from list bullet text.
922 if ( isListBulletIndicator( element
.parent
) )
925 element
.filterChildren( filter
);
926 if ( containsNothingButSpaces( element
) ) {
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';
938 var listSymbol
= listSymbolNode
&& ( listSymbolNode
.value
|| 'l.' ),
939 listType
= listSymbol
&& listSymbol
.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
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;
952 // Update the src attribute of image element with href.
953 var attrs
= element
.attributes
,
954 styleText
= attrs
&& attrs
.style
;
956 // Assume MS-Word mostly carry font related styles on <span>,
957 // adapting them to editor's convention.
959 attrs
.style
= stylesFilter( [
960 // Drop 'inline-height' style which make lines overlapping.
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
) || '';
972 if ( CKEDITOR
.tools
.isEmpty( attrs
) )
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
),
986 // Remove full paths from links to anchors.
987 a: function( element
) {
988 var attrs
= element
.attributes
;
990 if ( attrs
.name
&& attrs
.name
.match( /ole_link\d+/i ) ) {
995 if ( attrs
.href
&& attrs
.href
.match( /^file:\/\/\/[\S]+#/i ) )
996 attrs
.href
= attrs
.href
.replace( /^file:\/\/\/[^#]+/i, '' );
999 'cke:listbullet': function( element
) {
1000 if ( element
.getAncestor( /h\d/ ) && !config
.pasteFromWordNumberedHeadingToList
)
1001 delete element
.name
;
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.
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.
1021 // Leave list-style-type
1022 [ ( /^list-style-type$/ ), null ],
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';
1029 // Extract component value from 'margin' shorthand.
1030 if ( name
== 'margin' )
1031 value
= getStyleComponents( name
, value
, [ indentStyleName
] )[ indentStyleName
];
1032 else if ( name
!= indentStyleName
)
1035 if ( value
&& !emptyMarginRegex
.test( value
) )
1036 return [ indentStyleName
, value
];
1042 // Preserve clear float style.
1045 [ ( /^border.*|margin.*|vertical-align|float$/ ), null, function( value
, element
) {
1046 if ( element
.name
== 'img' )
1050 [ ( /^width|height$/ ), null, function( value
, element
) {
1051 if ( element
.name
in { table: 1, td: 1, th: 1, img: 1 } )
1055 // Otherwise provide a black-list of styles that we remove.
1058 // Fixing color values.
1059 [ ( /-color$/ ), null, function( value
) {
1060 if ( value
== 'transparent' )
1062 if ( CKEDITOR
.env
.gecko
)
1063 return value
.replace( /-moz-use-text-color/g, 'transparent' );
1065 // Remove empty margin values, e.g. 0.00001pt 0em 0pt
1066 [ ( /^margin$/ ), emptyMarginRegex
],
1067 [ 'text-indent', '0cm' ],
1068 [ 'page-break-before' ],
1070 [ 'display', 'none' ],
1071 removeFontStyles
? [ ( /font-?/ ) ] : null
1074 // Prefer width styles over 'width' attributes.
1075 'width': function( value
, element
) {
1076 if ( element
.name
in dtd
.$tableContent
)
1079 // Prefer border styles over table 'border' attributes.
1080 'border': function( value
, element
) {
1081 if ( element
.name
in dtd
.$tableContent
)
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 ) {
1096 // MS-Word always generate 'background-color' along with 'bgcolor',
1097 // simply drop the deprecated attributes.
1098 'bgcolor': falsyFilter
,
1100 // Deprecate 'valign' attribute in favor of 'vertical-align'.
1101 'valign': removeStyles
? falsyFilter : function( value
, element
) {
1102 element
.addStyle( 'vertical-align', value
);
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
\]$/ );
1114 // Seek for list bullet indicator.
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
);
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 ];
1130 // Is there a real 'src' url to be used?
1131 imgSrc
&& ( img
.attributes
.src
= imgSrc
);
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();
1146 pasteProcessor
.prototype = {
1147 toHtml: function( data
) {
1148 var fragment
= CKEDITOR
.htmlParser
.fragment
.fromHtml( data
),
1149 writer
= new CKEDITOR
.htmlParser
.basicWriter();
1151 fragment
.writeHtml( writer
, this.dataFilter
);
1152 return writer
.getHtml( true );
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]-->' );
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' );
1167 // #9456 - Webkit doesn't wrap list number with span, which is crucial for filter to recognize list.
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';"> </span>
1172 // <!--[endif]-->Test3<o:p></o:p>
1177 // <p class="MsoListParagraphCxSpLast" style="text-indent:-18.0pt;mso-list:l0 level1 lfo2">
1178 // <!--[if !supportLists]-->
1180 // 3.<span style="font-size: 7pt; line-height: normal; font-family: 'Times New Roman';"> </span>
1182 // <!--[endif]-->Test3<o:p></o:p>
1184 if ( CKEDITOR
.env
.webkit
)
1185 data
= data
.replace( /(class="MsoListParagraph[^>]+><!--\[if !supportLists\]-->)([^<]+<span[^<]+<\/span>)(<!--\[endif\]-->)/gi, '$1<span>$2</span>$3' );
1187 var dataProcessor = new pasteProcessor(),
1188 dataFilter = dataProcessor.dataFilter;
1190 // These rules will have higher priorities than default ones.
1191 dataFilter.addRules( CKEDITOR.plugins.pastefromword.getRules( editor, dataFilter ) );
1193 // Allow extending data filter rules.
1194 editor.fire( 'beforeCleanWord', { filter: dataFilter } );
1197 data = dataProcessor.toHtml( data );
1199 editor.showNotification( editor.lang.pastefromword.error );
1202 // Below post processing those things that are unable to delivered by filter rules.
1204 // Remove 'cke' namespaced attribute used in filter rules as marker.
1205 data = data.replace( /cke:.*?".*?"/g, '' );
1207 // Remove empty style attribute.
1208 data = data.replace( /style=""/g, '' );
1210 // Remove the dummy spans ( having no inline style ).
1211 data = data.replace( /<span>/g, '' );
1218 * Whether to ignore all font related formatting styles, including:
1222 * * font foreground/background color.
1224 * config.pasteFromWordRemoveFontStyles = false;
1227 * @cfg {Boolean} [pasteFromWordRemoveFontStyles=true]
1228 * @member CKEDITOR.config
1232 * Whether to transform MS Word outline numbered headings into lists.
1234 * config.pasteFromWordNumberedHeadingToList = true;
1237 * @cfg {Boolean} [pasteFromWordNumberedHeadingToList=false]
1238 * @member CKEDITOR.config
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.
1246 * config.pasteFromWordRemoveStyles = false;
1249 * @cfg {Boolean} [pasteFromWordRemoveStyles=true]
1250 * @member CKEDITOR.config