alexmen

专注.net软件开发,项目管理体系PMBOK.

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

Introduction

We produce professional business software, and we often have to import data from very simple Excel *.xslx-files: with some relevant rows and cells in the first worksheet of a workbook, and that's it. But we do not want to use large DLL's or third party software. Therefore we produced a small solution for our needs. It could be useful for you, too: 

Using the code

Download the "Excel.dll" (8 kByte, .net 4.5!) and add it to your project. Or adapt the source code. Then work with the rows and cells (of the first worksheet) in the Excel file like so:

worksheet ws = worksheet.GetData(@"C:\ExcelFile.xlsx");
foreach (var row in ws.Rows)
    foreach (var cell in row.Cells)
        if (cell != null)
            Console.WriteLine(cell.Text); // Do something with the cells

Here you open the Excel file, and iterate through the rows (and the cells of each row) within three lines of code.

Points of Interest

This article (written by M I developer) describes all the theoretical background, if you are interested in it. We only reduced our solution to the max using the integrated ZIP-library in .net 4.5 and the standard XML-serializer of .net.

If you want to adapt the solution to your needs: edit the simple source code for the Excel.dll. This is how it works:

Maybe you did not know that xlsx-files are ZIP-files. And the text strings of the Excel cells of all worksheets per workbook are always stored in a file named "xl/sharedStrings.xml", while the first worksheet is called "xl/worksheets/sheet1.xml".

So we have to unzip the Excel file and we have to deserialize the two mentioned XML files in it:

using System.IO.Compression;

public static worksheet GetData(string ExcelFileName)
{
    worksheet ws;

    using (ZipArchive zipArchive = ZipFile.Open(ExcelFileName, ZipArchiveMode.Read))
    {
        worksheet.SharedStrings = worksheet.DeserializedZipEntry<sst>(zipArchive, @"xl/sharedStrings.xml");
        ws = worksheet.DeserializedZipEntry<worksheet>(zipArchive, @"xl/worksheets/sheet1.xml");
    }

For deserialization of an XML formatted ZIP-entry (see also this article written by Md. Rashim uddin) we use this generic method:

private static T DeserializedZipEntry<t>(ZipArchive ZipArchive, string ZipEntryName)
{
    using (Stream stream
           = ZipArchive.Entries.First<ziparchiveentry>(n => n.FullName.Equals(ZipEntryName)).Open())
        return (T)new XmlSerializer(typeof(T), worksheet.RootAttr).Deserialize(XmlReader.Create(stream));
}

Therefore the XML-structures have to be reflected in our classes. Here you see the "sst"-class and the "SharedString"-class for the XML in the "shared strings table":

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="72" uniqueCount="6">
  <si>
    <t>Text A</t>
  </si>
  <si>
    <t>Text B</t>
  </si>
</sst>
public class sst
{
    [XmlElement("si")]
    public SharedString[] si;

    public sst()
    {
    }
}

public class SharedString
{
    public string t;
}

The same strategy we also use for the "worksheet" -XML-file in the ZIP-file. There we focus on the XML-elements and -attributes "row", "c", "v", "r" and "t". All the work is done again by the XmlSerializer:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<dimension ref="A1:F12"/>
<sheetViews>
  <sheetView workbookViewId="0"></sheetView>
</sheetViews>
<sheetFormatPr baseColWidth="10" defaultRowHeight="15"/>
<sheetData>
  <row r="1">
    <c r="A1" t="s">
      <v>0</v>
    </c>
    <c r="B1" t="s">
      <v>1</v>
    </c>
    <c r="C1" t="s">
      <v>2</v>
    </c>
  </row>
</sheetData>
</worksheet>
public class worksheet
{
    [XmlArray("sheetData")]
    [XmlArrayItem("row")]
    public Row[] Rows;

    public class worksheet
    {
    }
}
public class Row
{
    [XmlElement("c")]
    public Cell[] FilledCells;
}
public class Cell
{
    [XmlAttribute("r")]
    public string CellReference;
    [XmlAttribute("t")]
    public string tType = "";
    [XmlElement("v")]
    public string Value;
}

Of course we have to do a little bit in order to convert the usual Excel cell references like "A1", "B1" and so on to column indices. That is done via a setter of "CellReference" in the "Cell"-class and a small method named "GetColumnIndex()":

[XmlAttribute("r")]
public string CellReference
{
    get
    {
        return ColumnIndex.ToString();
    }
    set
    {
        ColumnIndex = worksheet.GetColumnIndex(value);
        if (ColumnIndex > worksheet.MaxColumnIndex)
            worksheet.MaxColumnIndex = ColumnIndex;
    }
}

(Here we also derive the maximum column index for the whole worksheet.)

public static int GetColumnIndex(string CellReference)
{
    string colLetter = new Regex("[A-Za-z]+").Match(CellReference).Value.ToUpper();
    int colIndex = 0;

    for (int i = 0; i < colLetter.Length; i++)
    {
        colIndex *= 26;
        colIndex += (colLetter[i] - 'A' + 1);
    }
    return colIndex - 1;
}

The last challenge has to do with the fact, that the Excel file does not contain empty Excel cells. So the tiny methods "ExpandRows()" and "ExpandCells()" handle that problem:

private void ExpandRows()
{
    foreach (var row in Rows)
        row.ExpandCells(NumberOfColumns);
}

public void ExpandCells(int NumberOfColumns)
{
    Cells = new Cell[NumberOfColumns];
    foreach (var cell in FilledCells)
        Cells[cell.ColumnIndex] = cell;
    FilledCells = null;
}

In the end we have an array of all rows and an array of all cells for each row representing all columns of the specific Excel worksheet. Empty cells are null in the array, but the ColumnIndex of each cell in "Row.Cells[]" corresponds with the actual Excel column of each cell.

Besides, when you know that an Excel cell contains a date as its value, you can use this method for conversion:

public static DateTime DateFromExcelFormat(string ExcelDateValue)
{
    return DateTime.FromOADate(Convert.ToDouble(ExcelDateValue));
}

Let me know how the total Excel.DLL works in your environment - and have fun with it!

posted on 2014-07-30 22:10  alexmen  阅读(737)  评论(0)    收藏  举报