diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-01-25 17:45:33 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-01-25 18:00:33 +0100 |
commit | 7adcb81e4f83f98c468889aaa5a85558ba88c770 (patch) | |
tree | 0d6ede733777b29060b48df4afaa2c64bfbae276 /sources/plugins/pastefromword/filter/default.js | |
download | connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.tar.gz connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.tar.zst connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.zip |
Initial commit4.5.6
Diffstat (limited to 'sources/plugins/pastefromword/filter/default.js')
-rw-r--r-- | sources/plugins/pastefromword/filter/default.js | 1251 |
1 files changed, 1251 insertions, 0 deletions
diff --git a/sources/plugins/pastefromword/filter/default.js b/sources/plugins/pastefromword/filter/default.js new file mode 100644 index 00000000..0c97ef52 --- /dev/null +++ b/sources/plugins/pastefromword/filter/default.js | |||
@@ -0,0 +1,1251 @@ | |||
1 | /** | ||
2 | * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. | ||
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | ||
4 | */ | ||
5 | |||
6 | ( function() { | ||
7 | 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| )+$/ ).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: "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, '' ) ); | ||
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';"> </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';"> </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 | */ | ||