| 121 | // 1. move list item styles up to list root if they're consistent. |
| 122 | // 2. clear out verbose list item numbering. |
| 123 | function postProcessList( list ) |
| 124 | { |
| 125 | var children = list.children, |
| 126 | child, |
| 127 | attrs, |
| 128 | count = list.children.length, |
| 129 | match, |
| 130 | mergeStyle, |
| 131 | styleTypeRegexp = /list-style-type:(.*?)(?:;|$)/, |
| 132 | stylesFilter = CKEDITOR.plugins.pastefromword.filters.stylesFilter; |
| 133 | |
| 134 | attrs = list.attributes; |
| 135 | if ( styleTypeRegexp.exec( attrs.style ) ) |
| 136 | return; |
| 137 | |
| 138 | for ( var i = 0; i < count; i++ ) |
| 139 | { |
| 140 | child = children[ i ]; |
| 141 | if ( child.attributes.value && Number( child.attributes.value ) == i + 1 ) |
| 142 | delete child.attributes.value; |
| 143 | |
| 144 | match = styleTypeRegexp.exec( child.attributes.style ); |
| 145 | |
| 146 | if ( match ) |
| 147 | { |
| 148 | if ( match[ 1 ] == mergeStyle || !mergeStyle ) |
| 149 | mergeStyle = match[ 1 ]; |
| 150 | else |
| 151 | { |
| 152 | mergeStyle = null; |
| 153 | break; |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | if ( mergeStyle ) |
| 159 | { |
| 160 | for ( i = 0; i < count; i++ ) |
| 161 | { |
| 162 | attrs = children[ i ].attributes; |
| 163 | attrs.style && ( attrs.style = stylesFilter( [ [ 'list-style-type'] ] )( attrs.style ) || '' ); |
| 164 | } |
| 165 | |
| 166 | list.addStyle( 'list-style-type', mergeStyle ); |
| 167 | } |
| 168 | } |
| 169 | |
121 | 170 | var cssLengthRelativeUnit = /^([.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz){1}?/i; |
122 | 171 | var emptyMarginRegex = /^(?:\b0[^\s]*\s*){1,4}$/; // e.g. 0px 0pt 0px |
123 | 172 | var romanLiternalPattern = '^m{0,4}(cm|cd|d?c{0,3})(xc|xl|l?x{0,3})(ix|iv|v?i{0,3})$', |
124 | 173 | lowerRomanLiteralRegex = new RegExp( romanLiternalPattern ), |
125 | 174 | upperRomanLiteralRegex = new RegExp( romanLiternalPattern.toUpperCase() ); |
126 | 175 | |
127 | | var listBaseIndent = 0, |
128 | | previousListItemMargin; |
| 176 | var orderedPatterns = { 'decimal' : /\d+/, 'lower-roman': lowerRomanLiteralRegex, 'upper-roman': upperRomanLiteralRegex, 'lower-alpha' : /^[a-z]+$/, 'upper-alpha': /^[A-Z]+$/ }, |
| 177 | unorderedPatterns = { 'disc' : /[l\u00B7\u2002]/, 'circle' : /[\u006F\u00D8]/,'square' : /[\u006E\u25C6]/}, |
| 178 | listMarkerPatterns = { 'ol' : orderedPatterns, 'ul' : unorderedPatterns }, |
| 179 | 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'] ], |
| 180 | alpahbets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
130 | | CKEDITOR.plugins.pastefromword = |
131 | | { |
132 | | utils : |
133 | | { |
134 | | // Create a <cke:listbullet> which indicate an list item type. |
135 | | createListBulletMarker : function ( bulletStyle, bulletText ) |
136 | | { |
137 | | var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' ), |
138 | | listType; |
| 182 | // Convert roman numbering back to decimal. |
| 183 | function fromRoman( str ) |
| 184 | { |
| 185 | str = str.toUpperCase(); |
| 186 | var l = romans.length, retVal = 0; |
| 187 | for ( var i = 0; i < l; ++i ) |
| 188 | { |
| 189 | for ( var j = romans[i], k = j[1].length; str.substr( 0, k ) == j[1]; str = str.substr( k ) ) |
| 190 | retVal += j[ 0 ]; |
| 191 | } |
| 192 | return retVal; |
| 193 | } |
140 | | // TODO: Support more list style type from MS-Word. |
141 | | if ( !bulletStyle ) |
142 | | { |
143 | | bulletStyle = 'decimal'; |
144 | | listType = 'ol'; |
145 | | } |
146 | | else if ( bulletStyle[ 2 ] ) |
147 | | { |
148 | | if ( !isNaN( bulletStyle[ 1 ] ) ) |
149 | | bulletStyle = 'decimal'; |
150 | | else if ( lowerRomanLiteralRegex.test( bulletStyle[ 1 ] ) ) |
151 | | bulletStyle = 'lower-roman'; |
152 | | else if ( upperRomanLiteralRegex.test( bulletStyle[ 1 ] ) ) |
153 | | bulletStyle = 'upper-roman'; |
154 | | else if ( /^[a-z]+$/.test( bulletStyle[ 1 ] ) ) |
155 | | bulletStyle = 'lower-alpha'; |
156 | | else if ( /^[A-Z]+$/.test( bulletStyle[ 1 ] ) ) |
157 | | bulletStyle = 'upper-alpha'; |
158 | | // Simply use decimal for the rest forms of unrepresentable |
159 | | // numerals, e.g. Chinese... |
160 | | else |
161 | | bulletStyle = 'decimal'; |
162 | | |
163 | | listType = 'ol'; |
164 | | } |
165 | | else |
166 | | { |
167 | | if ( /[l\u00B7\u2002]/.test( bulletStyle[ 1 ] ) ) |
168 | | bulletStyle = 'disc'; |
169 | | else if ( /[\u006F\u00D8]/.test( bulletStyle[ 1 ] ) ) |
170 | | bulletStyle = 'circle'; |
171 | | else if ( /[\u006E\u25C6]/.test( bulletStyle[ 1 ] ) ) |
172 | | bulletStyle = 'square'; |
173 | | else |
174 | | bulletStyle = 'disc'; |
175 | | |
176 | | listType = 'ul'; |
177 | | } |
| 195 | // Convert alphabet numbering back to decimal. |
| 196 | function fromAlphabet( str ) |
| 197 | { |
| 198 | str = str.toUpperCase(); |
| 199 | var l = alpahbets.length, retVal = 1; |
| 200 | for ( var x = 1; str.length > 0; x *= l ) |
| 201 | { |
| 202 | retVal += alpahbets.indexOf( str.charAt( str.length - 1 ) ) * x; |
| 203 | str = str.substr( 0, str.length - 1 ); |
| 204 | } |
| 205 | return retVal; |
| 206 | } |
179 | | // Represent list type as CSS style. |
180 | | marker.attributes = |
181 | | { |
182 | | 'cke:listtype' : listType, |
183 | | 'style' : 'list-style-type:' + bulletStyle + ';' |
184 | | }; |
| 208 | var listBaseIndent = 0, |
| 209 | previousListItemMargin = null, |
| 210 | previousListId; |
| 211 | |
| 212 | var plugin = ( CKEDITOR.plugins.pastefromword = |
| 213 | { |
| 214 | utils : |
| 215 | { |
| 216 | // Create a <cke:listbullet> which indicate an list item type. |
| 217 | createListBulletMarker : function ( bullet, bulletText ) |
| 218 | { |
| 219 | var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' ); |
| 220 | marker.attributes = { 'cke:listsymbol' : bullet[ 0 ] }; |
234 | | attrs[ 'cke:margin' ] = previousListItemMargin = margin; |
| 270 | previousListItemMargin = margin; |
| 271 | |
| 272 | attrs[ 'cke:indent' ] = listBaseIndent && ( Math.ceil( margin / listBaseIndent ) + 1 ) || 1; |
| 273 | } ], |
| 274 | // The best situation: "mso-list:l0 level1 lfo2" tells the belonged list root, list item indentation, etc. |
| 275 | [ ( /^mso-list$/ ), null, function( val ) |
| 276 | { |
| 277 | val = val.split( ' ' ); |
| 278 | var listId = Number( val[ 0 ].match( /\d+/ ) ), |
| 279 | indent = Number( val[ 1 ].match( /\d+/ ) ); |
| 280 | |
| 281 | listId !== previousListId && ( attrs[ 'cke:reset' ] = 1 ); |
| 282 | previousListId = listId; |
| 283 | attrs[ 'cke:indent' ] = indent; |
246 | | element.addStyle( listBulletStyle ); |
247 | | CKEDITOR.tools.extend( attrs, listBulletAttrs ); |
| 291 | // In case all above doesn't apply. |
| 292 | if ( !attrs[ 'cke:indent' ] ) |
| 293 | { |
| 294 | previousListItemMargin = 0; |
| 295 | attrs[ 'cke:indent' ] = 1; |
| 296 | } |
| 297 | |
| 298 | // Inherit attributes from bullet. |
| 299 | CKEDITOR.tools.extend( attrs, listMarker.attributes ); |
| 409 | |
| 410 | // Inherit numbering from list root on the first list item. |
| 411 | attrs.start && !i && ( attributes.value = attrs.start ); |
| 412 | |
| 413 | plugin.filters.stylesFilter( |
| 414 | [ |
| 415 | [ 'tab-stops', null, function( val ) |
| 416 | { |
| 417 | var margin = val.split( ' ' )[ 1 ].match( cssLengthRelativeUnit ); |
| 418 | margin && ( previousListItemMargin = parseInt( plugin.utils.convertToPx( margin[ 0 ] ), 10 ) ); |
| 419 | } ], |
| 420 | [ 'mso-list', null, function( val ) |
| 421 | { |
| 422 | val = val.split( ' ' ); |
| 423 | var listId = Number( val[ 0 ].match( /\d+/ ) ); |
| 424 | listId !== previousListId && ( attributes[ 'cke:reset' ] = 1 ); |
| 425 | previousListId = listId; |
| 426 | } ] |
| 427 | ] )( attributes.style ); |
| 428 | |
401 | | // Ignore the 'list-style-type' attribute if it's matched with |
402 | | // the list root element's default style type. |
403 | | listItemAttrs.style && ( listItemAttrs.style = |
404 | | CKEDITOR.plugins.pastefromword.filters.stylesFilter( |
405 | | [ |
406 | | [ 'list-style-type', listType == 'ol' ? 'decimal' : 'disc' ] |
407 | | ] )( listItemAttrs.style ) |
408 | | || '' ); |
| 493 | // We're moving out of the current list, cleaning up. |
| 494 | if ( listItemIndent != lastIndent ) |
| 495 | previousListType = previousListStyleType = null; |
| 497 | // List type and item style are already resolved. |
| 498 | if ( !bullet ) |
| 499 | { |
| 500 | listType = listItemAttrs[ 'cke:listtype' ] || 'ol'; |
| 501 | listStyleType = listItemAttrs[ 'cke:list-style-type' ] || 'decimal'; |
| 502 | } |
| 503 | else |
| 504 | { |
| 505 | // Probably share the same list style type with previous list item, |
| 506 | // give it priority to avoid ambiguous between C(Alpha) and C.(Roman). |
| 507 | if ( previousListType && listMarkerPatterns[ previousListType ] [ previousListStyleType ].test( bullet[ 1 ] ) ) |
| 508 | { |
| 509 | listType = previousListType; |
| 510 | listStyleType = previousListStyleType; |
| 511 | } |
| 512 | else |
| 513 | { |
| 514 | for ( var type in listMarkerPatterns ) |
| 515 | { |
| 516 | for ( var style in listMarkerPatterns[ type ] ) |
| 517 | { |
| 518 | if ( listMarkerPatterns[ type ][ style ].test( bullet[ 1 ] ) ) |
| 519 | { |
| 520 | // Small numbering has higher priority, when dealing with ambiguous |
| 521 | // between C(Alpha) and C.(Roman). |
| 522 | if ( type == 'ol' && /alpha|roman/.test( style ) ) |
| 523 | { |
| 524 | var num = /roman/.test( style ) ? fromRoman( bullet[ 1 ] ) : fromAlphabet( bullet[ 1 ] ); |
| 525 | if ( !itemNumeric || num < itemNumeric ) |
| 526 | { |
| 527 | itemNumeric = num; |
| 528 | listType = type; |
| 529 | listStyleType = style; |
| 530 | } |
| 531 | } |
| 532 | else |
| 533 | { |
| 534 | listType = type; |
| 535 | listStyleType = style; |
| 536 | break; |
| 537 | } |
| 538 | } |
| 539 | } |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | // Simply use decimal/disc for the rest forms of unrepresentable |
| 544 | // numerals, e.g. Chinese..., but as long as there a second part |
| 545 | // included, it has a bigger chance of being a order list ;) |
| 546 | !listType && ( listType = bullet[ 2 ] ? 'ol' : 'ul' ); |
| 547 | } |
| 548 | |
| 549 | previousListType = listType; |
| 550 | previousListStyleType = listStyleType || ( listType == 'ol' ? 'decimal' : 'disc' ); |
| 551 | if ( listStyleType && listStyleType != ( listType == 'ol' ? 'decimal' : 'disc' ) ) |
| 552 | listItem.addStyle( 'list-style-type', listStyleType ); |
| 553 | |
| 554 | // Figure out start numbering. |
| 555 | if ( listType == 'ol' && bullet ) |
| 556 | { |
| 557 | switch ( listStyleType ) |
| 558 | { |
| 559 | case 'decimal' : |
| 560 | itemNumeric = Number( bullet[ 1 ] ); |
| 561 | break; |
| 562 | case 'lower-roman': |
| 563 | case 'upper-roman': |
| 564 | itemNumeric = fromRoman( bullet[ 1 ] ); |
| 565 | break; |
| 566 | case 'lower-alpha': |
| 567 | case 'upper-alpha': |
| 568 | itemNumeric = fromAlphabet( bullet[ 1 ] ); |
| 569 | break; |
| 570 | } |
| 571 | |
| 572 | // Always create the numbering, swipe out unnecessary ones later. |
| 573 | listItem.attributes.value = itemNumeric; |
| 574 | } |
| 575 | |
| 576 | // Start the list construction. |
897 | | listType = listSymbol.match( /^([^\s]+?)([.)]?)$/ ); |
898 | | return createListBulletMarker( listType, listSymbol ); |
899 | | } |
| 1067 | listType = listSymbol.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ ); |
| 1068 | |
| 1069 | if ( listType ) |
| 1070 | { |
| 1071 | var marker = createListBulletMarker( listType, listSymbol ); |
| 1072 | // Some non-existed list items might be carried by an inconsequential list, indicate by "mso-hide:all/display:none", |
| 1073 | // those are to be removed later, now mark it with "cke:ignored". |
| 1074 | var ancestor = element.getAncestor( 'span' ); |
| 1075 | if ( ancestor && /mso-hide:\s*all|display:\s*none/.test( ancestor.attributes.style ) ) |
| 1076 | marker.attributes[ 'cke:ignored' ] = 1; |
| 1077 | return marker; |
| 1078 | } |
| 1079 | } |