diff --git a/docs/README.md b/docs/README.md index b221163..e487200 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ ### 0.13.1 - [New] SaveAsByTemplate by template bytes, convenient to cache and support multiple users to read the same template at the same time #189 -- [New] SaveAsByTemplate support input `IEnmerable> or DapperRows` parameters [#201](https://github.com/shps951023/MiniExcel/issues/201) +- [New] SaveAsByTemplate support input `IEnmerable> or DapperRows or DataTable` parameters [#201](https://github.com/shps951023/MiniExcel/issues/201) - [Bug] Fix after stream SaveAs/SaveAsByTemplate, miniexcel will close stream [#200](https://github.com/shps951023/MiniExcel/issues/200) ### 0.13.0 diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 5f16cef..0febef1 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -9,7 +9,7 @@ ### 0.13.1 - [New] SaveAsByTemplate 支持读取模板 byte[],方便缓存跟支持多用户同时读取同一个模板 #189 -- [New] SaveAsByTemplate 支持传入 `IEnmerable> 或 DapperRows` 参数 [#201](https://github.com/shps951023/MiniExcel/issues/201) +- [New] SaveAsByTemplate 支持传入 `IEnmerable> 或 DapperRows 或 DataTable` 参数 [#201](https://github.com/shps951023/MiniExcel/issues/201) - [Bug] 修正使用 stream SaveAs/SaveAsByTemplate 系统会自动关闭流 stream [#200](https://github.com/shps951023/MiniExcel/issues/200) ### 0.13.0 diff --git a/docs/README.zh-Hant.md b/docs/README.zh-Hant.md index d202369..4edeecf 100644 --- a/docs/README.zh-Hant.md +++ b/docs/README.zh-Hant.md @@ -9,7 +9,7 @@ ### 0.13.1 - [New] SaveAsByTemplate 支持讀取模板 byte[],方便緩存跟支持多用戶同時讀取同一個模板 [#189](https://github.com/shps951023/MiniExcel/issues/189) -- [New] SaveAsByTemplate 支持傳入 `IEnmerable> 或 DapperRows` 參數 [#201](https://github.com/shps951023/MiniExcel/issues/201) +- [New] SaveAsByTemplate 支持傳入 `IEnmerable> 或 DapperRows 或 DataTable` 參數 [#201](https://github.com/shps951023/MiniExcel/issues/201) - [Bug] 修正使用 stream SaveAs/SaveAsByTemplate 系統會自動關閉流 stream [#200](https://github.com/shps951023/MiniExcel/issues/200) ### 0.13.0 diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs index 6824c8b..2a494c1 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs @@ -3,6 +3,7 @@ using MiniExcelLibs.Zip; using System; using System.Collections; using System.Collections.Generic; +using System.Data; using System.IO; using System.IO.Compression; using System.Linq; @@ -21,8 +22,9 @@ namespace MiniExcelLibs.OpenXml public string IEnumerablePropName { get; set; } public XmlElement Row { get; set; } public Type IEnumerableGenricType { get; set; } - public IDictionary PropsMap { get; set; } + public IDictionary PropsMap { get; set; } public bool IsDictionary { get; set; } + public bool IsDataTable { get; set; } public IEnumerable CellIEnumerableValues { get; set; } } @@ -75,12 +77,11 @@ namespace MiniExcelLibs.OpenXml //TODO: some xlsx without r originRowIndex = int.Parse(row.GetAttribute("r")); var newRowIndex = originRowIndex + rowIndexDiff; + if (xInfo.CellIEnumerableValues != null) { var first = true; - - foreach (var item in xInfo.CellIEnumerableValues) { var newRow = row.Clone() as XmlElement; @@ -107,6 +108,42 @@ namespace MiniExcelLibs.OpenXml } + var cellValueStr = ExcelOpenXmlUtils.EncodeXML(cellValue); + var type = propInfo.Value.UnderlyingTypePropType; + if (type == typeof(bool)) + { + cellValueStr = (bool)cellValue ? "1" : "0"; + } + else if (type == typeof(DateTime)) + { + //c.SetAttribute("t", "d"); + cellValueStr = ((DateTime)cellValue).ToString("yyyy-MM-dd HH:mm:ss"); + } + + //TODO: ![image](https://user-images.githubusercontent.com/12729184/114848248-17735880-9e11-11eb-8258-63266bda0a1a.png) + newRow.InnerXml = newRow.InnerXml.Replace(key, cellValueStr); + } + } + else if (xInfo.IsDataTable) + { + var datarow = item as DataRow; + foreach (var propInfo in xInfo.PropsMap) + { + var key = $"{{{{{xInfo.IEnumerablePropName}.{propInfo.Key}}}}}"; + if (item == null) //![image](https://user-images.githubusercontent.com/12729184/114728510-bc3e5900-9d71-11eb-9721-8a414dca21a0.png) + { + newRow.InnerXml = newRow.InnerXml.Replace(key, ""); + continue; + } + + var cellValue = datarow[propInfo.Key]; + if (cellValue == null) + { + newRow.InnerXml = newRow.InnerXml.Replace(key, ""); + continue; + } + + var cellValueStr = ExcelOpenXmlUtils.EncodeXML(cellValue); var type = propInfo.Value.UnderlyingTypePropType; if (type == typeof(bool)) @@ -279,9 +316,9 @@ namespace MiniExcelLibs.OpenXml { xRowInfo.IsDictionary = true; var dic = element as IDictionary; - xRowInfo.PropsMap = dic.Keys.ToDictionary(key => key, key => dic[key] != null - ? new PropInfo { UnderlyingTypePropType= Nullable.GetUnderlyingType(dic[key].GetType()) ?? dic[key].GetType() } - : new PropInfo { UnderlyingTypePropType = typeof(object) } ) ; + xRowInfo.PropsMap = dic.Keys.ToDictionary(key => key, key => dic[key] != null + ? new PropInfo { UnderlyingTypePropType = Nullable.GetUnderlyingType(dic[key].GetType()) ?? dic[key].GetType() } + : new PropInfo { UnderlyingTypePropType = typeof(object) }); } else { @@ -296,14 +333,13 @@ namespace MiniExcelLibs.OpenXml } } - //TODO: check if not contain 1 index //only check first one match IEnumerable, so only render one collection at same row // auto check type https://github.com/shps951023/MiniExcel/issues/177 var prop = xRowInfo.PropsMap[propNames[1]]; var type = prop.UnderlyingTypePropType; //avoid nullable - // + // if (!xRowInfo.PropsMap.ContainsKey(propNames[1])) throw new InvalidDataException($"{propNames[0]} doesn't have {propNames[1]} property"); @@ -326,6 +362,43 @@ namespace MiniExcelLibs.OpenXml break; } + else if (cellValue is DataTable) + { + var dt = cellValue as DataTable; + if (xRowInfo.CellIEnumerableValues == null) + { + xRowInfo.IEnumerablePropName = propNames[0]; + xRowInfo.IEnumerableGenricType = typeof(DataRow); + xRowInfo.IsDataTable = true; + xRowInfo.CellIEnumerableValues = dt.Rows.Cast(); //TODO: need to optimize performance + maxRowIndexDiff = dt.Rows.Count <= 1 ? 0 : dt.Rows.Count; + xRowInfo.PropsMap = dt.Columns.Cast().ToDictionary(col => col.ColumnName, col => + new PropInfo { UnderlyingTypePropType = Nullable.GetUnderlyingType(col.DataType) } + ); + } + + var column = dt.Columns[propNames[1]]; + var type = Nullable.GetUnderlyingType(column.DataType) ?? column.DataType; //avoid nullable + if (!xRowInfo.PropsMap.ContainsKey(propNames[1])) + throw new InvalidDataException($"{propNames[0]} doesn't have {propNames[1]} property"); + + if (isMultiMatch) + { + c.SetAttribute("t", "str"); + } + else if (Helpers.IsNumericType(type)) + { + c.SetAttribute("t", "n"); + } + else if (Type.GetTypeCode(type) == TypeCode.Boolean) + { + c.SetAttribute("t", "b"); + } + else if (Type.GetTypeCode(type) == TypeCode.DateTime) + { + c.SetAttribute("t", "str"); + } + } else { var cellValueStr = ExcelOpenXmlUtils.EncodeXML(cellValue); diff --git a/tests/MiniExcelTests/MiniExcelTemplateTests.cs b/tests/MiniExcelTests/MiniExcelTemplateTests.cs index ec025be..88c62b5 100644 --- a/tests/MiniExcelTests/MiniExcelTemplateTests.cs +++ b/tests/MiniExcelTests/MiniExcelTemplateTests.cs @@ -3,6 +3,7 @@ using MiniExcelLibs; using MiniExcelLibs.Tests.Utils; using System; using System.Collections.Generic; +using System.Data; using System.IO; using System.Linq; using Xunit; @@ -11,6 +12,147 @@ namespace MiniExcelTests { public class MiniExcelTemplateTests { + [Fact] + public void DatatableTemptyRowTest() + { + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"..\..\..\..\..\samples\xlsx\TestTemplateComplex.xlsx"; + var managers = new DataTable(); + { + managers.Columns.Add("name"); + managers.Columns.Add("department"); + } + var employees = new DataTable(); + { + employees.Columns.Add("name"); + employees.Columns.Add("department"); + } + var value = new Dictionary() + { + ["title"] = "FooCompany", + ["managers"] = managers, + ["employees"] = employees + }; + MiniExcel.SaveAsByTemplate(path, templatePath, value); + { + var rows = MiniExcel.Query(path).ToList(); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C5", demension); + } + } + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"..\..\..\..\..\samples\xlsx\TestTemplateComplex.xlsx"; + var managers = new DataTable(); + { + managers.Columns.Add("name"); + managers.Columns.Add("department"); + managers.Rows.Add("Jack", "HR"); + } + var employees = new DataTable(); + { + employees.Columns.Add("name"); + employees.Columns.Add("department"); + employees.Rows.Add("Wade", "HR"); + } + var value = new Dictionary() + { + ["title"] = "FooCompany", + ["managers"] = managers, + ["employees"] = employees + }; + MiniExcel.SaveAsByTemplate(path, templatePath, value); + { + var rows = MiniExcel.Query(path).ToList(); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C5", demension); + } + } + } + + [Fact] + public void DatatableTest() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + var templatePath = @"..\..\..\..\..\samples\xlsx\TestTemplateComplex.xlsx"; + var managers = new DataTable(); + { + managers.Columns.Add("name"); + managers.Columns.Add("department"); + managers.Rows.Add("Jack", "HR"); + managers.Rows.Add("Loan", "IT"); + } + var employees = new DataTable(); + { + employees.Columns.Add("name"); + employees.Columns.Add("department"); + employees.Rows.Add("Wade", "HR"); + employees.Rows.Add("Felix", "HR"); + employees.Rows.Add("Eric", "IT"); + employees.Rows.Add("Keaton", "IT"); + } + var value = new Dictionary() + { + ["title"] = "FooCompany", + ["managers"] = managers, + ["employees"] = employees + }; + MiniExcel.SaveAsByTemplate(path, templatePath, value); + { + var rows = MiniExcel.Query(path).ToList(); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C9", demension); + + Assert.Equal(9, rows.Count); + + Assert.Equal("FooCompany", rows[0].A); + Assert.Equal("Jack", rows[2].B); + Assert.Equal("HR", rows[2].C); + Assert.Equal("Loan", rows[3].B); + Assert.Equal("IT", rows[3].C); + + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); + + Assert.Equal("Eric", rows[7].B); + Assert.Equal("IT", rows[7].C); + Assert.Equal("Keaton", rows[8].B); + Assert.Equal("IT", rows[8].C); + } + + { + var rows = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + + Assert.Equal(9, rows.Count); + + Assert.Equal("FooCompany", rows[0].A); + Assert.Equal("Jack", rows[2].B); + Assert.Equal("HR", rows[2].C); + Assert.Equal("Loan", rows[3].B); + Assert.Equal("IT", rows[3].C); + + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); + + Assert.Equal("Eric", rows[7].B); + Assert.Equal("IT", rows[7].C); + Assert.Equal("Keaton", rows[8].B); + Assert.Equal("IT", rows[8].C); + + var demension = Helpers.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C9", demension); + } + } + + [Fact] public void DapperTemplateTest() {