如何使用OpenXML API从Excel电子表格中读取表格?

6
我已经在网上阅读了许多关于如何使用OpenXML API获取单元格数据的内容。但实际上并没有太多直接明了的资料。大部分似乎都是关于写入SpreadsheetML,而不是读取…但即使这样也帮不上什么忙。 我有一个包含表格的电子表格。我知道表格的名称、该表格所在的工作表以及表中包含的列。但我无法弄清楚如何获取包含表格数据的行集合。
我使用以下内容加载文档并获取工作簿的句柄:
SpreadsheetDocument document = SpreadsheetDocument.Open("file.xlsx", false);
WorkbookPart workbook = document.WorkbookPart;

我需要找到这个表格/工作表:

Table table = null;
foreach (Sheet sheet in workbook.Workbook.GetFirstChild<Sheets>())
{
    WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheet.Id);
    foreach (TableDefinitionPart tableDefinitionPart in worksheetPart.TableDefinitionParts)
    {
        if (tableDefinitionPart.Table.DisplayName == this._tableName)
        {
            table = tableDefinitionPart.Table;
            break;
        }
    }
}

我可以通过在table.TableColumns上进行foreach循环来迭代表格中的列。

2个回答

4
使用OpenXML API读取Excel 2007/2010电子表格非常容易。某种程度上,甚至比我们过去使用的OleDB快速脏解决方案还要简单。此外,它不仅简单而且冗长,如果必须进行注释和解释,将所有代码放在这里是没有用的,因此我将只写一个摘要并链接一篇好文章。阅读MSDN上的这篇文章,它以非常简单的方式解释了如何读取XLSX文档。
总结一下,你需要做以下几步:
- 使用SpreadsheetDocument.Open打开SpreadsheetDocument。 - 从文档的WorkbookPart中使用LINQ查询获取所需的Sheet。 - 最后,使用Sheet的ID获取WorksheetPart(所需对象)。
在代码中,剥离注释和错误处理:
using (SpreadsheetDocument document = SpreadsheetDocument.Open(fileName, false))
{
   Sheet sheet = document.WorkbookPart.Workbook
      .Descendants<Sheet>()
      .Where(s => s.Name == sheetName)
      .FirstOrDefault();

   WorksheetPart sheetPart = 
      (WorksheetPart)(document.WorkbookPart.GetPartById(theSheet.Id));
}

现在(但在使用中!)你需要做的就是读取单元格的值:
Cell cell = sheetPart.Worksheet.Descendants<Cell>().
    Where(c => c.CellReference == addressName).FirstOrDefault();

如果您需要枚举行(而且它们很多),首先必须获取对SheetData对象的引用:

SheetData sheetData = sheetPart.Worksheet.Elements<SheetData>().First();

现在可以要求所有行和单元格:

foreach (Row row in sheetData.Elements<Row>())
{
   foreach (Cell cell in row.Elements<Cell>())
   {
      string text = cell.CellValue.Text;
      // Do something with the cell value
   }
}

要简单列举一个普通电子表格,您可以使用WorksheetPart对象的Descendants<Row>()

如果您需要更多关于OpenXML的资源,请查看OpenXML Developer,其中包含许多好的教程。


谢谢,Adriano - 但是SheetData元素与Table元素有什么关系?我想知道哪些单元格与哪些列相关... - Todd McDermid
使用Cell的CellReference属性。它包含单元格的引用名称(例如A1)。不要止步于我写的几行,我真的建议您查看MSDN上的链接。有许多读取数据的方法:更快但更难,更简单但更慢,容易但有一些限制... - Adriano Repetti
我会再仔细阅读一遍,但我没有看到任何关于表格构造与工作表上的单元格相关的内容...这就是我要找的。我之前看过那篇文章(以及其他解释),但我想让表格对象告诉我它的单元格在哪里,而不是假设电子表格的单元格位于特定位置。 - Todd McDermid
1
这些工具确实有所帮助,但文档中仍存在许多缺口(在我看来)。几乎所有文档都似乎是针对编写电子表格而非从中获取数据。我可以理解这一点,但这很遗憾... 对于那些稍后查看此问题的人,我应该会在我的博客(http://toddmcdermid.blogspot.com)上发布一两篇文章,谈论这个主题,并标记为“Microsoft Excel”。 - Todd McDermid
1
我不太确定为什么这被标记为答案,因为它实际上并没有关于如何处理表格的任何内容。 - Chris Rae
显示剩余5条评论

1

可能有更好的编码方法,但是因为我需要它,所以我把它拼凑在一起,希望它能帮助其他人。

using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml.Packaging;

    private static DataTable genericExcelTable(FileInfo fileName)
    {
        DataTable dataTable = new DataTable();
        try
        {
            using (SpreadsheetDocument doc = SpreadsheetDocument.Open(fileName.FullName, false))
            {
                Workbook wkb = doc.WorkbookPart.Workbook;
                Sheet wks = wkb.Descendants<Sheet>().FirstOrDefault();
                SharedStringTable sst = wkb.WorkbookPart.SharedStringTablePart.SharedStringTable;
                List<SharedStringItem> allSSI = sst.Descendants<SharedStringItem>().ToList<SharedStringItem>();
                WorksheetPart wksp = (WorksheetPart)doc.WorkbookPart.GetPartById(wks.Id);

                foreach (TableDefinitionPart tdp in wksp.TableDefinitionParts)
                {
                    QueryTablePart qtp = tdp.QueryTableParts.FirstOrDefault<QueryTablePart>();
                    Table excelTable = tdp.Table;
                    int colcounter = 0;
                    foreach (TableColumn col in excelTable.TableColumns)
                    {
                        DataColumn dcol = dataTable.Columns.Add(col.Name);
                        dcol.SetOrdinal(colcounter);
                        colcounter++;
                    }

                    SheetData data = wksp.Worksheet.Elements<SheetData>().First();

                    foreach (DocumentFormat.OpenXml.Spreadsheet.Row row in data)
                    {
                        if (isInTable(row.Descendants<Cell>().FirstOrDefault(), excelTable.Reference, true))
                        {
                            int cellcount = 0;
                            DataRow dataRow = dataTable.NewRow();
                            foreach (Cell cell in row.Elements<Cell>())
                            {

                                if (cell.DataType != null && cell.DataType.InnerText == "s")
                                {
                                    dataRow[cellcount] = allSSI[int.Parse(cell.CellValue.InnerText)].InnerText;
                                }
                                else
                                {
                                    dataRow[cellcount] = cell.CellValue.Text;
                                }
                                cellcount++;
                            }
                            dataTable.Rows.Add(dataRow);
                        }
                    }
                }
            }
            //do whatever you want with the DataTable
            return dataTable;
        }
        catch (Exception ex)
        {
            //handle an error
            return dataTable;
        }
    }
    private static Tuple<int, int> returnCellReference(string cellRef)
    {
        int startIndex = cellRef.IndexOfAny("0123456789".ToCharArray());
        string column = cellRef.Substring(0, startIndex);
        int row = Int32.Parse(cellRef.Substring(startIndex));
        return new Tuple<int,int>(TextToNumber(column), row);
    }
    private static int TextToNumber(string text)
    {
        return text
            .Select(c => c - 'A' + 1)
            .Aggregate((sum, next) => sum * 26 + next);
    }
    private static bool isInTable(Cell testCell, string tableRef, bool headerRow){
        Tuple<int, int> cellRef = returnCellReference(testCell.CellReference.ToString());
        if (tableRef.Contains(":"))
        {
            int header = 0;
            if (headerRow)
            {
                header = 1;
            }
            string[] tableExtremes = tableRef.Split(':');
            Tuple<int, int> startCell = returnCellReference(tableExtremes[0]);
            Tuple<int, int> endCell = returnCellReference(tableExtremes[1]);
            if (cellRef.Item1 >= startCell.Item1
                && cellRef.Item1 <= endCell.Item1
                && cellRef.Item2 >= startCell.Item2 + header
                && cellRef.Item2 <= endCell.Item2) { return true; }
            else { return false; }
        }
        else if (cellRef.Equals(returnCellReference(tableRef)))
        {
            return true;
        } 
        else 
        {
            return false;
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接