Reputation: 1640
I have template.xlsx file which I must modify by adding a few copies of one row in specific index. When I try use clone method for this operation I add a row but each row modify each other. I need to create deep clone of opemxml row object but when I try this I have an error that openxml row object are not serialized. How can I deep clone row in .xlsx file using openXML with serialization or if there is another way to deep clone openxml row object?
Upvotes: 3
Views: 8158
Reputation: 111
In my case, I needed to copy several rows at the end of the sheet. I literally needed to clone a specific range of rows, i.e. values, styles and formulas, merged cells. After spending many hours on the problem of copy and paste several rows, I finally found a solution. I was able to copy rows 18 to 26 and paste them from row 27.
Example below:
Below is the code:
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);
}
Upvotes: 4
Reputation: 1074
You can deep clone using .CloneNode(true)
on an OpenXmlElement
So if you want to duplicate a row inside a table it will look like
// suppose table an OpenXml Table and row the row you want to clone
table.Append(row.CloneNode(true));
edit : to insert it before a specific row
// suppose r is the row you want to insert it before
r.InsertBeforeSelf(row.CloneNode(true));
Upvotes: 5