Share via


使用 Open XML SDK 2.0 检索 Excel 2010 中单元格的值

Office 可视操作方法

**摘要:**在 Open XML SDK 2.0 中使用强类型类可检索 Excel 2007 或 Excel 2010 文档中的单元格的值,而无需将该文档加载到 Microsoft Excel 中。

上次修改时间: 2015年3月9日

适用范围: Excel 2007 | Excel 2010 | Office 2007 | Office 2010 | Open XML | PowerPoint 2010 | VBA | Word 2010

**发布时间:**2010 年 8 月

**供稿人:**Ken Getz,MCW Technologies, LLC

概述

利用 Open XML 文件格式,可以检索有关 Excel 工作簿中特定单元格的信息。Open XML SDK 2.0 添加了强类型类以简化对 Open XML 文件格式的访问:SDK 简化了检索工作簿相关信息的任务,并会查找适当的 XML 内容。此直观操作方法附带的代码示例演示如何使用 SDK 执行此任务。

编码

此直观操作方法附带的代码示例包括检索 Excel 2007 或 Excel 2010 工作簿中指定工作表中的指定单元格的值所需的代码。

设置引用

若要使用 Open XML SDK 2.0 中的代码,您必须向您的项目添加多个引用。虽然示例项目包含这些引用,但您必须在您的代码中显式引用以下程序集:

  • WindowsBase - 可以根据您创建的项目类型为您设置此引用。

  • DocumentFormat.OpenXml - 由 Open XML SDK 2.0 安装。

您还应将下面的 using/Imports 语句添加到代码文件的顶部。

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

检查过程

XLGetCellValue 过程接受三个参数,这些参数指示以下内容:

  • 要检查的文档的名称(字符串)。

  • 要检查的工作表的名称(字符串)。

  • 要从中检索值的单元格地址,例如 A1、B12(字符串)。

Public Function XLGetCellValue(ByVal fileName As String,
  ByVal sheetName As String, ByVal addressName As String) As String
public static string XLGetCellValue(string fileName, 
  string sheetName, string addressName)

如果能找到指定单元格的值,则此过程将返回该值。若要调用该过程,请传递所有参数值,如下面的代码示例所示。

Const fileName As String = "C:\temp\GetCellValue.xlsx"
Dim value As String = XLGetCellValue(fileName, "Sheet1", "B3")
const string fileName = @"C:\temp\GetCellValue.xlsx";
string value = XLGetCellValue(fileName, "Sheet1", "A1");

访问单元格

下面的代码示例演示代码如何首先创建将包含返回值的变量,并假定结果为 Null。

Dim value as String = Nothing
string value = null;

接下来,此代码将使用 SpreadsheetDocument.Open 方法打开文档,并指示应打开此文档以供只读访问(最后的 false 参数)。紧接着,此代码将使用文档的 WorkbookPart 属性来检索对工作簿部分的引用。

Using document As SpreadsheetDocument =
  SpreadsheetDocument.Open(fileName, False)

  Dim wbPart As WorkbookPart = document.WorkbookPart
  ' Code removed here…
End Using
using (SpreadsheetDocument document = 
  SpreadsheetDocument.Open(fileName, false))
{
    WorkbookPart wbPart = document.WorkbookPart;
    // Code removed here…
}

若要查找请求的单元格,则代码必须先检索对工作表的引用(假定已提供其名称)。代码必须搜索工作簿部分 Workbook 属性的所有工作表类型后代,并检查它查找的每个工作表的 Name 属性。请注意,此搜索只会检查工作簿的关系,而不会实际查找工作簿部分 - 它仅查找对工作表的引用,其中包含工作表的名称和 ID 等信息。使用 LINQ 查询执行此操作最为简单。

Dim theSheet As Sheet = wbPart.Workbook.Descendants(Of Sheet)(). 
  Where(Function(s) s.Name = sheetName).FirstOrDefault()

If theSheet Is Nothing Then
  Throw New ArgumentException("sheetName")
End If
Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
  Where(s => s.Name == sheetName).FirstOrDefault();
if (theSheet == null)
{
  throw new ArgumentException("sheetName");
}

请注意,FirstOrDefault 方法将返回第一个匹配引用(在此示例中,为一个工作表)或一个空引用(如果未找到匹配项)。代码将检查空引用,如果您传递的工作表名无效,则将引发异常。

现在,您已具有有关工作表的信息,代码必须检索对相应的工作表部分的引用。您检索到的工作表信息提供了一个 Id 属性,利用此 Id 属性,代码可通过调用 WorkbookPartGetPartById 属性来检索对相应的 WorksheetPart 的引用。

Dim wsPart As WorksheetPart =
  CType(wbPart.GetPartById(theSheet.Id), WorksheetPart)
WorksheetPart wsPart = 
  (WorksheetPart)(wbPart.GetPartById(theSheet.Id));

如同在查找命名工作表时一样,在查找命名单元格时,代码将使用 Descendants 方法来查找第一个匹配项,其中 Reference 属性等于指定的 addressName 参数。在此方法调用后,名为 theCell 的变量将包含对单元格的引用或包含空引用。

Dim theCell As Cell = wsPart.Worksheet.Descendants(Of Cell).
  Where(Function(c) c.CellReference = addressName).FirstOrDefault
Cell theCell = wsPart.Worksheet.Descendants<Cell>().
  Where(c => c.CellReference == addressName).FirstOrDefault();

检索值

此时,名为 theCell 的变量将包含空引用或包含对请求的单元格的引用。如果您检查该单元格的 Open XML 内容(即 theCell.OuterXml),您将发现 XML 与以下代码示例类似。

<x:c r="A1">
  <x:v>12.345000000000001</x:v>
</x:c>

InnerText 属性包含单元格的内容,这样一来,下一个代码块将检索此值。

If theCell IsNot Nothing Then
  value = theCell.InnerText
  ' Code removed here…
End If
if (theCell != null)
{
  value = theCell.InnerText;
  // Code removed here…
}

现在,示例过程必须解释该值。实际上,此示例将处理数值、日期值、字符串值和布尔值 - 您可以在必要时扩展此示例。单元格类型提供了一个 DataType 属性,该属性指示单元格内的数据类型;对于数字和日期类型,DataType 属性的值为 null。它包含值 CellValues.SharedString(对于字符串)和 CellValues.Boolean(对于布尔值)。如果 DataType 属性为 null,则代码仅返回该单元格的值(一个数值)。否则,代码将根据数据类型来进行分支以继续。

If theCell.DataType IsNot Nothing Then
  Select Case theCell.DataType.Value
  ' Code removed here…
  End Select
End If
if (theCell.DataType != null)
{
  switch (theCell.DataType.Value)
    {
    // Code removed here…
    }
}

如果 DataType 属性包含 CellValues.SharedString,则代码必须检索对单个 SharedStringTablePart 的引用。

Dim stringTable = wbPart.
  GetPartsOfType(Of SharedStringTablePart).FirstOrDefault()ws.Save()
var stringTable = 
  wbPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();

接下来,如果有字符串表,则代码将返回它在指定索引处找到的元素的 InnerText 属性(首先会将值属性转换为整数)。

> [!NOTE] >

如果没有字符串表,则表示工作簿已损坏,并且示例代码会将索引返回到字符串表中而非字符串本身。

If stringTable IsNot Nothing Then
  value = stringTable.SharedStringTable.
    ElementAt(Integer.Parse(value)).InnerText
End If
if (stringTable != null)
{
  value = stringTable.SharedStringTable.
    ElementAt(int.Parse(value)).InnerText;
}

如果 DataType 属性包含 CellValues.Boolean,则代码会将它在单元格值中找到的 0 或 1 转换为适当的文本字符串。

Case CellValues.Boolean
  Select Case value
    Case "0"
      value = "FALSE"
    Case Else
      value = "TRUE"
  End Select
case CellValues.Boolean:
  switch (value)
  {
    case "0":
      value = "FALSE";
      break;
    default:
      value = "TRUE";
      break;
  }

最后,该过程将返回变量 value,其中包含请求的信息。

示例过程

下面的代码示例演示完整的过程。

Public Function XLGetCellValue(ByVal fileName As String,
  ByVal sheetName As String, ByVal addressName As String) As String
  Dim value As String = Nothing

  Using document As SpreadsheetDocument =
    SpreadsheetDocument.Open(fileName, False)

    Dim wbPart As WorkbookPart = document.WorkbookPart

    ' Find the sheet with the supplied name, and then use that 
    ' Sheet object to retrieve a reference to the appropriate 
    ' worksheet.
    Dim theSheet As Sheet = wbPart.Workbook.Descendants(Of Sheet)().
      Where(Function(s) s.Name = sheetName).FirstOrDefault()

    If theSheet Is Nothing Then
      Throw New ArgumentException("sheetName")
    End If

    ' Retrieve a reference to the worksheet part, and then use its 
    ' Worksheet property to get a reference to the cell whose address 
    ' matches the address you supplied:
    Dim wsPart As WorksheetPart =
      CType(wbPart.GetPartById(theSheet.Id), WorksheetPart)
    Dim theCell As Cell = wsPart.Worksheet.Descendants(Of Cell).
      Where(Function(c) c.CellReference = addressName).FirstOrDefault

    ' If the cell does not exist, return an empty string.
    If theCell IsNot Nothing Then
      value = theCell.InnerText

      ' If the cell represents an numeric value, you are done. 
      ' For dates, this code returns the serialized value that 
      ' represents the date. The code handles strings and Booleans
      ' individually. For shared strings, the code looks up the 
      ' corresponding value in the shared string table. For Booleans, 
      ' the code converts the value into the words TRUE or FALSE.
      If theCell.DataType IsNot Nothing Then
        Select Case theCell.DataType.Value
          Case CellValues.SharedString
            ' For shared strings, look up the value in the shared 
            ' strings table.
            Dim stringTable = wbPart.
              GetPartsOfType(Of SharedStringTablePart).FirstOrDefault()
            ' If the shared string table is missing, something is wrong.
            ' Return the index that you found in the cell.
            ' Otherwise, look up the correct text in the table.
            If stringTable IsNot Nothing Then
              value = stringTable.SharedStringTable.
                ElementAt(Integer.Parse(value)).InnerText
            End If
          Case CellValues.Boolean
            Select Case value
              Case "0"
                value = "FALSE"
              Case Else
                value = "TRUE"
            End Select
        End Select
      End If
    End If
  End Using
  Return value
End Function
public static string XLGetCellValue(
  string fileName, string sheetName, string addressName)
{
  string value = null;

  using (SpreadsheetDocument document = 
    SpreadsheetDocument.Open(fileName, false))
  {
    WorkbookPart wbPart = document.WorkbookPart;

    // Find the sheet with the supplied name, and then use that Sheet
    // object to retrieve a reference to the appropriate worksheet.
    Sheet theSheet = wbPart.Workbook.Descendants<Sheet>().
      Where(s => s.Name == sheetName).FirstOrDefault();

    if (theSheet == null)
    {
      throw new ArgumentException("sheetName");
    }

    // Retrieve a reference to the worksheet part, and then use its 
    // Worksheet property to get a reference to the cell whose 
    // address matches the address you supplied:
    WorksheetPart wsPart = 
      (WorksheetPart)(wbPart.GetPartById(theSheet.Id));
    Cell theCell = wsPart.Worksheet.Descendants<Cell>().
      Where(c => c.CellReference == addressName).FirstOrDefault();

    // If the cell does not exist, return an empty string:
    if (theCell != null)
    {
      value = theCell.InnerText;

      // If the cell represents a numeric value, you are done. 
      // For dates, this code returns the serialized value that 
      // represents the date. The code handles strings and Booleans
      // individually. For shared strings, the code looks up the 
      // corresponding value in the shared string table. For Booleans, 
      // the code converts the value into the words TRUE or FALSE.
      if (theCell.DataType != null)
      {
        switch (theCell.DataType.Value)
        {
          case CellValues.SharedString:
            // For shared strings, look up the value in the shared 
            // strings table.
            var stringTable = wbPart.
              GetPartsOfType<SharedStringTablePart>().FirstOrDefault();
            // If the shared string table is missing, something is 
            // wrong. Return the index that you found in the cell.
            // Otherwise, look up the correct text in the table.
            if (stringTable != null)
            {
              value = stringTable.SharedStringTable.
                ElementAt(int.Parse(value)).InnerText;
            }
            break;

          case CellValues.Boolean:
            switch (value)
            {
              case "0":
                value = "FALSE";
                break;
              default:
                value = "TRUE";
                break;
            }
            break;
        }
      }
    }
  }
  return value;
}
读取

此直观操作方法附带的示例包含一些代码,这些代码将检索 Excel 2007 或 Excel 2010 工作簿中指定工作表中的特定单元格的值。若要使用该代码示例,请安装 Open XML SDK 2.0(可通过"浏览"一节中列出的链接获得)。该示例还使用作为 Open XML SDK 2.0 的代码示例集的一部分包含的代码。"浏览"一节还包括指向完整代码示例集的链接,但您无需下载并安装示例代码即可使用示例。

示例应用程序将检索您提供的文档中多个单元格中的值,并在示例中调用 XLGetCellValue 方法来执行此操作。此方法会将指定单元格的值作为字符串返回 - 调用的代码必须解释字符串值。对此方法的调用类似于以下代码示例。

Const fileName As String = "C:\temp\GetCellValue.xlsx"
Dim value As String = XLGetCellValue(fileName, "Sheet1", "A1")
Console.WriteLine(value)
value = XLGetCellValue(fileName, "Sheet1", "A2")
Console.WriteLine(value)
value = XLGetCellValue(fileName, "Sheet1", "A3")
Console.WriteLine(value)
value = XLGetCellValue(fileName, "Sheet1", "A4")
Console.WriteLine(value)
const string fileName = @"C:\temp\GetCellValue.xlsx";
string value = XLGetCellValue(fileName, "Sheet1", "A1");
Console.WriteLine(value);
value = XLGetCellValue(fileName, "Sheet1", "A2");
Console.WriteLine(value);
value = XLGetCellValue(fileName, "Sheet1", "A3");
Console.WriteLine(value);
value = XLGetCellValue(fileName, "Sheet1", "A4");
Console.WriteLine(value);

了解 Excel 存储单元格及其值的方式非常重要。图 1 显示了一个示例工作簿,其中的四个单元格中具有四个类型的值 - 示例应用程序假定您已创建此工作簿并将其另存为 C:\temp\GetCellValue.xlsx。Open XML SDK 2.0 的工具目录中包含一个名为 OpenXmlSdkTool.exe 的有用的应用程序,如图 2 所示。利用此工具,您可以打开一个文档并查看该文档的各个部分及其层次结构。图 2 显示了测试文档 - 在左窗格中,已将文档扩展到工作表节点;在右窗格中,此工具显示了部分的 XML 和可用来生成部分的内容的反射 C# 代码。

图 1. Excel 工作簿中的示例数据

Excel 工作簿中的示例数据

图 2 显示了 Open XML SDK 2.0 附带的 OpenXmlSdkTool.exe 应用程序。

图 2. Open XML SDK 2.0 Productivity Tool

Open XML SDK 2.0 生产力工具

如果您检查图 2 中的左窗格(部分的层次结构)和 XML 内容,您会发现一个事实,即您需要了解此直观操作方法中的代码:

  • 在右侧突出显示的 XML 内容中,您会看到包含对单元格地址的引用的标记 - 因此,在给定了单元格地址的情况下,您可以找到所需内容。

  • 若要查找您所需的单元格,请先查找包含该单元格的工作表。

无法使用 Productivity Tool 来查看对命名工作表的引用的检索,您必须先检查工作簿包含的所有工作表以查找与该名称匹配的项,并在给定工作表及其 ID 的情况下,检索工作表部分。该示例代码将为您解决这些问题。

Excel 仅保留一个部分来包含您在电子表格中使用的每个字符串。每个唯一的字符串仅在此表中出现一次,并且 Excel 会在此表中每个包含字符串的单元格中存储一个索引。在给定 CellValues.SharedString 的 DataType 属性的情况下,代码必须按索引检索共享字符串表部分中相应的共享字符串。图 3 显示了示例工作簿的共享字符串表的 Open XML SDK 2.0 Productivity Tool 表示形式。此示例包含两个字符串,其索引分别为 0 和 1。

图 3. 显示了示例工作表的共享字符串表的 Open XML SDK 2.0 Productivity Tool

显示共享字符串表的生产力工具
观看

观看视频

> [!VIDEO https://www.microsoft.com/zh-cn/videoplayer/embed/238bbb52-39ae-49c9-807c-966bc7296bca]

长度:00:10:46

单击以获取代码

获取代码

浏览

关于作者
Ken Getz 是 MCW Technologies 的高级顾问。他是 ASP.NET Developers Jumpstart(《ASP.NET 开发人员入门》,Addison-Wesley,2002)、Access Developer's Handbook(《Access 开发人员手册》,Sybex,2001)和 VBA Developer's Handbook, 2nd Edition(《VBA 开发人员手册第 2 版》,Sybex,2001)的合著者。