From a3693db1887308c6f15a462ccb9b70ea3bb8110d Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 22 Jan 2022 17:02:16 +0800 Subject: [PATCH] [New] ExcelFormat support DateTimeOffset/Decimal/double etc. type format #I49RZH #312 #305 --- README.md | 6 +- README.zh-CN.md | 18 ++-- README.zh-Hant.md | 6 +- docs/README.md | 4 +- docs/README.zh-CN.md | 1 + docs/README.zh-Hant.md | 1 + src/MiniExcel/Csv/CsvWriter.cs | 9 +- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 7 +- src/MiniExcel/Utils/CustomPropertyHelper.cs | 14 ++- tests/MiniExcelTests/MiniExcelIssueTests.cs | 89 +++++++++++++++++-- 10 files changed, 135 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 283c374..86d1817 100644 --- a/README.md +++ b/README.md @@ -486,7 +486,7 @@ MiniExcel.SaveAs(path, value); -### 11. File Export +#### 11. File Export Since 0.21.0, when value type is `byte[]` then system will save base64 string at cell by default, and when import system can be converted to `byte[]`. And if you don't want to use it, you can set `OpenXmlConfiguration.ConvertByteArrayToBase64String` to `false`, it can improve the system efficiency. @@ -771,7 +771,9 @@ Assert.Equal("Test4", rows[0].Test7); -#### 2. Custom DateTime Format (ExcelFormatAttribute) +#### 2. Custom Format (ExcelFormatAttribute) + +Since V0.21.0 support class which contains `ToString(string content)` method format Class diff --git a/README.zh-CN.md b/README.zh-CN.md index 7eb923f..f2a18c1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -14,9 +14,10 @@ ---
-

您的 Star 能帮助 MiniExcel 让更多人看到

+

您的 Star★ 能帮助 MiniExcel 让更多人看到

+ --- ### 简介 @@ -492,7 +493,7 @@ MiniExcel.SaveAs(path, value); -### 11. 文件导出 +#### 11. 文件导出 从 0.21.0 开始,当值类型为 `byte[]` 系统预设会转成 base64 字串以便导入时转回 `byte[]`,如不想转换可以将 `OpenXmlConfiguration.ConvertByteArrayToBase64String` 改为 `false`,能提升系统效率。 @@ -782,7 +783,9 @@ Assert.Null(rows[0].Test6); Assert.Equal("Test4", rows[0].Test7); ``` -#### 2. 自定义日期格式 (ExcelFormatAttribute) +#### 2. 自定义Format格式 (ExcelFormatAttribute) + +從 V0.21.0 開始支持有 `ToString(string content)` 的類別 format 类别 @@ -1450,5 +1453,10 @@ public static DataTable QueryAsDataTableWithoutEmptyRow(Stream stream, bool useH ![](https://contrib.rocks/image?repo=shps951023/MiniExcel) ### QQ群 -欢迎交流,QQ群: 813100564(.NET MiniExcel、MiniReport),请给源码项目点个Star吧!!! -希望MiniExcel对您有用,您的支持也是MiniExcel开源的动力,MiniExcel有您更精彩! \ No newline at end of file +欢迎交流,QQ群号: [813100564](https://qm.qq.com/cgi-bin/qm/qr?k=3OkxuL14sXhJsUimWK8wx_Hf28Wl49QE&jump_from=webapi) + + + +### 闲话家常 + +开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在帮忙在Github [![img](https://img.shields.io/github/stars/shps951023/miniexcel.svg?style=flat-square&label=Stars&logo=github)](https://github.com/shps951023/miniexcel)点个★Star。 diff --git a/README.zh-Hant.md b/README.zh-Hant.md index a12f4f7..96f1487 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -496,7 +496,7 @@ MiniExcel.SaveAs(path, value); -### 11. 文件導出 +#### 11. 文件導出 從 0.21.0 開始,當值類型為 `byte[]` 系統預設會轉成 base64 字串以便導入時轉回 `byte[]`,如不想轉換可以將 `OpenXmlConfiguration.ConvertByteArrayToBase64String` 改為 `false`,能提升系統效率。 @@ -784,7 +784,9 @@ Assert.Null(rows[0].Test6); Assert.Equal("Test4", rows[0].Test7); ``` -#### 2. 自定義日期格式 (ExcelFormatAttribute) +#### 2. 自定義Format格式 (ExcelFormatAttribute) + +從 V0.21.0 開始支持有 `ToString(string content)` 的類別 format 類別 diff --git a/docs/README.md b/docs/README.md index ac65a4f..fef9419 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,11 +19,13 @@ ### 0.20.1 +- [New] ExcelFormat support DateTimeOffset/Decimal/double etc. type format #I49RZH #312 #305 - [New] Support byte file import/export - [New] SaveAs support to convert byte[] value to base64 string - [New] Query support to convert base64 value to byte[] - [New] OpenXmlConfiguration add `ConvertByteArrayToBase64String` to turn on/off base64 convertor -- [New] Query support ExcelInvalidCastException to store column, row index #309 +- [New] Query support ExcelInvalidCastException to store column, row, value data #309 + ### 0.20.0 - [New] SaveAs support image #304 diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index dfe4018..28591a9 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -24,6 +24,7 @@ --- ### 0.20.1 +- [New] ExcelFormat 支持 DateTimeOffset/Decimal/double 等类别 format #I49RZH #312 #305 - [New] 支持byte文件导入/导出 - [New] SaveAs 支持预设转换byte[] 值为 base64 字串 - [New] Query 支持转换 base64 字串值为 bytep[] diff --git a/docs/README.zh-Hant.md b/docs/README.zh-Hant.md index 04dfb0c..23b6c04 100644 --- a/docs/README.zh-Hant.md +++ b/docs/README.zh-Hant.md @@ -18,6 +18,7 @@ --- ### 0.20.1 +- [New] ExcelFormat 支持 DateTimeOffset/Decimal/double 等類別 format #I49RZH #312 #305 - [New] 支持byte文件導入/導出 - [New] SaveAs 支持預設轉換byte[] 值為 base64 字串 - [New] Query 支持轉換 base64 字串值為 bytep[] diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index 44a49fe..bd79bb0 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -1,4 +1,5 @@ -using MiniExcelLibs.Utils; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.Utils; using System; using System.Collections; using System.Collections.Generic; @@ -241,7 +242,11 @@ namespace MiniExcelLibs.Csv type = p.ExcludeNullableType; //sometime it doesn't need to re-get type like prop } - if (type == typeof(DateTime)) + if (p?.ExcelFormat != null && p?.ExcelFormatToStringMethod != null) + { + return p.ExcelFormatToStringMethod.Invoke(value, new[] { p.ExcelFormat })?.ToString(); + } + else if (type == typeof(DateTime)) { if (p == null || p.ExcelFormat == null) { diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index c65a077..de6c872 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -359,6 +359,11 @@ namespace MiniExcelLibs.OpenXml { v = ExcelOpenXmlUtils.EncodeXML(value.ToString()); } + else if(p?.ExcelFormat != null && p?.ExcelFormatToStringMethod != null) + { + var formatedStr = p.ExcelFormatToStringMethod.Invoke(value, new[] { p.ExcelFormat })?.ToString(); + v = ExcelOpenXmlUtils.EncodeXML(formatedStr); + } else { Type type = null; @@ -433,7 +438,7 @@ namespace MiniExcelLibs.OpenXml } else { - v = ExcelOpenXmlUtils.EncodeXML(value.ToString()); + v = ExcelOpenXmlUtils.EncodeXML(value.ToString()); } } diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index 4a68783..3b77e50 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -18,6 +18,8 @@ public bool Nullable { get; internal set; } public string ExcelFormat { get; internal set; } public double? ExcelColumnWidth { get; internal set; } + + public MethodInfo ExcelFormatToStringMethod { get; internal set; } } internal static partial class CustomPropertyHelper @@ -149,17 +151,25 @@ { var gt = Nullable.GetUnderlyingType(p.PropertyType); var excelColumnName = p.GetAttribute(); + var excludeNullableType = gt ?? p.PropertyType; + var excelFormat = p.GetAttribute()?.Format; + MethodInfo method = null; + if(excelFormat != null && excludeNullableType != null) + { + method = excludeNullableType.GetMethod("ToString", new[] { typeof(string) }); + } return new ExcelCustomPropertyInfo { Property = p, - ExcludeNullableType = gt ?? p.PropertyType, + ExcludeNullableType = excludeNullableType, Nullable = gt != null, ExcelColumnAliases = excelColumnName?.Aliases, ExcelColumnName = excelColumnName?.ExcelColumnName ?? p.Name, ExcelColumnIndex = p.GetAttribute()?.ExcelColumnIndex, ExcelColumnWidth = p.GetAttribute()?.ExcelColumnWidth, - ExcelFormat = p.GetAttribute()?.Format, + ExcelFormat = excelFormat, + ExcelFormatToStringMethod = method }; }); } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index adbc9a5..808c3da 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -29,6 +29,85 @@ namespace MiniExcelLibs.Tests this.output = output; } + /// + /// https://gitee.com/dotnetchina/MiniExcel/issues/I49RZH + /// https://github.com/shps951023/MiniExcel/issues/305 + /// + [Fact] + public void TestIssueI49RZH() + { + // xlsx + { + var path = PathHelper.GetTempFilePath(); + var value = new TestIssueI49RZHDto[] { + new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, + new TestIssueI49RZHDto{ dd = null} + }; + MiniExcel.SaveAs(path, value); + + var rows = MiniExcel.Query(path).ToList(); + Assert.Equal("2022-01-22", rows[1].A); + } + + //TODO:CSV + { + var path = PathHelper.GetTempFilePath("csv"); + var value = new TestIssueI49RZHDto[] { + new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, + new TestIssueI49RZHDto{ dd = null} + }; + MiniExcel.SaveAs(path, value); + + var rows = MiniExcel.Query(path).ToList(); + Assert.Equal("2022-01-22", rows[1].A); + } + } + + public class TestIssueI49RZHDto + { + [ExcelFormat("yyyy-MM-dd")] + public DateTimeOffset? dd { get; set; } + } + + /// + /// https://github.com/shps951023/MiniExcel/issues/312 + /// + [Fact] + public void TestIssue312() + { + //xlsx + { + var path = PathHelper.GetTempFilePath(); + var value = new TestIssue312Dto[] { + new TestIssue312Dto{ value = 12345.6789}, + new TestIssue312Dto{ value = null} + }; + MiniExcel.SaveAs(path, value); + + var rows = MiniExcel.Query(path).ToList(); + Assert.Equal("12,345.68", rows[1].A); + } + + //TODO:CSV + { + var path = PathHelper.GetTempFilePath("csv"); + var value = new TestIssue312Dto[] { + new TestIssue312Dto{ value = 12345.6789}, + new TestIssue312Dto{ value = null} + }; + MiniExcel.SaveAs(path, value); + + var rows = MiniExcel.Query(path).ToList(); + Assert.Equal("12,345.68", rows[1].A); + } + } + + public class TestIssue312Dto + { + [ExcelFormat("0,0.00")] + public double? value { get; set; } + } + /// /// Query type conversion error /// https://github.com/shps951023/MiniExcel/issues/309 @@ -47,8 +126,8 @@ namespace MiniExcelLibs.Tests Assert.Equal(4, ex.Row); Assert.Equal("Error", ex.Value); Assert.Equal(typeof(int), ex.InvalidCastType); - Assert.Equal("ColumnName : SEQ, CellRow : 4, Value : Error, it can't cast to Int32 type.",ex.Message); - } + Assert.Equal("ColumnName : SEQ, CellRow : 4, Value : Error, it can't cast to Int32 type.", ex.Message); + } } public class TestIssue209Dto @@ -78,12 +157,12 @@ namespace MiniExcelLibs.Tests var expectedBase64 = "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAIAAAD9b0jDAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAEXRFWHRTb2Z0d2FyZQBTbmlwYXN0ZV0Xzt0AAALNSURBVEiJ7ZVLTBNBGMdndrfdIofy0ERbCgcFeYRuCy2JGOPNRA9qeIZS6YEEogQj0YMmGOqDSATxQaLRxKtRID4SgjGelUBpaQvGZ7kpII8aWtjd2dkdDxsJoS1pIh6M/k+z8833m/3+8+0OJISArRa15cT/0D8CZTYPe32+Zy+GxjzjMzOzAACDYafdZquqOG7hzJtkwUQthRC6cavv0eN+QRTBujUQQp1OV1dbffZMq1arTRaqKIok4eZTrSNjHqIo6gIIIQBgbQwpal+Z/f7dPo2GoaiNHtJut3vjPhBe7+kdfvW61Mq1nGyaX1xYjkRzsk2Z6Rm8IOTvzWs73SLwwqjHK4jCgf3lcV6VxGgiECji7AXm0gvtHYQQnue/zy8ghCRJWlxaWuV5Qsilq9cKzLYiiz04ORVLiHP6A4NPRQlhjLWsVpZlnU63Y3umRqNhGCYjPV3HsrIsMwyDsYQQejIwGEuIA/WMT1AAaDSahnoHTdPKL1vXPKVp2umoZVkWAOj1+ZOCzs7NKYTo9XqjYRcAgKIo9ZRUu9VxltGYZTQAAL5+m0kKijEmAPCrqyJCcRuOECKI4lL4ByEEYykpaE62iQIgurLi9wchhLIsry8fYwwh9PomwuEwACDbZEoKauHMgKJSU1PbOy6Hpqdpml5fPsMwn7+EOru6IYQAghKrJSloTVUFURSX02G3lRw+WulqbA4EJ9XQh4+f2s6dr65zhkLTEEIKwtqaylhCnG/fauFO1Nfde/Bw6Hm/0WiYevc+LU2vhlK2pQwNvwQAsCwrYexyOrji4lhCnOaXZRljXONoOHTk2Ju3I/5AcC3EC0JZ+cE9Bea8IqursUkUker4BsWBqpIk6aL7Sm4htzvfvByJqJORaDS3kMsvLuns6kYIJcpNCFU17pvouXlHEET1URDEnt7bo2OezbMS/vp+R3/PdfKPQ38Ccg0E/CDcpY8AAAAASUVORK5CYII="; var actulBase64 = Convert.ToBase64String((byte[])rows[0].Image); Assert.Equal(expectedBase64, actulBase64); - } - + } + // import to base64 string { var config = new OpenXmlConfiguration() { ConvertByteArrayToBase64String = false }; - var rows = MiniExcel.Query(path, true,configuration: config).ToList(); + var rows = MiniExcel.Query(path, true, configuration: config).ToList(); var expectedBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAIAAAD9b0jDAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAEXRFWHRTb2Z0d2FyZQBTbmlwYXN0ZV0Xzt0AAALNSURBVEiJ7ZVLTBNBGMdndrfdIofy0ERbCgcFeYRuCy2JGOPNRA9qeIZS6YEEogQj0YMmGOqDSATxQaLRxKtRID4SgjGelUBpaQvGZ7kpII8aWtjd2dkdDxsJoS1pIh6M/k+z8833m/3+8+0OJISArRa15cT/0D8CZTYPe32+Zy+GxjzjMzOzAACDYafdZquqOG7hzJtkwUQthRC6cavv0eN+QRTBujUQQp1OV1dbffZMq1arTRaqKIok4eZTrSNjHqIo6gIIIQBgbQwpal+Z/f7dPo2GoaiNHtJut3vjPhBe7+kdfvW61Mq1nGyaX1xYjkRzsk2Z6Rm8IOTvzWs73SLwwqjHK4jCgf3lcV6VxGgiECji7AXm0gvtHYQQnue/zy8ghCRJWlxaWuV5Qsilq9cKzLYiiz04ORVLiHP6A4NPRQlhjLWsVpZlnU63Y3umRqNhGCYjPV3HsrIsMwyDsYQQejIwGEuIA/WMT1AAaDSahnoHTdPKL1vXPKVp2umoZVkWAOj1+ZOCzs7NKYTo9XqjYRcAgKIo9ZRUu9VxltGYZTQAAL5+m0kKijEmAPCrqyJCcRuOECKI4lL4ByEEYykpaE62iQIgurLi9wchhLIsry8fYwwh9PomwuEwACDbZEoKauHMgKJSU1PbOy6Hpqdpml5fPsMwn7+EOru6IYQAghKrJSloTVUFURSX02G3lRw+WulqbA4EJ9XQh4+f2s6dr65zhkLTEEIKwtqaylhCnG/fauFO1Nfde/Bw6Hm/0WiYevc+LU2vhlK2pQwNvwQAsCwrYexyOrji4lhCnOaXZRljXONoOHTk2Ju3I/5AcC3EC0JZ+cE9Bea8IqursUkUker4BsWBqpIk6aL7Sm4htzvfvByJqJORaDS3kMsvLuns6kYIJcpNCFU17pvouXlHEET1URDEnt7bo2OezbMS/vp+R3/PdfKPQ38Ccg0E/CDcpY8AAAAASUVORK5CYII="; var actulBase64 = (string)rows[0].Image; Assert.Equal(expectedBase64, actulBase64);