如何使用 openXML 深度克隆 .xlsx 文件中的行?

How can I deep clone row in .xlsx file using openXML?

我有 template.xlsx 文件,我必须通过在特定索引中添加一行的几个副本来修改它。当我尝试使用克隆方法进行此操作时,我添加了一行,但每一行都相互修改。我需要创建 opemxml 行对象的深层克隆,但是当我尝试这样做时,出现错误,指出 openxml 行对象未序列化。我如何使用带有序列化的 openXML 深度克隆 .xlsx 文件中的行,或者是否有其他方法可以深度克隆 openxml 行对象?

您可以在 OpenXmlElement

上使用 .CloneNode(true) 进行深度克隆

所以如果你想在 table 中复制一行,它看起来像

// suppose table an OpenXml Table and row the row you want to clone
table.Append(row.CloneNode(true));

编辑: 在特定行之前插入它

// suppose r is the row you want to insert it before
r.InsertBeforeSelf(row.CloneNode(true));

在我的例子中,我需要在 sheet 的末尾复制几行。 我确实需要克隆特定范围的行,即值、样式和公式、合并的单元格。在复制和粘贴几行的问题上花了很多时间,我终于找到了解决办法。我能够复制第 18 行到第 26 行并从第 27 行粘贴它们。

示例如下:

代码如下:

public static void CopyRowRange(SpreadsheetDocument document, string sheetName,
       int srcRowFrom, int srcRowTo, int destRowFrom)
    {
        WorkbookPart workbookPart = document.WorkbookPart;
        if (srcRowTo < srcRowFrom || destRowFrom < srcRowFrom) return;
        int destRowFromBase = destRowFrom;

        WorksheetPart worksheetPart = GetWorksheetPartByName(document, sheetName);
        SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

        IList<Cell> cells = sheetData.Descendants<Cell>().Where(c =>
            GetRowIndex(c.CellReference) >= srcRowFrom &&
            GetRowIndex(c.CellReference) <= srcRowTo).ToList<Cell>();

        if (cells.Count() == 0) return;

        int copiedRowCount = srcRowTo - srcRowFrom + 1;

        MoveRowIndex(document, sheetName, destRowFrom - 1, srcRowTo, srcRowFrom);

        IDictionary<int, IList<Cell>> clonedCells = null;

        IList<Cell> formulaCells = new List<Cell>();

        IList<Row> cloneRelatedRows = new List<Row>();

        destRowFrom = destRowFromBase;
        int changedRowsCount = destRowFrom - srcRowFrom;

        formulaCells.Clear();

        clonedCells = new Dictionary<int, IList<Cell>>();

        foreach (Cell cell in cells)
        {
            Cell newCell = (Cell)cell.CloneNode(true);
            int index = Convert.ToInt32(GetRowIndex(cell.CellReference));

            int rowIndex = index - changedRowsCount;
            newCell.CellReference = GetColumnName(cell.CellReference) + rowIndex.ToString();

            IList<Cell> rowCells = null;
            if (clonedCells.ContainsKey(rowIndex))
                rowCells = clonedCells[rowIndex];
            else
            {
                rowCells = new List<Cell>();
                clonedCells.Add(rowIndex, rowCells);
            }
            rowCells.Add(newCell);

            if (newCell.CellFormula != null && newCell.CellFormula.Text.Length > 0)
            {
                formulaCells.Add(newCell);
            }
        }

        foreach (int rowIndex in clonedCells.Keys)
        {
            Row row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).FirstOrDefault();
            if (row == null)
            {
                row = new Row() { RowIndex = (uint)rowIndex };

                Row refRow = sheetData.Elements<Row>().Where(r => r.RowIndex > rowIndex).OrderBy(r => r.RowIndex).FirstOrDefault();
                if (refRow == null)
                    sheetData.AppendChild<Row>(row);
                else
                    sheetData.InsertBefore<Row>(row, refRow);
            }
            row.Append(clonedCells[rowIndex].ToArray());

            cloneRelatedRows.Add(row);
        }

        ChangeFormulaRowNumber(worksheetPart.Worksheet, formulaCells, changedRowsCount);

        foreach (Row row in cloneRelatedRows)
        {
            IList<Cell> cs = row.Elements<Cell>().OrderBy(c => c.CellReference.Value).ToList<Cell>();
            row.RemoveAllChildren();
            row.Append(cs.ToArray());
        }

        MergeCells mcells = worksheetPart.Worksheet.GetFirstChild<MergeCells>();
        if (mcells != null)
        {
            IList<MergeCell> newMergeCells = new List<MergeCell>();
            IEnumerable<MergeCell> clonedMergeCells = mcells.Elements<MergeCell>().
                Where(m => MergeCellInRange(m, srcRowFrom, srcRowTo)).ToList<MergeCell>();
            foreach (MergeCell cmCell in clonedMergeCells)
            {
                MergeCell newMergeCell = CreateChangedRowMergeCell(worksheetPart.Worksheet, cmCell, changedRowsCount);
                newMergeCells.Add(newMergeCell);
            }
            uint count = mcells.Count.Value;
            mcells.Count = new UInt32Value(count + (uint)newMergeCells.Count);
            mcells.Append(newMergeCells.ToArray());
        }
    }

    private static WorksheetPart
       GetWorksheetPartByName(SpreadsheetDocument document,
       string sheetName)
    {
        IEnumerable<Sheet> sheets =
            document.WorkbookPart.Workbook.GetFirstChild<Sheets>().
            Elements<Sheet>().Where(s => s.Name == sheetName);

        if (sheets.Count() == 0)
        {
            return null;
        }

        string relationshipId = sheets.First().Id.Value;
        WorksheetPart worksheetPart = (WorksheetPart)
             document.WorkbookPart.GetPartById(relationshipId);
        return worksheetPart;

    }
    private static void MoveRowIndex(SpreadsheetDocument document, string sheetName, int destRowFrom, int srcRowTo, int srcRowFrom)
    {
        WorksheetPart worksheetPart = GetWorksheetPartByName(document, sheetName);
        SheetData sheetData = worksheetPart.Worksheet.GetFirstChild<SheetData>();

        uint newRowIndex;

        IEnumerable<Row> rows = sheetData.Descendants<Row>().Where(r => r.RowIndex.Value >= srcRowFrom && r.RowIndex.Value <= srcRowTo);
        foreach (Row row in rows)
        {
            newRowIndex = Convert.ToUInt32(destRowFrom + 1);

            foreach (Cell cell in row.Elements<Cell>())
            {
                string cellReference = cell.CellReference.Value;
                cell.CellReference = new StringValue(cellReference.Replace(row.RowIndex.Value.ToString(), newRowIndex.ToString()));
            }
            row.RowIndex = new UInt32Value(newRowIndex);

            destRowFrom++;
        }

    }

    private static void ChangeFormulaRowNumber(Worksheet worksheet, IList<Cell> formulaCells, int changedRowsCount)
    {
        foreach (Cell formulaCell in formulaCells)
        {
            Regex regex = new Regex(@"\d+");
            var rowIndex = Convert.ToInt32(regex.Match(formulaCell.CellReference).Value);

            Regex regex2 = new Regex("[A-Za-z]+");
            var columnIndex = regex2.Match(formulaCell.CellReference).Value;

            int newRowIndex = rowIndex + changedRowsCount;
            Cell cell = GetCell(worksheet, columnIndex, newRowIndex);
            cell.CellFormula = new CellFormula(cell.CellFormula.Text.Replace($"{rowIndex}",$"{newRowIndex}"));
        }
    }

    private static MergeCell CreateChangedRowMergeCell(Worksheet worksheet, MergeCell cmCell, int changedRows)
    {
        string[] cells = cmCell.Reference.Value.Split(':', 2, StringSplitOptions.RemoveEmptyEntries);

        Regex regex = new Regex(@"\d+");
        var rowIndex1 = Convert.ToInt32(regex.Match(cells[0]).Value);
        var rowIndex2 = Convert.ToInt32(regex.Match(cells[1]).Value);

        Regex regex2 = new Regex("[A-Za-z]+");
        var columnIndex1 = regex2.Match(cells[0]).Value;
        var columnIndex2 = regex2.Match(cells[1]).Value;

        var cell1Name = $"{columnIndex1}{rowIndex1 + changedRows}";
        var cell2Name = $"{columnIndex2}{rowIndex2 + changedRows}";

        CreateSpreadsheetCellIfNotExist(worksheet, cell1Name);
        CreateSpreadsheetCellIfNotExist(worksheet, cell2Name);

        return new MergeCell() { Reference = new StringValue(cell1Name + ":" + cell2Name) };
    }

    private static bool MergeCellInRange(MergeCell mergeCell, int srcRowFrom, int srcRowTo)
    {
        string[] cells = mergeCell.Reference.Value.Split(':', 2, StringSplitOptions.RemoveEmptyEntries);

        Regex regex = new Regex(@"\d+");
        var cellIndex1 = Convert.ToInt32(regex.Match(cells[0]).Value);
        var cellIndex2 = Convert.ToInt32(regex.Match(cells[1]).Value);

        if (srcRowFrom <= cellIndex1 && cellIndex1 <= srcRowTo &&
            srcRowFrom <= cellIndex2 && cellIndex2 <= srcRowTo)
            return true;
        else
            return false;
    }

    private static uint GetRowIndex(string cellName)
    {
        Regex regex = new Regex(@"\d+");
        Match match = regex.Match(cellName);

        return uint.Parse(match.Value);
    }

    private static string GetColumnName(string cellName)
    {
        Regex regex = new Regex("[A-Za-z]+");
        Match match = regex.Match(cellName);

        return match.Value;
    }

    private static void CreateSpreadsheetCellIfNotExist(Worksheet worksheet, string cellName)
    {
        string columnName = GetColumnName(cellName);
        uint rowIndex = GetRowIndex(cellName);

        IEnumerable<Row> rows = worksheet.Descendants<Row>().Where(r => r.RowIndex.Value == rowIndex);

        if (rows.Count() == 0)
        {
            Row row = new Row() { RowIndex = new UInt32Value(rowIndex) };
            Cell cell = new Cell() { CellReference = new StringValue(cellName) };
            row.Append(cell);
            worksheet.Descendants<SheetData>().First().Append(row);
            worksheet.Save();
        }
        else
        {
            Row row = rows.First();

            IEnumerable<Cell> cells = row.Elements<Cell>().Where(c => c.CellReference.Value == cellName);

            if (cells.Count() == 0)
            {
                Cell cell = new Cell() { CellReference = new StringValue(cellName) };
                row.Append(cell);
                worksheet.Save();
            }
        }
    }

    private static Cell GetCell(Worksheet worksheet, string columnIndex, int newRowIndex)
    {
        string cellName = columnIndex + newRowIndex;
        Row row = worksheet.GetFirstChild<SheetData>().Descendants<Row>().FirstOrDefault(r => r.RowIndex.Value == newRowIndex);
        return row.Elements<Cell>().FirstOrDefault(c => c.CellReference.Value == cellName);
    }