add support for grouped cells (#464)

* add support for grouped cells

* fix for rows after endgroup

* minor fix for text after grouping
This commit is contained in:
eynarhaji 2023-02-15 18:23:01 +04:00 committed by GitHub
parent 83d1208a51
commit 1520b76fd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 198 additions and 14 deletions

View File

@ -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<object> CellIlListValues { get; set; }
public IEnumerable CellIEnumerableValues { get; set; }
public XMergeCell IEnumerableMercell { get; set; }
public List<XMergeCell> 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<object> 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<object>) && !(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<object>;
// 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<DataRow>(); //TODO: need to optimize performance
xRowInfo.CellIEnumerableValues = dt.Rows.Cast<object>().ToList(); //TODO: need to optimize performance
xRowInfo.CellIlListValues = dt.Rows.Cast<object>().ToList();
var first = true;
foreach (var element in xRowInfo.CellIEnumerableValues)
{

View File

@ -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<string, object>()
{
["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<string, object>()
{
["employees"] = dt
};
await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value);
var demension = Helpers.GetFirstSheetDimensionRefValue(path);
Assert.Equal("A1:B15", demension);
}
}
[Fact]
public async Task TemplateTest()
{