OpenXMLExcel如何在值位于SharedStringTable中时更改单元格的值

本文关键字:单元格 OpenXMLExcel SharedStringTable | 更新日期: 2023-09-27 18:22:02

我正在寻找一种安全有效的方法来更新SharedStringTable中文本所在单元格的值(这似乎是MS Excel创建的任何电子表格的情况)。

顾名思义,SharedStringTable包含可以在多个单元格中使用的字符串。

因此,仅仅在字符串表中找到项并更新值不是一种方法,因为它可能也被其他单元格使用。

据我所知,必须做到以下几点:

  1. 检查单元格是否使用字符串表

  2. 如果是,请检查新字符串是否已经存在,在这种情况下只需使用它(如果任何其他单元格不再使用旧字符串,请记住删除该项!)

  3. 如果没有,请检查带有旧字符串的项目是否被电子表格中的任何其他单元格引用

  4. 如果是,请使用新字符串创建新项目并引用它

  5. 如果没有,只需使用新字符串更新现有项目

使用OpenXMLSDK有什么更简单的解决方案吗?

还要考虑,可能不仅要更新一个单元格,还要为几个单元格设置新的(不同的)值。所以我们可能在循环中调用update cell方法。。。

OpenXMLExcel如何在值位于SharedStringTable中时更改单元格的值

首先考虑这个问题。似乎适用于我的特殊情况。但必须有可能改进,甚至更好的是,做完全不同的事情:

private static void UpdateCell(SharedStringTable sharedStringTable, 
   Dictionary<string, SheetData> sheetDatas, string sheetName, 
   string cellReference, string text)
{
   Cell cell = sheetDatas[sheetName].Descendants<Cell>()
    .FirstOrDefault(c => c.CellReference.Value == cellReference);
   if (cell == null) return;
   if (cell.DataType == null || cell.DataType != CellValues.SharedString)
   {
    cell.RemoveAllChildren();
    cell.AppendChild(new InlineString(new Text { Text = text }));
    cell.DataType = CellValues.InlineString;
    return;
   }
   // Cell is refering to string table. Check if new text is already in string table, if so use it.
   IEnumerable<SharedStringItem> sharedStringItems 
    = sharedStringTable.Elements<SharedStringItem>();
   int i = 0;
   foreach (SharedStringItem sharedStringItem in sharedStringItems)
   {
    if (sharedStringItem.InnerText == text)
    {
       cell.CellValue = new CellValue(i.ToString());
       // TODO: Should clean up, ie remove item with old text from string table if it is no longer in use.
       return;
    }
    i++;
   }
   // New text not in string table. Check if any other cells in the Workbook referes to item with old text.
   foreach (SheetData sheetData in sheetDatas.Values)
   {
    var cells = sheetData.Descendants<Cell>();
    foreach (Cell cell0 in cells)
    {
       if (cell0.Equals(cell)) continue;
       if (cell0.DataType != null 
       && cell0.DataType == CellValues.SharedString 
       && cell0.CellValue.InnerText == cell.CellValue.InnerText)
       {
        // Other cells refer to item with old text so we cannot update it. Add new item.
        sharedStringTable.AppendChild(new SharedStringItem(new Text(text)));
        cell.CellValue.Text = (i).ToString();
        return;
       }
    }
   }
   // No other cells refered to old item. Update it.
   sharedStringItems.ElementAt(int.Parse(cell.CellValue.InnerText)).Text = new Text(text);
}

private static void DoIt(string filePath)
{
   using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(filePath, true))
   {
    SharedStringTable sharedStringTable 
       = spreadSheet.WorkbookPart.GetPartsOfType<SharedStringTablePart>()
        .First().SharedStringTable;
    Dictionary<string, SheetData> sheetDatas = new Dictionary<string, SheetData>();
    foreach (var sheet in spreadSheet.WorkbookPart.Workbook.Descendants<Sheet>())
    {
       SheetData sheetData 
        = (spreadSheet.WorkbookPart.GetPartById(sheet.Id) as WorksheetPart)
           .Worksheet.GetFirstChild<SheetData>();
       sheetDatas.Add(sheet.Name, sheetData);
    }
    UpdateCell(sharedStringTable, sheetDatas, "Sheet1", "A2", "Mjau");
   }
}

警告:不要按原样使用以上内容,它适用于特定的电子表格。如果一个人在其他情况下使用它,很可能事情没有得到处理。这是我第一次尝试为电子表格使用OpenXML。最后听从了乔治·波列沃伊的建议。更容易,而且似乎没有不良副作用(也就是说,在操作电子表格时还有一百万个其他问题需要处理,这些问题可能会在您的控制范围之外编辑…)

正如您所看到的,共享字符串表的更新操作确实让开发人员忙得不可开交。

根据我的经验,共享字符串表在性能和文件大小经济性方面并没有添加任何内容。OpenXml格式无论如何都是在打包容器中压缩的,所以即使您有大量重复的字符串,它也不会影响文件大小。

Microsoft Excel在共享字符串表中写入所有内容,即使没有重复。

我建议在修改文档之前将所有内容转换为InlineStrings,这样进一步的操作就会变得非常简单。

您可以简单地将其写成InlineStrings,这将是一个功能相同的文档文件。

当文件被编辑时,Microsoft Excel会将其转换回共享字符串表,但谁在乎呢。

我建议在标准的未来版本中删除共享字符串表功能,除非有一些合理的基准。