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