如何在不创建损坏文件的情况下插入 Excel 个单元格?

How do I insert Excel cells without creating a corrupt file?

我正在使用 OpenXML SDK 更新 Excel 电子表格的内容。将单元格插入 Excel 行时,必须以正确的顺序插入,否则文件将无法在 Excel 中正确打开。我正在使用以下代码查找将在我插入的单元格之后的第一个单元格。此代码几乎直接来自 OpenXML SDK documentation

public static Cell GetFirstFollowingCell(Row row, string newCellReference)
{
    Cell refCell = null;
    foreach (Cell cell in row.Elements<Cell>())
    {
        if (string.Compare(cell.CellReference.Value, newCellReference, true) > 0)
        {
            refCell = cell;
            break;
        }
    }

    return refCell;
}

当我使用此代码编辑文件然后在 Excel 中打开它们时,Excel 报告文件已损坏。 Excel 能够修复文件,但大部分数据已从工作簿中删除。为什么这会导致文件损坏?

旁注:在转向痛苦的低级 OpenXML SDK 之前,我尝试了两个不同的 .NET Excel 库。 NPOI 创建了损坏的电子表格,每当我尝试保存时,EPPlus 都会抛出异常。我使用的是每个的最新版本。

您使用的代码存在严重缺陷。这是非常不幸的,因为它来自文档。对于仅使用前 26 列的电子表格,它可能可以正常工作,但在遇到 "wider" 电子表格时会惨败。前 26 列按字母顺序命名,A-Z。第 27-52 列命名为 AA-AZ。第 53-78 列命名为 BA-BZ。 (您应该注意到这种模式。)

单元格 "AA1" 应该 所有具有单个字符列名称的单元格之后(即 "A1" - "Z1")。让我们检查比较单元格 "AA1" 和单元格 "B1" 的当前代码。

  1. string.Compare("B1", "AA1", true)returns值1
  2. 代码将其解释为 "AA1" 应该放在 单元格 之前 "B1"。
  3. 调用代码会在XML"B1"之前插入"AA1"

此时单元格将乱序并且 Excel 文件已损坏。显然,string.Compare 本身不足以确定一行中单元格的正确顺序。需要更复杂的比较。

public static bool IsNewCellAfterCurrentCell(string currentCellReference, string newCellReference)
{
    var columnNameRegex = new Regex("[A-Za-z]+");
    var currentCellColumn = columnNameRegex.Match(currentCellReference).Value;
    var newCellColumn = columnNameRegex.Match(newCellReference).Value;
    var currentCellColumnLength = currentCellColumn.Length;
    var newCellColumnLength = newCellColumn.Length;
    if (currentCellColumnLength == newCellColumnLength)
    {
        var comparisonValue = string.Compare(currentCellColumn, newCellColumn, StringComparison.OrdinalIgnoreCase);
        return comparisonValue > 0;
    }

    return currentCellColumnLength < newCellColumnLength;
}

如果您想在列 "BC" 中放置一个新单元格,并且您要与单元格 "D5" 进行比较,您可以使用 IsCellAfterColumn("D5", "BC5")。将新的比较函数代入原代码并用 LINQ 简化:

public static Cell GetFirstFollowingCell(Row row, string newCellReference)
{
    var rowCells = row.Elements<Cell>();
    return rowCells.FirstOrDefault(c => IsNewCellAfterCurrentCell(c.CellReference.Value, newCellReference));
}