【问题标题】:OpenXML SDK returning a number for CellValue instead of cells textOpenXML SDK 返回 CellValue 的数字而不是单元格文本
【发布时间】:2011-07-04 04:03:39
【问题描述】:

我正在使用 Open XML SDK 打开 Excel xlsx 文件,并尝试读取每张工作表中位置 A1 上的单元格值。 我使用以下代码:

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(openFileDialog1.FileName, false))
{
    var sheets = spreadsheetDocument.WorkbookPart.Workbook.Descendants<Sheet>();

    foreach (Sheet sheet in sheets)
    {
        WorksheetPart worksheetPart = (WorksheetPart)spreadsheetDocument.WorkbookPart.GetPartById(sheet.Id);
        Worksheet worksheet = worksheetPart.Worksheet;

        Cell cell = GetCell(worksheet, "A", 1);

        Console.Writeline(cell.CellValue.Text);
     }
}

private static Cell GetCell(Worksheet worksheet, string columnName, uint rowIndex)
{
     Row row = GetRow(worksheet, rowIndex);

     if (row == null)
         return null;

     return row.Elements<Cell>().Where(c => string.Compare
               (c.CellReference.Value, columnName +
               rowIndex, true) == 0).First();
}

// Given a worksheet and a row index, return the row.
private static Row GetRow(Worksheet worksheet, uint rowIndex)
{
    return worksheet.GetFirstChild<SheetData>().
          Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
} 

位置 A1 的第一个工作表中的文本只是“测试”,但是在我的控制台中,我将值“0”视为 cell.CellValue.Text

有没有人知道获取单元格的正确值?

【问题讨论】:

    标签: c# openxml openxml-sdk


    【解决方案1】:

    Excel 工作表中的所有字符串都存储在名为 SharedStringTable 的类似数组的结构中。此表的目标是将所有字符串集中在基于索引的数组中,然后如果该字符串在文档中多次使用,则仅引用此数组中的索引。话虽如此,当您获得 A1 单元格的文本值时收到的 0 是 SharedStringTable 的索引。要获得真正的价值,你可以使用这个辅助函数:

    public static SharedStringItem GetSharedStringItemById(WorkbookPart workbookPart, int id)
    {
        return workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id);
    }
    

    然后在你的代码中这样调用它以获得真正的价值:

    Cell cell = GetCell(worksheet, "A", 1);
    
    string cellValue = string.Empty;
    
    if (cell.DataType != null)
    {
        if (cell.DataType == CellValues.SharedString)
        {
           int id = -1;
    
           if (Int32.TryParse(cell.InnerText, out id))
           {
               SharedStringItem item = GetSharedStringItemById(workbookPart, id);
    
               if (item.Text != null)
               {
                   cellValue = item.Text.Text;
               }
               else if (item.InnerText != null)
               {
                   cellValue = item.InnerText;
               }
               else if (item.InnerXml != null)
               {
                   cellValue = item.InnerXml;
               }
           }
        }
    }
    

    【讨论】:

    • 这是正确的,但并未解决所有需要的问题。在 SST 中查找单元格值之前,您需要实际确定该单元格值是代表 SST 索引还是实际上是一个值。
    • @Samuel Neff - 默认情况下,Excel 会将所有基本字符串放入 SST,在这个问题中,他只关心获取这个基本字符串值。无需将基本场景过度复杂化。如果他正在处理公式或其他数据,那么显然上面的代码需要更改以包含您的评论。
    • 在这一点上倾向于同意 amurra - OP 只是要求基本价值。通过这些 cmets,他现在知道他可能需要考虑其他因素这一事实使得答案足以回答所提出的问题。其他问题(例如公式)可以在不同的问题中提出。
    • 我添加此评论是因为确定单元格值是否代表 SST 索引的实际解决方案由于某种原因从未发布(非常烦人): if (cell.DataType != null && cell. DataType == CellValues.SharedString)
    • Samuel 和 genki 是正确的。如果有一个日期或其他值不是共享字符串,则此代码将阻塞尝试从 id 获取 SI,其中 id 已经是一个值,而不是实际的 si 标识
    【解决方案2】:

    Amurra 的回答似乎完成了百分之九十,但可能需要一些细微差别。

    1) 函数“GetSharedStringItemById”返回一个SharedStringItem,而不是一个字符串,这样调用代码示例将不起作用。要获取字符串的实际值,相信需要询问SharedStringItem的InnerText属性,如下:

    public static string GetSharedStringItemById(WorkbookPart workbookPart, int id)
    {
        return workbookPart.SharedStringTablePart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id).InnerText;
    }
    

    2) 该函数还(正确地)要求将 int 作为其签名的一部分,但示例代码调用提供了一个字符串 cell.CellValue.Text。将字符串转换为 int 很简单,但需要完成,因为编写的代码可能会令人困惑。

    【讨论】:

      【解决方案3】:

      很久以前发现这个很有用的sn-p,所以不能指望作者。

      private static string GetCellValue(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;
          }
      

      【讨论】:

      • 这段代码太慢了,加载超过 5x5 的表格是不切实际的。添加一行大约需要 200 毫秒!
      • 这很可能是因为它每次都打开文件。 :)
      【解决方案4】:

      我发现this 将整个 excel 数据作为数据表读取的帖子非常有帮助。它还使用 open-xml sdk。

      using System;
      using System.Data;
      using System.Linq;
      using DocumentFormat.OpenXml.Packaging;
      using DocumentFormat.OpenXml.Spreadsheet;
      
      public static DataTable ReadAsDataTable(string fileName)
      {
          DataTable dataTable = new DataTable();
          using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(fileName, false))
          {
              WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;
              IEnumerable<Sheet> sheets = spreadSheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>().Elements<Sheet>();
              string relationshipId = sheets.First().Id.Value;
              WorksheetPart worksheetPart = (WorksheetPart)spreadSheetDocument.WorkbookPart.GetPartById(relationshipId);
              Worksheet workSheet = worksheetPart.Worksheet;
              SheetData sheetData = workSheet.GetFirstChild<SheetData>();
              IEnumerable<Row> rows = sheetData.Descendants<Row>();
      
              foreach (Cell cell in rows.ElementAt(0))
              {
                  dataTable.Columns.Add(GetCellValue(spreadSheetDocument, cell));
              }
      
              foreach (Row row in rows)
              {
                  DataRow dataRow = dataTable.NewRow();
                  for (int i = 0; i < row.Descendants<Cell>().Count(); i++)
                  {
                      dataRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));
                  }
      
                  dataTable.Rows.Add(dataRow);
              }
      
          }
          dataTable.Rows.RemoveAt(0);
      
          return dataTable;
      }
      
      private static string GetCellValue(SpreadsheetDocument document, Cell cell)
      {
          SharedStringTablePart stringTablePart = document.WorkbookPart.SharedStringTablePart;
          string value = cell.CellValue.InnerXml;
      
          if (cell.DataType != null && cell.DataType.Value == CellValues.SharedString)
          {
              return stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
          }
          else
          {
              return value;
          }
      }
      

      注意:存在读取 excel 时会忽略每行中的空单元格的问题。因此,当您确定每行中的每个单元格都有一些数据时,此代码最适合。如果您想对其进行适当的处​​理,则可以执行以下操作:

      更改for循环的代码:

      dataRow[i] = GetCellValue(spreadSheetDocument, row.Descendants<Cell>().ElementAt(i));
      

      Cell cell = row.Descendants<Cell>().ElementAt(i);
      int actualCellIndex = CellReferenceToIndex(cell);
      dataRow[actualCellIndex] = GetCellValue(spreadSheetDocument, cell);
      

      并添加上面修改后的代码sn-p中使用的方法:

      private static int CellReferenceToIndex(Cell cell)
      {
          int index = 0;
          string reference = cell.CellReference.ToString().ToUpper();
          foreach (char ch in reference)
          {
              if (Char.IsLetter(ch))
              {
                  int value = (int)ch - (int)'A';
                  index = (index == 0) ? value : ((index + 1) * 26) + value;
              }
              else
                  return index;
          }
          return index;
      }
      

      我从this 回答中得到了这个修复。

      【讨论】:

        【解决方案5】:

        另一种选择:将数据导出到 html 表格并利用样式表指定只读单元格。有关更多信息,请参阅此页面: http://www.c-sharpcorner.com/UploadFile/kaushikborah28/79Nick08302007171404PM/79Nick.aspx

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-02
          • 1970-01-01
          • 1970-01-01
          • 2016-03-07
          • 1970-01-01
          相关资源
          最近更新 更多