diff --git a/ClosedXML/Excel/Tables/IXLTable.cs b/ClosedXML/Excel/Tables/IXLTable.cs index 0f714ef..e6c2912 100644 --- a/ClosedXML/Excel/Tables/IXLTable.cs +++ b/ClosedXML/Excel/Tables/IXLTable.cs @@ -36,73 +36,85 @@ /// Appends the IEnumerable data elements and returns the range of the new rows. /// /// The IEnumerable data. + /// if set to true propagate extra columns' values and formulas. /// /// The range of the new rows. /// - IXLRange AppendData(IEnumerable data); + IXLRange AppendData(IEnumerable data, Boolean propagateExtraColumns = false); /// /// Appends the IEnumerable data elements and returns the range of the new rows. /// /// The IEnumerable data. /// if set to true the data will be transposed before inserting. + /// if set to true propagate extra columns' values and formulas. /// /// The range of the new rows. /// - IXLRange AppendData(IEnumerable data, Boolean transpose); + IXLRange AppendData(IEnumerable data, Boolean transpose, Boolean propagateExtraColumns = false); /// /// Appends the data of a data table and returns the range of the new rows. /// /// The data table. + /// if set to true propagate extra columns' values and formulas. /// /// The range of the new rows. /// - IXLRange AppendData(DataTable dataTable); + IXLRange AppendData(DataTable dataTable, Boolean propagateExtraColumns = false); /// /// Appends the IEnumerable data elements and returns the range of the new rows. /// /// /// The table data. + /// if set to true propagate extra columns' values and formulas. /// /// The range of the new rows. /// - IXLRange AppendData(IEnumerable data); + IXLRange AppendData(IEnumerable data, Boolean propagateExtraColumns = false); /// /// Replaces the IEnumerable data elements and returns the table's data range. /// /// The IEnumerable data. - /// The table's data range. - IXLRange ReplaceData(IEnumerable data); + /// if set to true propagate extra columns' values and formulas. + /// + /// The table's data range. + /// + IXLRange ReplaceData(IEnumerable data, Boolean propagateExtraColumns = false); /// /// Replaces the IEnumerable data elements and returns the table's data range. /// /// The IEnumerable data. /// if set to true the data will be transposed before inserting. + /// if set to true propagate extra columns' values and formulas. /// /// The table's data range. /// - IXLRange ReplaceData(IEnumerable data, Boolean transpose); + IXLRange ReplaceData(IEnumerable data, Boolean transpose, Boolean propagateExtraColumns = false); /// /// Replaces the data from the records of a data table and returns the table's data range. /// /// The data table. + /// if set to true propagate extra columns' values and formulas. /// /// The table's data range. /// - IXLRange ReplaceData(DataTable dataTable); + IXLRange ReplaceData(DataTable dataTable, Boolean propagateExtraColumns = false); /// /// Replaces the IEnumerable data elements as a table and the table's data range. /// /// /// The table data. - /// The table's data range. - IXLRange ReplaceData(IEnumerable data); + /// if set to true propagate extra columns' values and formulas. + /// + /// The table's data range. + /// + IXLRange ReplaceData(IEnumerable data, Boolean propagateExtraColumns = false); /// /// Resizes the table to the specified range address. diff --git a/ClosedXML/Excel/Tables/XLTable.cs b/ClosedXML/Excel/Tables/XLTable.cs index aa0024d..1739063 100644 --- a/ClosedXML/Excel/Tables/XLTable.cs +++ b/ClosedXML/Excel/Tables/XLTable.cs @@ -872,12 +872,12 @@ #region Append and replace data - public IXLRange AppendData(IEnumerable data) + public IXLRange AppendData(IEnumerable data, Boolean propagateExtraColumns = false) { - return AppendData(data, false); + return AppendData(data, transpose: false, propagateExtraColumns: propagateExtraColumns); } - public IXLRange AppendData(IEnumerable data, bool transpose) + public IXLRange AppendData(IEnumerable data, bool transpose, Boolean propagateExtraColumns = false) { var castedData = data?.Cast(); if (!(castedData?.Any() ?? false) || data is String) @@ -887,16 +887,21 @@ var lastRowOfOldRange = this.DataRange.LastRow(); lastRowOfOldRange.InsertRowsBelow(numberOfNewRows); + this.Fields.Cast().ForEach(f => f.Column = null); - return lastRowOfOldRange.RowBelow().FirstCell().InsertData(castedData, transpose); + var insertedRange = lastRowOfOldRange.RowBelow().FirstCell().InsertData(castedData, transpose); + + PropagateExtraColumns(insertedRange.ColumnCount(), lastRowOfOldRange.RowNumber()); + + return insertedRange; } - public IXLRange AppendData(DataTable dataTable) + public IXLRange AppendData(DataTable dataTable, Boolean propagateExtraColumns = false) { - return AppendData(dataTable.Rows.Cast()); + return AppendData(dataTable.Rows.Cast(), propagateExtraColumns: propagateExtraColumns); } - public IXLRange AppendData(IEnumerable data) + public IXLRange AppendData(IEnumerable data, Boolean propagateExtraColumns = false) { if (!(data?.Any() ?? false) || data is String) return null; @@ -908,16 +913,21 @@ var lastRowOfOldRange = this.DataRange.LastRow(); lastRowOfOldRange.InsertRowsBelow(numberOfNewRows); + this.Fields.Cast().ForEach(f => f.Column = null); - return lastRowOfOldRange.RowBelow().FirstCell().InsertData(data); + var insertedRange = lastRowOfOldRange.RowBelow().FirstCell().InsertData(data); + + PropagateExtraColumns(insertedRange.ColumnCount(), lastRowOfOldRange.RowNumber()); + + return insertedRange; } - public IXLRange ReplaceData(IEnumerable data) + public IXLRange ReplaceData(IEnumerable data, Boolean propagateExtraColumns = false) { - return ReplaceData(data, false); + return ReplaceData(data, transpose: false, propagateExtraColumns: propagateExtraColumns); } - public IXLRange ReplaceData(IEnumerable data, bool transpose) + public IXLRange ReplaceData(IEnumerable data, bool transpose, Boolean propagateExtraColumns = false) { var castedData = data?.Cast(); if (!(castedData?.Any() ?? false) || data is String) @@ -931,6 +941,7 @@ if (sizeDifference > 0) this.DataRange.LastRow().InsertRowsBelow(sizeDifference); else if (sizeDifference < 0) + { this.DataRange.Rows ( lastDataRowNumber + sizeDifference + 1 - firstDataRowNumber + 1, @@ -938,15 +949,28 @@ ) .Delete(); - return this.DataRange.FirstCell().InsertData(castedData, transpose); + // No propagation needed when reducing the number of rows + propagateExtraColumns = false; + } + + if (sizeDifference != 0) + // Invalidate table fields' columns + this.Fields.Cast().ForEach(f => f.Column = null); + + var replacedRange = this.DataRange.FirstCell().InsertData(castedData, transpose); + + if (propagateExtraColumns) + PropagateExtraColumns(replacedRange.ColumnCount(), lastDataRowNumber); + + return replacedRange; } - public IXLRange ReplaceData(DataTable dataTable) + public IXLRange ReplaceData(DataTable dataTable, Boolean propagateExtraColumns = false) { - return ReplaceData(dataTable.Rows.Cast()); + return ReplaceData(dataTable.Rows.Cast(), propagateExtraColumns: propagateExtraColumns); } - public IXLRange ReplaceData(IEnumerable data) + public IXLRange ReplaceData(IEnumerable data, Boolean propagateExtraColumns = false) { if (!(data?.Any() ?? false) || data is String) throw new InvalidOperationException("Cannot replace table data with empty enumerable."); @@ -959,6 +983,7 @@ if (sizeDifference > 0) this.DataRange.LastRow().InsertRowsBelow(sizeDifference); else if (sizeDifference < 0) + { this.DataRange.Rows ( lastDataRowNumber + sizeDifference + 1 - firstDataRowNumber + 1, @@ -966,7 +991,38 @@ ) .Delete(); - return this.DataRange.FirstCell().InsertData(data); + // No propagation needed when reducing the number of rows + propagateExtraColumns = false; + } + + if (sizeDifference != 0) + // Invalidate table fields' columns + this.Fields.Cast().ForEach(f => f.Column = null); + + var replacedRange = this.DataRange.FirstCell().InsertData(data); + + if (propagateExtraColumns) + PropagateExtraColumns(replacedRange.ColumnCount(), lastDataRowNumber); + + return replacedRange; + } + + private void PropagateExtraColumns(int numberOfNonExtraColumns, int previousLastDataRow) + { + for (var i = numberOfNonExtraColumns; i < this.Fields.Count(); i++) + { + var field = this.Field(i); + + var cell = this.Worksheet.Cell(previousLastDataRow, field.Column.ColumnNumber()); + field.Column.Cells(c => c.Address.RowNumber > previousLastDataRow) + .ForEach(c => + { + if (cell.HasFormula) + c.FormulaR1C1 = cell.FormulaR1C1; + else + c.Value = cell.Value; + }); + } } #endregion Append and replace data diff --git a/ClosedXML_Tests/Excel/Tables/AddingAndReplacingTableDataTests.cs b/ClosedXML_Tests/Excel/Tables/AddingAndReplacingTableDataTests.cs index 5dbd18d..ccae4ad 100644 --- a/ClosedXML_Tests/Excel/Tables/AddingAndReplacingTableDataTests.cs +++ b/ClosedXML_Tests/Excel/Tables/AddingAndReplacingTableDataTests.cs @@ -57,6 +57,26 @@ return wb; } + private XLWorkbook PrepareWorkbookWithAdditionalColumns() + { + var wb = PrepareWorkbook(); + var ws = wb.Worksheets.First(); + + var table = ws.Tables.First(); + table.HeadersRow() + .LastCell().CellRight() + .InsertData(new[] { "CumulativeAge", "NameLength", "IsOld", "HardCodedValue" }, transpose: true); + + table.Resize(ws.Range(table.FirstCell(), table.LastCell().CellRight(4))); + + table.Field("CumulativeAge").DataCells.ForEach(c => c.FormulaA1 = $"SUM($G$3:G{c.WorksheetRow().RowNumber()})"); + table.Field("NameLength").DataCells.ForEach(c => c.FormulaA1 = $"LEN(B{c.WorksheetRow().RowNumber()})"); + table.Field("IsOld").DataCells.ForEach(c => c.FormulaA1 = $"=G{c.WorksheetRow().RowNumber()}>=40"); + table.Field("HardCodedValue").DataCells.Value = "40 is not old!"; + + return wb; + } + private Person[] NewData { get @@ -448,5 +468,208 @@ } } } + + [Test] + public void CanReplaceWithUntypedEnumerableAndPropagateExtraColumns() + { + using (var ms = new MemoryStream()) + { + using (var wb = PrepareWorkbookWithAdditionalColumns()) + { + var ws = wb.Worksheets.First(); + var table = ws.Tables.First(); + + var list = new ArrayList(); + list.AddRange(NewData); + list.AddRange(NewData); + + var replacedRange = table.ReplaceData(list, propagateExtraColumns: true); + + Assert.AreEqual("B3:G6", replacedRange.RangeAddress.ToString()); + + ws.Columns().AdjustToContents(); + + wb.SaveAs(ms); + } + + using (var wb = new XLWorkbook(ms)) + { + var table = wb.Worksheets.SelectMany(ws => ws.Tables).First(); + + Assert.AreEqual(4, table.DataRange.RowCount()); + Assert.AreEqual(10, table.DataRange.ColumnCount()); + + Assert.AreEqual("SUM($G$3:G5)", table.Worksheet.Cell("H5").FormulaA1); + Assert.AreEqual("SUM($G$3:G6)", table.Worksheet.Cell("H6").FormulaA1); + Assert.AreEqual(100, table.Worksheet.Cell("H5").Value); + Assert.AreEqual(130, table.Worksheet.Cell("H6").Value); + + Assert.AreEqual("LEN(B5)", table.Worksheet.Cell("I5").FormulaA1); + Assert.AreEqual("LEN(B6)", table.Worksheet.Cell("I6").FormulaA1); + Assert.AreEqual(16, table.Worksheet.Cell("I5").Value); + Assert.AreEqual(21, table.Worksheet.Cell("I6").Value); + + Assert.AreEqual("G5>=40", table.Worksheet.Cell("J5").FormulaA1); + Assert.AreEqual("G6>=40", table.Worksheet.Cell("J6").FormulaA1); + Assert.AreEqual(false, table.Worksheet.Cell("J5").Value); + Assert.AreEqual(false, table.Worksheet.Cell("J6").Value); + + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K5").Value); + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K6").Value); + } + } + } + + [Test] + public void CanReplaceWithTypedEnumerableAndPropagateExtraColumns() + { + using (var ms = new MemoryStream()) + { + using (var wb = PrepareWorkbookWithAdditionalColumns()) + { + var ws = wb.Worksheets.First(); + + var table = ws.Tables.First(); + + IEnumerable personEnumerable = NewData.Concat(NewData).OrderBy(p => p.Age); + var replacedRange = table.ReplaceData(personEnumerable, propagateExtraColumns: true); + + Assert.AreEqual("B3:G6", replacedRange.RangeAddress.ToString()); + ws.Columns().AdjustToContents(); + + wb.SaveAs(ms); + } + + using (var wb = new XLWorkbook(ms)) + { + var table = wb.Worksheets.SelectMany(ws => ws.Tables).First(); + + Assert.AreEqual(4, table.DataRange.RowCount()); + Assert.AreEqual(10, table.DataRange.ColumnCount()); + + Assert.AreEqual("SUM($G$3:G5)", table.Worksheet.Cell("H5").FormulaA1); + Assert.AreEqual("SUM($G$3:G6)", table.Worksheet.Cell("H6").FormulaA1); + Assert.AreEqual(95, table.Worksheet.Cell("H5").Value); + Assert.AreEqual(130, table.Worksheet.Cell("H6").Value); + + Assert.AreEqual("LEN(B5)", table.Worksheet.Cell("I5").FormulaA1); + Assert.AreEqual("LEN(B6)", table.Worksheet.Cell("I6").FormulaA1); + Assert.AreEqual(16, table.Worksheet.Cell("I5").Value); + Assert.AreEqual(16, table.Worksheet.Cell("I6").Value); + + Assert.AreEqual("G5>=40", table.Worksheet.Cell("J5").FormulaA1); + Assert.AreEqual("G6>=40", table.Worksheet.Cell("J6").FormulaA1); + Assert.AreEqual(false, table.Worksheet.Cell("J5").Value); + Assert.AreEqual(false, table.Worksheet.Cell("J6").Value); + + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K5").Value); + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K6").Value); + } + } + } + + [Test] + public void CanAppendWithUntypedEnumerableAndPropagateExtraColumns() + { + using (var ms = new MemoryStream()) + { + using (var wb = PrepareWorkbookWithAdditionalColumns()) + { + var ws = wb.Worksheets.First(); + var table = ws.Tables.First(); + + var list = new ArrayList(); + list.AddRange(NewData); + list.AddRange(NewData); + + var appendedRange = table.AppendData(list, propagateExtraColumns: true); + + Assert.AreEqual("B6:G9", appendedRange.RangeAddress.ToString()); + + ws.Columns().AdjustToContents(); + + wb.SaveAs(ms); + } + + using (var wb = new XLWorkbook(ms)) + { + var table = wb.Worksheets.SelectMany(ws => ws.Tables).First(); + + Assert.AreEqual(7, table.DataRange.RowCount()); + Assert.AreEqual(10, table.DataRange.ColumnCount()); + + Assert.AreEqual("SUM($G$3:G8)", table.Worksheet.Cell("H8").FormulaA1); + Assert.AreEqual("SUM($G$3:G9)", table.Worksheet.Cell("H9").FormulaA1); + Assert.AreEqual(220, table.Worksheet.Cell("H8").Value); + Assert.AreEqual(250, table.Worksheet.Cell("H9").Value); + + Assert.AreEqual("LEN(B8)", table.Worksheet.Cell("I8").FormulaA1); + Assert.AreEqual("LEN(B9)", table.Worksheet.Cell("I9").FormulaA1); + Assert.AreEqual(16, table.Worksheet.Cell("I8").Value); + Assert.AreEqual(21, table.Worksheet.Cell("I9").Value); + + Assert.AreEqual("G8>=40", table.Worksheet.Cell("J8").FormulaA1); + Assert.AreEqual("G9>=40", table.Worksheet.Cell("J9").FormulaA1); + Assert.AreEqual(false, table.Worksheet.Cell("J8").Value); + Assert.AreEqual(false, table.Worksheet.Cell("J9").Value); + + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K8").Value); + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K9").Value); + } + } + } + + [Test] + public void CanAppendTypedEnumerableAndPropagateExtraColumns() + { + using (var ms = new MemoryStream()) + { + using (var wb = PrepareWorkbookWithAdditionalColumns()) + { + var ws = wb.Worksheets.First(); + + var table = ws.Tables.First(); + + IEnumerable personEnumerable = + NewData + .Concat(NewData) + .Concat(NewData) + .OrderBy(p => p.FirstName); + + var addedRange = table.AppendData(personEnumerable); + + Assert.AreEqual("B6:G11", addedRange.RangeAddress.ToString()); + ws.Columns().AdjustToContents(); + + wb.SaveAs(ms); + } + + using (var wb = new XLWorkbook(ms)) + { + var table = wb.Worksheets.SelectMany(ws => ws.Tables).First(); + + Assert.AreEqual(9, table.DataRange.RowCount()); + Assert.AreEqual(10, table.DataRange.ColumnCount()); + + Assert.AreEqual("SUM($G$3:G10)", table.Worksheet.Cell("H10").FormulaA1); + Assert.AreEqual("SUM($G$3:G11)", table.Worksheet.Cell("H11").FormulaA1); + Assert.AreEqual(280, table.Worksheet.Cell("H10").Value); + Assert.AreEqual(315, table.Worksheet.Cell("H11").Value); + + Assert.AreEqual("LEN(B10)", table.Worksheet.Cell("I10").FormulaA1); + Assert.AreEqual("LEN(B11)", table.Worksheet.Cell("I11").FormulaA1); + Assert.AreEqual(16, table.Worksheet.Cell("I10").Value); + Assert.AreEqual(16, table.Worksheet.Cell("I11").Value); + + Assert.AreEqual("G10>=40", table.Worksheet.Cell("J10").FormulaA1); + Assert.AreEqual("G11>=40", table.Worksheet.Cell("J11").FormulaA1); + Assert.AreEqual(false, table.Worksheet.Cell("J10").Value); + Assert.AreEqual(false, table.Worksheet.Cell("J11").Value); + + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K10").Value); + Assert.AreEqual("40 is not old!", table.Worksheet.Cell("K11").Value); + } + } + } } }