Index: CKEditor/trunk/CHANGES.html
===================================================================
--- CKEditor/trunk/CHANGES.html	(revision 6615)
+++ CKEditor/trunk/CHANGES.html	(revision 6616)
@@ -44,4 +44,5 @@
 		<li><a href="http://dev.ckeditor.com/ticket/7315">#7315</a> : Fire the 'resize' event on dialog instances.</li>
 		<li><a href="http://dev.ckeditor.com/ticket/7259">#7259</a> : Dialog definition allows to specify initial 'width' and 'height' values.</li>
+		<li><a href="http://dev.ckeditor.com/ticket/7131">#7131</a> : List item numbering is now supported when paste from MS-Word.</li>
 	</ul>
 	<p>
Index: CKEditor/trunk/_source/plugins/pastefromword/filter/default.js
===================================================================
--- CKEditor/trunk/_source/plugins/pastefromword/filter/default.js	(revision 6615)
+++ CKEditor/trunk/_source/plugins/pastefromword/filter/default.js	(revision 6616)
@@ -119,4 +119,54 @@
 	};
 
+	// 1. move consistent list item styles up to list root.
+	// 2. clear out unnecessary list item numbering.
+	function postProcessList( list )
+	{
+		var children = list.children,
+			child,
+			attrs,
+			count = list.children.length,
+			match,
+			mergeStyle,
+			styleTypeRegexp = /list-style-type:(.*?)(?:;|$)/,
+			stylesFilter = CKEDITOR.plugins.pastefromword.filters.stylesFilter;
+
+		attrs = list.attributes;
+		if ( styleTypeRegexp.exec( attrs.style ) )
+			return;
+
+		for ( var i = 0; i < count; i++ )
+		{
+			child = children[ i ];
+
+			if ( child.attributes.value && Number( child.attributes.value ) == i + 1 )
+				delete child.attributes.value;
+
+			match = styleTypeRegexp.exec( child.attributes.style );
+
+			if ( match )
+			{
+				if ( match[ 1 ] == mergeStyle || !mergeStyle )
+					mergeStyle = match[ 1 ];
+				else
+				{
+					mergeStyle = null;
+					break;
+				}
+			}
+		}
+
+		if ( mergeStyle )
+		{
+			for ( i = 0; i < count; i++ )
+			{
+				attrs = children[ i ].attributes;
+				attrs.style && ( attrs.style = stylesFilter( [ [ 'list-style-type'] ] )( attrs.style ) || '' );
+			}
+
+			list.addStyle( 'list-style-type', mergeStyle );
+		}
+	}
+
 	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;
 	var emptyMarginRegex = /^(?:\b0[^\s]*\s*){1,4}$/;		// e.g. 0px 0pt 0px
@@ -125,62 +175,49 @@
 		upperRomanLiteralRegex = new RegExp( romanLiternalPattern.toUpperCase() );
 
+	var orderedPatterns = { 'decimal' : /\d+/, 'lower-roman': lowerRomanLiteralRegex, 'upper-roman': upperRomanLiteralRegex, 'lower-alpha' : /^[a-z]+$/, 'upper-alpha': /^[A-Z]+$/ },
+		unorderedPatterns = { 'disc' : /[l\u00B7\u2002]/, 'circle' : /[\u006F\u00D8]/,'square' : /[\u006E\u25C6]/},
+		listMarkerPatterns = { 'ol' : orderedPatterns, 'ul' : unorderedPatterns },
+		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'] ],
+		alpahbets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+	// Convert roman numbering back to decimal.
+	function fromRoman( str )
+	 {
+		 str = str.toUpperCase();
+		 var l = romans.length, retVal = 0;
+		 for ( var i = 0; i < l; ++i )
+		 {
+			 for ( var j = romans[i], k = j[1].length; str.substr( 0, k ) == j[1]; str = str.substr( k ) )
+				 retVal += j[ 0 ];
+		 }
+		 return retVal;
+	 }
+
+	// Convert alphabet numbering back to decimal.
+	function fromAlphabet( str )
+	{
+		str = str.toUpperCase();
+		var l = alpahbets.length, retVal = 1;
+		for ( var x = 1; str.length > 0; x *= l )
+		{
+			retVal += alpahbets.indexOf( str.charAt( str.length - 1 ) ) * x;
+			str = str.substr( 0, str.length - 1 );
+		}
+		return retVal;
+	}
+
 	var listBaseIndent = 0,
-		 previousListItemMargin;
-
-	CKEDITOR.plugins.pastefromword =
+		previousListItemMargin = null,
+		previousListId;
+
+	var plugin = ( CKEDITOR.plugins.pastefromword =
 	{
 		utils :
 		{
 			// Create a <cke:listbullet> which indicate an list item type.
-			createListBulletMarker : function ( bulletStyle, bulletText )
+			createListBulletMarker : function ( bullet, bulletText )
 			{
-				var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' ),
-					listType;
-
-				// TODO: Support more list style type from MS-Word.
-				if ( !bulletStyle )
-				{
-					bulletStyle = 'decimal';
-					listType = 'ol';
-				}
-				else if ( bulletStyle[ 2 ] )
-				{
-					if ( !isNaN( bulletStyle[ 1 ] ) )
-						bulletStyle = 'decimal';
-					else if ( lowerRomanLiteralRegex.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'lower-roman';
-					else if ( upperRomanLiteralRegex.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'upper-roman';
-					else if ( /^[a-z]+$/.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'lower-alpha';
-					else if ( /^[A-Z]+$/.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'upper-alpha';
-					// Simply use decimal for the rest forms of unrepresentable
-					// numerals, e.g. Chinese...
-					else
-						bulletStyle = 'decimal';
-
-					listType = 'ol';
-				}
-				else
-				{
-					if ( /[l\u00B7\u2002]/.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'disc';
-					else if ( /[\u006F\u00D8]/.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'circle';
-					else if ( /[\u006E\u25C6]/.test( bulletStyle[ 1 ] ) )
-						bulletStyle = 'square';
-					else
-						bulletStyle = 'disc';
-
-					listType = 'ul';
-				}
-
-				// Represent list type as CSS style.
-				marker.attributes =
-				{
-					'cke:listtype' : listType,
-					'style' : 'list-style-type:' + bulletStyle + ';'
-				};
+				var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' );
+				marker.attributes = { 'cke:listsymbol' : bullet[ 0 ] };
 				marker.add( new CKEDITOR.htmlParser.text( bulletText ) );
 				return marker;
@@ -208,6 +245,6 @@
 
 				if ( ( listMarker = element.removeAnyChildWithName( 'cke:listbullet' ) )
-					  && listMarker.length
-					  && ( listMarker = listMarker[ 0 ] ) )
+						&& listMarker.length
+						&& ( listMarker = listMarker[ 0 ] ) )
 				{
 					element.name = 'cke:li';
@@ -215,37 +252,56 @@
 					if ( attrs.style )
 					{
-						attrs.style = CKEDITOR.plugins.pastefromword.filters.stylesFilter(
+						attrs.style = plugin.filters.stylesFilter(
 								[
 									// Text-indent is not representing list item level any more.
 									[ 'text-indent' ],
 									[ 'line-height' ],
-									// Resolve indent level from 'margin-left' value.
+									// First attempt is to resolve indent level from on a constant margin increment.
 									[ ( /^margin(:?-left)?$/ ), null, function( margin )
 									{
-										// Be able to deal with component/short-hand form style.
+										// Deal with component/short-hand form.
 										var values = margin.split( ' ' );
-										margin = CKEDITOR.plugins.pastefromword.utils.convertToPx( values[ 3 ] || values[ 1 ] || values [ 0 ] );
+										margin = plugin.utils.convertToPx( values[ 3 ] || values[ 1 ] || values [ 0 ] );
 										margin = parseInt( margin, 10 );
 
-										// Figure out the indent unit by looking at the first increament.
-										if ( !listBaseIndent && previousListItemMargin != undefined && margin > previousListItemMargin )
+										// Figure out the indent unit by checking the first time of incrementation.
+										if ( !listBaseIndent && previousListItemMargin != null && margin > previousListItemMargin )
 											listBaseIndent = margin - previousListItemMargin;
 
-										attrs[ 'cke:margin' ] = previousListItemMargin = margin;
+										previousListItemMargin = margin;
+
+										attrs[ 'cke:indent' ] = listBaseIndent && ( Math.ceil( margin / listBaseIndent ) + 1 ) || 1;
+									} ],
+									// The best situation: "mso-list:l0 level1 lfo2" tells the belonged list root, list item indentation, etc.
+									[ ( /^mso-list$/ ), null, function( val )
+									{
+										val = val.split( ' ' );
+										var listId = Number( val[ 0 ].match( /\d+/ ) ),
+											indent = Number( val[ 1 ].match( /\d+/ ) );
+
+										listId !== previousListId && ( attrs[ 'cke:reset' ] = 1 );
+										previousListId = listId;
+										attrs[ 'cke:indent' ] = indent;
 									} ]
-							] )( attrs.style, element ) || '' ;
+								] )( attrs.style, element ) || '';
 					}
 
-					// First level list item are always presented without a margin.
-					!attrs[ 'cke:margin' ] && ( attrs[ 'cke:margin' ] = previousListItemMargin = 0 );
-
-					// Inherit list-type-style from bullet.
-					var listBulletAttrs = listMarker.attributes,
-						listBulletStyle = listBulletAttrs.style;
-
-					element.addStyle( listBulletStyle );
-					CKEDITOR.tools.extend( attrs, listBulletAttrs );
+					// First level list item might be presented without a margin.
+
+
+					// In case all above doesn't apply.
+					if ( !attrs[ 'cke:indent' ] )
+					{
+						previousListItemMargin = 0;
+						attrs[ 'cke:indent' ] = 1;
+					}
+
+					// Inherit attributes from bullet.
+					CKEDITOR.tools.extend( attrs, listMarker.attributes );
 					return true;
 				}
+				// Current list disconnected.
+				else
+					previousListId = previousListItemMargin = listBaseIndent = null;
 
 				return false;
@@ -313,5 +369,5 @@
 					while ( parent )
 					{
-						parent.attributes && parent.attributes[ 'cke:list'] && indentLevel++;
+						parent.attributes && parent.attributes[ 'cke:list' ] && indentLevel++;
 						parent = parent.parent;
 					}
@@ -352,8 +408,27 @@
 
 							child.name = 'cke:li';
+
+							// Inherit numbering from list root on the first list item.
+							attrs.start && !i && ( attributes.value = attrs.start );
+
+							plugin.filters.stylesFilter(
+								[
+								    [ 'tab-stops', null, function( val )
+									{
+										var margin = val.split( ' ' )[ 1 ].match( cssLengthRelativeUnit );
+										margin && ( previousListItemMargin = parseInt( plugin.utils.convertToPx( margin[ 0 ] ), 10 ) );
+									} ],
+									[ 'mso-list', null, function( val )
+									{
+										val = val.split( ' ' );
+										var listId = Number( val[ 0 ].match( /\d+/ ) );
+										listId !== previousListId && ( attributes[ 'cke:reset' ] = 1 );
+										previousListId = listId;
+									} ]
+								] )( attributes.style );
+
 							attributes[ 'cke:indent' ] = indentLevel;
-							previousListItemMargin = 0;
 							attributes[ 'cke:listtype' ] = element.name;
-							listStyleType && child.addStyle( 'list-style-type', listStyleType, true );
+							attributes[ 'cke:list-style-type' ] = listStyleType;
 						}
 					}
@@ -375,9 +450,17 @@
 							listItem,   // The current processing cke:li element.
 							listItemAttrs,
-							listType,   // Determine the root type of the list.
 							listItemIndent, // Indent level of current list item.
+							lastIndent,
 							lastListItem, // The previous one just been added to the list.
-							list, parentList, // Current staging list and it's parent list if any.
-							indent;
+							list, // Current staging list and it's parent list if any.
+							openedLists = [],
+							previousListStyleType,
+							previousListType;
+
+					// Properties of the list item are to be resolved from the list bullet.
+					var bullet,
+						listType,
+						listStyleType,
+						itemNumeric;
 
 					for ( var i = 0; i < children.length; i++ )
@@ -390,25 +473,110 @@
 							listItem = child;
 							listItemAttrs = listItem.attributes;
-							listType = listItem.attributes[ 'cke:listtype' ];
+							bullet = listItemAttrs[ 'cke:listsymbol' ];
+							bullet = bullet && bullet.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
+							listType = listStyleType = itemNumeric = null;
+
+							if ( listItemAttrs[ 'cke:ignored' ] )
+							{
+								children.splice( i--, 1 );
+								continue;
+							}
+
+
+							// This's from a new list root.
+							listItemAttrs[ 'cke:reset' ] && ( list = lastIndent = lastListItem = null );
 
 							// List item indent level might come from a real list indentation or
 							// been resolved from a pseudo list item's margin value, even get
 							// no indentation at all.
-							listItemIndent = parseInt( listItemAttrs[ 'cke:indent' ], 10 )
-													|| listBaseIndent && ( Math.ceil( listItemAttrs[ 'cke:margin' ] / listBaseIndent ) + 1 )
-													|| 1;
-
-							// Ignore the 'list-style-type' attribute if it's matched with
-							// the list root element's default style type.
-							listItemAttrs.style && ( listItemAttrs.style =
-							        CKEDITOR.plugins.pastefromword.filters.stylesFilter(
-									[
-										[ 'list-style-type', listType == 'ol' ? 'decimal' : 'disc' ]
-									] )( listItemAttrs.style )
-									|| '' );
-
+							listItemIndent = Number( listItemAttrs[ 'cke:indent' ] );
+
+							// We're moving out of the current list, cleaning up.
+							if ( listItemIndent != lastIndent )
+								previousListType = previousListStyleType = null;
+
+							// List type and item style are already resolved.
+							if ( !bullet )
+							{
+								listType = listItemAttrs[ 'cke:listtype' ] || 'ol';
+								listStyleType = listItemAttrs[ 'cke:list-style-type' ] || 'decimal';
+							}
+							else
+							{
+								// Probably share the same list style type with previous list item,
+								// give it priority to avoid ambiguous between C(Alpha) and C.(Roman).
+								if ( previousListType && listMarkerPatterns[ previousListType ] [ previousListStyleType ].test( bullet[ 1 ] ) )
+								{
+									listType = previousListType;
+									listStyleType = previousListStyleType;
+								}
+								else
+								{
+									for ( var type in listMarkerPatterns )
+									{
+										for ( var style in listMarkerPatterns[ type ] )
+										{
+											if ( listMarkerPatterns[ type ][ style ].test( bullet[ 1 ] ) )
+											{
+												// Small numbering has higher priority, when dealing with ambiguous
+												// between C(Alpha) and C.(Roman).
+												if ( type == 'ol' && /alpha|roman/.test( style ) )
+												{
+													var num = /roman/.test( style ) ? fromRoman( bullet[ 1 ] ) : fromAlphabet( bullet[ 1 ] );
+													if ( !itemNumeric || num < itemNumeric )
+													{
+														itemNumeric = num;
+														listType = type;
+														listStyleType = style;
+													}
+												}
+												else
+												{
+													listType = type;
+													listStyleType = style;
+													break;
+												}
+											}
+										}
+									}
+								}
+
+								// Simply use decimal/disc for the rest forms of unrepresentable
+								// numerals, e.g. Chinese..., but as long as there a second part
+								// included, it has a bigger chance of being a order list ;)
+								!listType && ( listType = bullet[ 2 ] ? 'ol' : 'ul' );
+							}
+
+							previousListType = listType;
+							previousListStyleType = listStyleType || ( listType == 'ol' ? 'decimal' : 'disc' );
+							if ( listStyleType && listStyleType != ( listType == 'ol' ? 'decimal' : 'disc' ) )
+								listItem.addStyle( 'list-style-type', listStyleType );
+
+							// Figure out start numbering.
+							if ( listType == 'ol' && bullet )
+							{
+								switch ( listStyleType )
+								{
+									case 'decimal' :
+										itemNumeric = Number( bullet[ 1 ] );
+										break;
+									case 'lower-roman':
+									case 'upper-roman':
+										itemNumeric = fromRoman( bullet[ 1 ] );
+										break;
+									case 'lower-alpha':
+									case 'upper-alpha':
+										itemNumeric = fromAlphabet( bullet[ 1 ] );
+										break;
+								}
+
+								// Always create the numbering, swipe out unnecessary ones later.
+								listItem.attributes.value = itemNumeric;
+							}
+
+							// Start the list construction.
 							if ( !list )
 							{
-								list = new CKEDITOR.htmlParser.element( listType );
+								openedLists.push( list = new CKEDITOR.htmlParser.element( listType ) );
 								list.add( listItem );
 								children[ i ] = list;
@@ -416,15 +584,15 @@
 							else
 							{
-								if ( listItemIndent > indent )
+								if ( listItemIndent > lastIndent )
 								{
-									list = new CKEDITOR.htmlParser.element( listType );
+									openedLists.push( list = new CKEDITOR.htmlParser.element( listType ) );
 									list.add( listItem );
 									lastListItem.add( list );
 								}
-								else if ( listItemIndent < indent )
+								else if ( listItemIndent < lastIndent )
 								{
 									// There might be a negative gap between two list levels. (#4944)
-									var diff = indent - listItemIndent,
-										parent;
+									var diff = lastIndent - listItemIndent,
+											parent;
 									while ( diff-- && ( parent = list.parent ) )
 										list = parent.parent;
@@ -439,11 +607,14 @@
 
 							lastListItem = listItem;
-							indent = listItemIndent;
-						}
-						else
-							list = null;
+							lastIndent = listItemIndent;
+						}
+						else if ( list )
+							list = lastIndent = lastListItem = null;
 					}
 
-					listBaseIndent = 0;
+					for ( i = 0; i < openedLists.length; i++ )
+						postProcessList( openedLists[ i ] );
+
+					list = lastIndent = lastListItem = previousListId = previousListItemMargin = listBaseIndent = null;
 				},
 
@@ -471,5 +642,5 @@
 						// from MS-Word which confused the following regexp. e.g.
 						//'font-family: &quot;Lucida, Console&quot;'
-						 styleText
+						( styleText || '' )
 							.replace( /&quot;/g, '"' )
 							.replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g,
@@ -764,5 +935,8 @@
 						if ( /MsoListParagraph/.exec( element.attributes[ 'class' ] ) )
 						{
-							var bulletText = element.firstChild( function( node ) { return node.type == CKEDITOR.NODE_TEXT; } );
+							var bulletText = element.firstChild( function( node )
+							{
+								return node.type == CKEDITOR.NODE_TEXT && !containsNothingButSpaces( node.parent );
+							});
 							var bullet = bulletText && bulletText.parent,
 								bulletAttrs = bullet && bullet.attributes;
@@ -895,6 +1069,16 @@
 
 							var listSymbol =  listSymbolNode && ( listSymbolNode.value || 'l.' ),
-								listType = listSymbol.match( /^([^\s]+?)([.)]?)$/ );
-							return createListBulletMarker( listType, listSymbol );
+								listType = listSymbol.match( /^(?:[(]?)([^\s]+?)([.)]?)$/ );
+
+							if ( listType )
+							{
+								var marker = createListBulletMarker( listType, listSymbol );
+								// Some non-existed list items might be carried by an inconsequential list, indicate by "mso-hide:all/display:none",
+								// those are to be removed later, now mark it with "cke:ignored".
+								var ancestor = element.getAncestor( 'span' );
+								if ( ancestor && /mso-hide:\s*all|display:\s*none/.test( ancestor.attributes.style ) )
+									marker.attributes[ 'cke:ignored' ] = 1;
+								return marker;
+							}
 						}
 
@@ -942,5 +1126,5 @@
 						if ( element.getAncestor( /h\d/ ) && !config.pasteFromWordNumberedHeadingToList )
 							delete element.name;
-						}
+					}
 				},
 
@@ -964,4 +1148,7 @@
 					// be the ones that could later be altered with editor tools.
 					[
+						// Leave list-style-type
+						[ /^list-style-type$/, null ],
+
 						// Preserve margin-left/right which used as default indent style in the editor.
 						[ ( /^margin$|^margin-(?!bottom|top)/ ), null, function( value, element, name )
@@ -1070,5 +1257,5 @@
 								// Bullet symbol could be either text or an image.
 								var listSymbol = listInfo[ 1 ] || ( imageInfo && 'l.' ),
-									listType = listSymbol && listSymbol.match( />([^\s]+?)([.)]?)</ );
+									listType = listSymbol && listSymbol.match( />(?:[(]?)([^\s]+?)([.)]?)</ );
 								return createListBulletMarker( listType, listSymbol );
 							}
@@ -1093,5 +1280,5 @@
 			};
 		}
-	};
+	});
 
 	// The paste processor here is just a reduced copy of html data processor.
