diff --git a/samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx b/samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx new file mode 100644 index 0000000..20d6469 Binary files /dev/null and b/samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx differ diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs index c58403b..9efbe65 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs @@ -1,6 +1,5 @@ using MiniExcelLibs.Attributes; using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; using System; using System.Collections; using System.Collections.Generic; @@ -27,6 +26,7 @@ namespace MiniExcelLibs.OpenXml public bool IsDictionary { get; set; } public bool IsDataTable { get; set; } public int CellIEnumerableValuesCount { get; set; } + public IList CellIlListValues { get; set; } public IEnumerable CellIEnumerableValues { get; set; } public XMergeCell IEnumerableMercell { get; set; } public List RowMercells { get; set; } @@ -145,13 +145,87 @@ namespace MiniExcelLibs.OpenXml int originRowIndex; int rowIndexDiff = 0; var rowXml = new StringBuilder(); - foreach (var rowInfo in XRowInfos) + + // for grouped cells + bool groupingStarted = false; + bool hasEverGroupStarted = false; + int groupStartRowIndex = 0; + IList cellIEnumerableValues = null; + bool isCellIEnumerableValuesSet = false; + int cellIEnumerableValuesIndex = 0; + int groupRowCount = 0; + int headerDiff = 0; + bool isFirstRound = true; + string currentHeader = ""; + string prevHeader = ""; + bool isHeaderRow = false; + string headerText = ""; + + for (int rowNo = 0; rowNo < XRowInfos.Count; rowNo++) { + isHeaderRow = false; + currentHeader = ""; + + var rowInfo = XRowInfos[rowNo]; var row = rowInfo.Row; + if (row.InnerText.Contains("@group")) + { + groupingStarted = true; + hasEverGroupStarted = true; + groupStartRowIndex = rowNo; + isFirstRound = true; + prevHeader = ""; + continue; + } + else if (row.InnerText.Contains("@endgroup")) + { + if(cellIEnumerableValuesIndex >= cellIEnumerableValues.Count - 1) + { + groupingStarted = false; + groupStartRowIndex = 0; + cellIEnumerableValues = null; + isCellIEnumerableValuesSet = false; + headerDiff++; + continue; + } + rowNo = groupStartRowIndex; + cellIEnumerableValuesIndex++; + isFirstRound = false; + continue; + } + else if (row.InnerText.Contains("@header")) + { + isHeaderRow = true; + } + + if (groupingStarted && !isCellIEnumerableValuesSet && rowInfo.CellIlListValues != null) + { + cellIEnumerableValues = rowInfo.CellIlListValues; + isCellIEnumerableValuesSet = true; + } + + var groupingRowDiff = + (hasEverGroupStarted ? (-1 + cellIEnumerableValuesIndex * groupRowCount - headerDiff) : 0); + + if (groupingStarted) + { + if (isFirstRound) + { + groupRowCount++; + } + + if(cellIEnumerableValues != null) + { + rowInfo.CellIEnumerableValuesCount = 1; + rowInfo.CellIEnumerableValues = + cellIEnumerableValues.Skip(cellIEnumerableValuesIndex).Take(1).ToList(); + } + } + //TODO: some xlsx without r originRowIndex = int.Parse(row.GetAttribute("r")); - var newRowIndex = originRowIndex + rowIndexDiff; + var newRowIndex = originRowIndex + rowIndexDiff + groupingRowDiff; string innerXml = row.InnerXml; rowXml.Clear() @@ -212,7 +286,14 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) + + rowXml.Replace("@header" + key, cellValueStr); rowXml.Replace(key, cellValueStr); + + if(isHeaderRow && row.InnerText.Contains(key)) + { + currentHeader += cellValueStr; + } } } else if (rowInfo.IsDataTable) @@ -247,7 +328,14 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) + + rowXml.Replace("@header" + key, cellValueStr); rowXml.Replace(key, cellValueStr); + + if(isHeaderRow && row.InnerText.Contains(key)) + { + currentHeader += cellValueStr; + } } } else @@ -269,8 +357,7 @@ namespace MiniExcelLibs.OpenXml rowXml.Replace(key, ""); continue; } - - + var cellValueStr = ExcelOpenXmlUtils.EncodeXML(cellValue?.ToString()); var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; if (type == typeof(bool)) @@ -288,7 +375,27 @@ namespace MiniExcelLibs.OpenXml } //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) + + rowXml.Replace("@header" + key, cellValueStr); rowXml.Replace(key, cellValueStr); + + if(isHeaderRow && row.InnerText.Contains(key)) + { + currentHeader += cellValueStr; + } + } + } + + if (isHeaderRow) + { + if(currentHeader == prevHeader) + { + headerDiff++; + continue; + } + else + { + prevHeader = currentHeader; } } @@ -308,8 +415,8 @@ namespace MiniExcelLibs.OpenXml foreach (var mergeCell in rowInfo.RowMercells) { var newMergeCell = new XMergeCell(mergeCell); - newMergeCell.Y1 = newMergeCell.Y1 + rowIndexDiff; - newMergeCell.Y2 = newMergeCell.Y2 + rowIndexDiff; + newMergeCell.Y1 = newMergeCell.Y1 + rowIndexDiff + groupingRowDiff; + newMergeCell.Y2 = newMergeCell.Y2 + rowIndexDiff + groupingRowDiff; this.NewXMergeCellInfos.Add(newMergeCell); } @@ -361,8 +468,8 @@ namespace MiniExcelLibs.OpenXml foreach (var mergeCell in rowInfo.RowMercells) { var newMergeCell = new XMergeCell(mergeCell); - newMergeCell.Y1 = newMergeCell.Y1 + rowIndexDiff; - newMergeCell.Y2 = newMergeCell.Y2 + rowIndexDiff; + newMergeCell.Y1 = newMergeCell.Y1 + rowIndexDiff + groupingRowDiff; + newMergeCell.Y2 = newMergeCell.Y2 + rowIndexDiff + groupingRowDiff; this.NewXMergeCellInfos.Add(newMergeCell); } } @@ -514,7 +621,7 @@ namespace MiniExcelLibs.OpenXml } var cellValue = inputMaps[propNames[0]]; // 1. From left to right, only the first set is used as the basis for the list - if (cellValue is IEnumerable && !(cellValue is string)) + if ((cellValue is IEnumerable || cellValue is IList) && !(cellValue is string)) { if (this.XMergeCellInfos.ContainsKey(r)) { @@ -523,8 +630,10 @@ namespace MiniExcelLibs.OpenXml xRowInfo.IEnumerableMercell = this.XMergeCellInfos[r]; } } - + xRowInfo.CellIEnumerableValues = cellValue as IEnumerable; + xRowInfo.CellIlListValues = cellValue as IList; + // get ienumerable runtime type if (xRowInfo.IEnumerableGenricType == null) //avoid duplicate to add rowindexdiff ![image](https://user-images.githubusercontent.com/12729184/114851348-522ac000-9e14-11eb-8244-4730754d6885.png) { @@ -604,7 +713,8 @@ namespace MiniExcelLibs.OpenXml xRowInfo.IEnumerablePropName = propNames[0]; xRowInfo.IEnumerableGenricType = typeof(DataRow); xRowInfo.IsDataTable = true; - xRowInfo.CellIEnumerableValues = dt.Rows.Cast(); //TODO: need to optimize performance + xRowInfo.CellIEnumerableValues = dt.Rows.Cast().ToList(); //TODO: need to optimize performance + xRowInfo.CellIlListValues = dt.Rows.Cast().ToList(); var first = true; foreach (var element in xRowInfo.CellIEnumerableValues) { diff --git a/tests/MiniExcelTests/MiniExcelTemplateAsyncTests.cs b/tests/MiniExcelTests/MiniExcelTemplateAsyncTests.cs index d5b37ad..e911f0a 100644 --- a/tests/MiniExcelTests/MiniExcelTemplateAsyncTests.cs +++ b/tests/MiniExcelTests/MiniExcelTemplateAsyncTests.cs @@ -549,7 +549,7 @@ namespace MiniExcelTests { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; - + //1. By POCO var value = new { @@ -563,7 +563,7 @@ namespace MiniExcelTests } }; await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); - + var demension = Helpers.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:B7", demension); } @@ -617,6 +617,80 @@ namespace MiniExcelTests } } + [Fact] + public async Task TestIEnumerableGrouped() + { + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx"; + + //1. By POCO + var value = new + { + employees = new[] { + new {name="Jack",department="HR"}, + new {name="Lisa",department="HR"}, + new {name="John",department="HR"}, + new {name="Mike",department="IT"}, + new {name="Neo",department="IT"}, + new {name="Loan",department="IT"} + } + }; + await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:B15", demension); + } + + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx"; + + //2. By Dictionary + var value = new Dictionary() + { + ["employees"] = new[] { + new {name="Jack",department="HR"}, + new {name="Jack",department="HR"}, + new {name="John",department="HR"}, + new {name="John",department="IT"}, + new {name="Neo",department="IT"}, + new {name="Loan",department="IT"} + } + }; + await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:B15", demension); + } + + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx"; + + //3. By DataTable + var dt = new DataTable(); + { + dt.Columns.Add("name"); + dt.Columns.Add("department"); + dt.Rows.Add("Jack", "HR"); + dt.Rows.Add("Lisa", "HR"); + dt.Rows.Add("John", "HR"); + dt.Rows.Add("Mike", "IT"); + dt.Rows.Add("Neo", "IT"); + dt.Rows.Add("Loan", "IT"); + } + var value = new Dictionary() + { + ["employees"] = dt + }; + await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:B15", demension); + } + } + [Fact] public async Task TemplateTest() {