Error parsing XSLT file: \xslt\BlogRssFeedLink.xslt

OpenXML SDK 2.0: Formatting Excel Values

posted by Administrator on Friday, June 11, 2010

This is a follow-up article to my previous post, OpenXML SDK 2.0: Export a DataTable to Excel. In that post, I demonstrated how to install and use the OpenXML SDK, and also provided sample code for exporting a DataTable to Microsoft Excel 2007. I highly recommend starting with the previous post if you are not familiar with the SDK.

This article builds on the original code and demonstrates how to apply style and value formatting to the cells inside the workbook.

To enable value and text formatting for the resulting workbook, I have created new helper methods.

Setting Colors and Styles

This first method will create a new Font object and add it to the stylesheet collection within the workbook:

  1. private UInt32Value createFont(
  2.              Stylesheet styleSheet,
  3.              string fontName,
  4.              Nullable<double> fontSize,
  5.              bool isBold,
  6.             System.Drawing. Color foreColor)
  7.         {
  8.  
  9.              Font font = new Font();
  10.             
  11.              if (!string.IsNullOrEmpty(fontName))
  12.             {
  13.                  FontName name = new FontName()
  14.                 {
  15.                     Val = fontName
  16.                 };
  17.                 font.Append(name);
  18.             }
  19.  
  20.              if (fontSize.HasValue)
  21.             {
  22.                  FontSize size = new FontSize()
  23.                 {
  24.                     Val = fontSize.Value
  25.                 };
  26.                 font.Append(size);
  27.             }
  28.  
  29.              if (isBold == true)
  30.             {
  31.                  Bold bold = new Bold();
  32.                 font.Append(bold);
  33.             }
  34.  
  35.              if (foreColor != null)
  36.             {
  37.                  Color color = new Color()
  38.                 {
  39.                     Rgb = new HexBinaryValue()
  40.                     {
  41.                         Value =
  42.                             System.Drawing. ColorTranslator.ToHtml(
  43.                                 System.Drawing. Color.FromArgb(
  44.                                     foreColor.A,
  45.                                     foreColor.R,
  46.                                     foreColor.G,
  47.                                     foreColor.B)).Replace( "#", "")
  48.                     }
  49.                 };
  50.                 font.Append(color);
  51.             }
  52.             styleSheet.Fonts.Append(font);
  53.              UInt32Value result = styleSheet.Fonts.Count;
  54.             styleSheet.Fonts.Count++;
  55.              return result;
  56.         }

The return value of the method above is the index number of the newly added Font object.

Next is the createFill method. This method works exactly like the createFont method and also returns a new index value after the Fill has been added to the workbook's styles collection:

  1. private UInt32Value createFill(
  2.     Stylesheet styleSheet,
  3.     System.Drawing.Color fillColor)
  4. {
  5.     Fill fill = new Fill(
  6.         new PatternFill(
  7.              new ForegroundColor()
  8.             {
  9.                 Rgb = new HexBinaryValue()
  10.                 {
  11.                     Value =
  12.                     System.Drawing. ColorTranslator.ToHtml(
  13.                         System.Drawing. Color.FromArgb(
  14.                             fillColor.A,
  15.                             fillColor.R,
  16.                             fillColor.G,
  17.                             fillColor.B)).Replace( "#", "")
  18.                 }
  19.             })
  20.         {
  21.             PatternType = PatternValues.Solid
  22.         }
  23.     );
  24.     styleSheet.Fills.Append(fill);
  25.  
  26.     UInt32Value result = styleSheet.Fills.Count;
  27.     styleSheet.Fills.Count++;
  28.     return result;
  29. }

 

Finally, once you have created a Font and/or Fill, you need to create a CellFormat object and add it to your styles collection:

  1. private UInt32Value createCellFormat(
  2.     Stylesheet styleSheet,
  3.     UInt32Value fontIndex,
  4.     UInt32Value fillIndex,
  5.     UInt32Value numberFormatId)
  6. {
  7.     CellFormat cellFormat = new CellFormat();
  8.     
  9.     if (fontIndex != null)
  10.         cellFormat.FontId = fontIndex;
  11.     
  12.     if (fillIndex != null)
  13.         cellFormat.FillId = fillIndex;
  14.  
  15.     if (numberFormatId != null)
  16.     {
  17.         cellFormat.NumberFormatId = numberFormatId;
  18.         cellFormat.ApplyNumberFormat = BooleanValue.FromBoolean(true);
  19.     }
  20.  
  21.     styleSheet.CellFormats.Append(cellFormat);
  22.  
  23.     UInt32Value result = styleSheet.CellFormats.Count;
  24.     styleSheet.CellFormats.Count++;
  25.     return result;
  26. }

 

Once you have created your CellFormat objects, you can apply them to any cell value by setting the StyleIndex property for the Cell object. This code demonstrates using all of the methods above to create a header format for the first row of the resulting workbook:

  1. //add column names to the first row
  2.                  Row header = new Row();
  3.                 header.RowIndex = (UInt32)1;
  4.  
  5.                  Stylesheet styleSheet = workbook.WorkbookStylesPart.Stylesheet;
  6.  
  7.                  //build the formatted header style
  8.                  UInt32Value headerFontIndex =
  9.                     createFont(
  10.                         styleSheet,
  11.                          "Arial",
  12.                         12,
  13.                          true,
  14.                         System.Drawing. Color.White);
  15.                  //set the background color style
  16.                  UInt32Value headerFillIndex =
  17.                     createFill(
  18.                         styleSheet,
  19.                         System.Drawing. Color.SlateGray);
  20.                  //create the cell style by combining font/background
  21.                  UInt32Value headerStyleIndex =
  22.                     createCellFormat(
  23.                         styleSheet,
  24.                         headerFontIndex,
  25.                         headerFillIndex,
  26.                          null);
  27.                 
  28.                  foreach (DataColumn column in table.Columns)
  29.                 {
  30.                      Cell headerCell = createTextCell(
  31.                         table.Columns.IndexOf(column) + 1,
  32.                         1,
  33.                         column.ColumnName,
  34.                         headerStyleIndex);
  35.  
  36.                     header.AppendChild(headerCell);
  37.                 }
  38.                 data.AppendChild(header);

The final piece of the solution is to add the styleIndex parameter to the createTextCell method and set the StyleIndex property on the Cell:

  1. //apply the cell style if supplied
  2. if (styleIndex.HasValue)
  3.     cell.StyleIndex = styleIndex.Value;

 

Formatting Cell Values Correctly

 

The fourth parameter for the createCellFormat method allows you to format cell values based on your data types. Although you can create custom cell formats in the same manner that you build the font and background color, the OpenXML specification (detailed in the section 8.8.30 of of the Standard ECMA-376 Office Open XML File Formats second edition draft) has a set of implied cell formats, meaning you can reference them without explicitly adding them to the StyleSheet. Here is a list of the implied formats from the draft:

ID Format Code
0 General
1 0
2 0.00
3 #,##0
4 #,##0.00
9 0%
10 0.00%
11 0.00E+00
12 # ?/?
13 # ??/??
14 mm-dd-yy
15 d-mmm-yy
16 d-mmm
17 mmm-yy
18 h:mm AM/PM
19 h:mm:ss AM/PM
20 h:mm
21 h:mm:ss
22 m/d/yy h:mm
37 #,##0 ;(#,##0)
38 #,##0 ;[Red](#,##0)
39 #,##0.00;(#,##0.00)
40 #,##0.00;[Red](#,##0.00)
45 mm:ss
46 [h]:mm:ss
47 mmss.0
48 ##0.0E+0
49 @

If you run the sample code provided in the first article, one thing you will notice about the resulting Excel document is that even cells with numeric values are treated as text. Anyone familiar with Excel has seen this pop-up tag on many occasions:

 image

Error correction in Excel highlights the values that are stored as text.

The reason behind this was due to the way cell values were added in the createTextCell method. Because the DataType of the cell is set to InlineString, the content passed into the cell was always treated as text. To overcome this limitation, I created another new method, createValueCell. First, I created three new basic styles using the createCellFormat method (using the cell formats from the table above):

/*
 * Create a set of basic cell styles for specific formats...
 * If you are controlling your table then you can simply create the styles you need,
 * this set of code is still intended to be generic.
 */
_numberStyleId = createCellFormat(styleSheet, null, null, UInt32Value.FromUInt32(3));
_doubleStyleId = createCellFormat(styleSheet, null, null, UInt32Value.FromUInt32(4));
_dateStyleId = createCellFormat(styleSheet, null, null, UInt32Value.FromUInt32(14));

You will notice in the code above that each "style" was created using the same createCellFormat method; the font and fill values have just been omitted.

Now while the table rows are being iterated through, we can check the data type of each value and decide whether or not to apply one of the formatted styles to each cell:

  1. private Row createContentRow(
  2.              DataRow dataRow,
  3.              int rowIndex)
  4.         {
  5.              Row row = new Row
  6.  
  7.             {
  8.                 RowIndex = (UInt32)rowIndex
  9.             };
  10.             
  11.              Nullable<uint> styleIndex = null;
  12.              double doubleValue;
  13.              int intValue;
  14.              DateTime dateValue;
  15.             
  16.              for (int i = 0; i < dataRow.Table.Columns.Count; i++)
  17.             {
  18.                  Cell dataCell;
  19.  
  20.                  //check the data type of the cell content to apply basic formatting
  21.                  if (DateTime.TryParse(dataRow[i].ToString(), out dateValue))
  22.                 {
  23.                     styleIndex = _dateStyleId;
  24.                      //the ToOADate method addresses how Excel stores Date values...
  25.                     dataCell = createValueCell(i + 1, rowIndex, dateValue.ToOADate().ToString(), styleIndex);
  26.                 }
  27.                  else if (int.TryParse(dataRow[i].ToString(), out intValue))
  28.                 {
  29.                     styleIndex = _numberStyleId;
  30.                     dataCell = createValueCell(i + 1, rowIndex, intValue, styleIndex);
  31.                 }
  32.                  else if (Double.TryParse(dataRow[i].ToString(), out doubleValue))
  33.                 {
  34.                     styleIndex = _doubleStyleId;
  35.                     dataCell = createValueCell(i + 1, rowIndex, doubleValue, styleIndex);
  36.                 }
  37.                  else
  38.                 {
  39.                      //assume the value is string, use the InlineString value type...
  40.                     dataCell = createTextCell(i + 1, rowIndex, dataRow[i], null);
  41.                 }
  42.  
  43.                  
  44.                 row.AppendChild(dataCell);
  45.                 styleIndex = null;                
  46.             }
  47.              return row;
  48.         }

In most cases, you will know when to apply the correct styles to each value based on your source data. In either case, the createValueCell method will store the cell content correctly, and also allow you to format those values as needed:

  1. private Cell createValueCell(
  2.     int columnIndex,
  3.     int rowIndex,
  4.     object cellValue,
  5.     Nullable<uint> styleIndex)
  6. {
  7.     Cell cell = new Cell();
  8.     cell.CellReference = getColumnName(columnIndex) + rowIndex;
  9.     CellValue value = new CellValue();  
  10.     value.Text = cellValue.ToString();            
  11.  
  12.     //apply the cell style if supplied
  13.     if (styleIndex.HasValue)
  14.         cell.StyleIndex = styleIndex.Value;
  15.  
  16.     cell.AppendChild(value);
  17.  
  18.     return cell;
  19. }

 

The final result looks much more professional than the original:

image

This code does a lot to expand on the original article, but it still only covers a small percentage of what is possible within the OpenXML SDK. Hopefully these samples will help you become familiar with how the SDK works and you can customize your own solutions to fit your requirements. I am also providing an updated sample Visual Studio 2008 project with this article so you can review the entire solution.

6 comments for “OpenXML SDK 2.0: Formatting Excel Values”

  1. Shaz Posted Tuesday, July 27, 2010 at 8:03:03 AM
    Gravatar of ShazHi,

    Thanks for a great article.
    I have tried your code and it works fine. But after the excel-file has been created and I open the file. Microsoft Excel prompts with following:
    "Excel found unreadable content in 'excelExport.xlsx'. Do you want to recover the conents of this workbook?"

    Do you have any clue why this is appearing, and is there a fix for this.?

    Would be thankful for your reply...

    Kind Regards
    SHaz
  2. Matt Posted Thursday, July 29, 2010 at 7:43:19 PM
    Gravatar of MattThat usually means something is getting written incorrectly in the Excel document. It's hard to say what might be causing the issue. Did you make any modifications to the template and/or the code itself? I run it here (the same sample) without any issues... if you're still having issues with the unmodified sample let me know.
  3. Sowmya Posted Wednesday, August 11, 2010 at 7:22:50 AM
    Gravatar of SowmyaWhen i get dialog box -Open, Save and Cancel and when I hit Open or save it into a different location than the configured location, and try to open the excel, it says the above error and the excel fiel is corrupted. However if I go to the Configured path where the file gets saved automatically, it shows correct data, just that I am not able to open from the dialog box command and save it to a different location. Any solution for this?
  4. Imtiaz Posted Wednesday, November 10, 2010 at 5:04:49 AM
    Gravatar of ImtiazCould you please mention the format code to show the currency value e.g; $ 2334 in any cell.
  5. mohmmad Posted Thursday, November 11, 2010 at 5:55:15 AM
    Gravatar of mohmmadi have the same problem that Shaz has. i did not do any changes of code, just download sample and run it , but i get same error. i'm e pro developer but new in Office Xml and i can't find out what is the problem, please help us out :) BTW thanks for your great code ;)
  6. Arild RP Posted Friday, November 12, 2010 at 3:05:09 AM
    Gravatar of Arild RPSame here, I have "converted" it to vb, can that be the reason?
    As far as I understand the error or fault appear in xl/styles.xml?

Post a comment