mirror of
https://gitee.com/dotnetchina/MiniExcel.git
synced 2024-11-29 18:38:08 +08:00
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:
parent
83d1208a51
commit
1520b76fd3
BIN
samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx
Normal file
BIN
samples/xlsx/TestTemplateBasicIEmumerableFillGroup.xlsx
Normal file
Binary file not shown.
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user