diff --git a/README-V2.md b/README-V2.md index ace49a81..9234bb0e 100644 --- a/README-V2.md +++ b/README-V2.md @@ -1411,7 +1411,7 @@ var data = new TestEntity Points = 123 }; -var termplater = MiniExcel.Templaters.GetMappingExporter(registry); +var termplater = MiniExcel.Templaters.GetMappingTemplater(registry); await termplater.ApplyTemplateAsync(outputPath, templatePath, new[] { data }); ``` diff --git a/README.md b/README.md index 49e5024a..3195135e 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,6 @@ If you do, make sure to also check out the [new docs](README-V2.md) and the [upg - [Excel Column Name/Index/Ignore Attribute](#getstart4) -- [Fluent Cell Mapping](#getstart4.5) - - [Examples](#getstart5) diff --git a/samples/xlsx/TestIssue409.xlsx b/samples/xlsx/TestIssue409.xlsx new file mode 100644 index 00000000..9361134e Binary files /dev/null and b/samples/xlsx/TestIssue409.xlsx differ diff --git a/samples/xlsx/TestIssue881.xlsx b/samples/xlsx/TestIssue881.xlsx new file mode 100644 index 00000000..7cc784bb Binary files /dev/null and b/samples/xlsx/TestIssue881.xlsx differ diff --git a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs index 681f4d5a..ad2223af 100644 --- a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs +++ b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs @@ -84,13 +84,17 @@ public static partial class MiniExcelMapper try { object? newValue = null; + if (pInfo.Nullable && string.IsNullOrWhiteSpace(itemValue?.ToString())) { + // value is null, no transformation required } + else if (pInfo.ExcludeNullableType == typeof(Guid)) { newValue = Guid.Parse(itemValue?.ToString() ?? Guid.Empty.ToString()); } + else if (pInfo.ExcludeNullableType == typeof(DateTimeOffset)) { var vs = itemValue?.ToString(); @@ -110,6 +114,7 @@ public static partial class MiniExcelMapper throw new InvalidCastException($"{vs} cannot be cast to DateTime"); } } + else if (pInfo.ExcludeNullableType == typeof(DateTime)) { // fix issue 257 https://github.com/mini-software/MiniExcel/issues/257 @@ -141,7 +146,8 @@ public static partial class MiniExcelMapper else throw new InvalidCastException($"{vs} cannot be cast to DateTime"); } - #if NET6_0_OR_GREATER + +#if NET6_0_OR_GREATER else if (pInfo.ExcludeNullableType == typeof(DateOnly)) { if (itemValue is DateOnly) @@ -178,7 +184,8 @@ public static partial class MiniExcelMapper else throw new InvalidCastException($"{vs} cannot be cast to DateOnly"); } - #endif +#endif + else if (pInfo.ExcludeNullableType == typeof(TimeSpan)) { if (itemValue is TimeSpan) @@ -205,11 +212,22 @@ public static partial class MiniExcelMapper else throw new InvalidCastException($"{vs} cannot be cast to TimeSpan"); } - else if (pInfo.ExcludeNullableType == typeof(double)) // && (!Regex.IsMatch(itemValue.ToString(), @"^-?\d+(\.\d+)?([eE][-+]?\d+)?$") || itemValue.ToString().Trim().Equals("NaN"))) + + else if (pInfo.ExcludeNullableType == typeof(double)) { - var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture); - newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value) ? value : double.NaN; + if (double.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var doubleValue)) + { + newValue = doubleValue; + } + else + { + var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture); + newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value) + ? value + : throw new InvalidCastException(); + } } + else if (pInfo.ExcludeNullableType == typeof(bool)) { var vs = itemValue?.ToString(); @@ -220,16 +238,19 @@ public static partial class MiniExcelMapper _ => bool.TryParse(vs, out var parsed) ? parsed : null }; } + else if (pInfo.Property.Info.PropertyType == typeof(string)) { newValue = XmlHelper.DecodeString(itemValue?.ToString()); } + else if (pInfo.ExcludeNullableType.IsEnum) { var fieldInfo = pInfo.ExcludeNullableType.GetFields().FirstOrDefault(e => e.GetCustomAttribute(false)?.Description == itemValue?.ToString()); var value = fieldInfo?.Name ?? itemValue?.ToString() ?? ""; newValue = Enum.Parse(pInfo.ExcludeNullableType, value, true); } + else if (pInfo.ExcludeNullableType == typeof(Uri)) { var rawValue = itemValue?.ToString(); @@ -237,6 +258,7 @@ public static partial class MiniExcelMapper throw new InvalidCastException($"Value \"{rawValue}\" cannot be converted to Uri"); newValue = uri; } + else { // Use pInfo.ExcludeNullableType to resolve : https://github.com/mini-software/MiniExcel/issues/138 diff --git a/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs index 94dfde91..a4b1041f 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs @@ -1326,12 +1326,13 @@ public async Task Issue153() /// https://github.com/mini-software/MiniExcel/issues/137 /// [Fact] - public async Task Issue137() + public void Issue137() { - var path = "../../../../../samples/xlsx/TestIssue137.xlsx"; - + const string path = "../../../../../samples/xlsx/TestIssue137.xlsx"; + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it")}; + { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); var first = rows[0] as IDictionary; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray()); @@ -1367,7 +1368,7 @@ public async Task Issue137() // dynamic query with head { - var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); var first = rows[0] as IDictionary; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray()); @@ -1389,7 +1390,7 @@ public async Task Issue137() } { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(10, rows.Count); @@ -1419,11 +1420,13 @@ private class Issue137ExcelRow /// https://github.com/mini-software/MiniExcel/issues/138 /// [Fact] - public async Task Issue138() + public void Issue138() { const string path = "../../../../../samples/xlsx/TestIssue138.xlsx"; + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; + { - var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(6, rows.Count); @@ -1449,7 +1452,7 @@ public async Task Issue138() } { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(6, rows.Count); Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); diff --git a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs index 9e22e35e..769856e6 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs @@ -2527,9 +2527,10 @@ public void Issue153() public void Issue137() { const string path = "../../../../../samples/xlsx/TestIssue137.xlsx"; + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; { - var rows = _excelImporter.Query(path).ToList(); + var rows = _excelImporter.Query(path, configuration: config).ToList(); var first = rows[0] as IDictionary; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray()); Assert.Equal(11, rows.Count); @@ -2563,7 +2564,7 @@ public void Issue137() // dynamic query with head { - var rows = _excelImporter.Query(path, true).ToList(); + var rows = _excelImporter.Query(path, true, configuration: config).ToList(); var first = rows[0] as IDictionary; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png) Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray()); Assert.Equal(10, rows.Count); @@ -2583,7 +2584,7 @@ public void Issue137() } { - var rows = _excelImporter.Query(path).ToList(); + var rows = _excelImporter.Query(path, configuration: config).ToList(); Assert.Equal(10, rows.Count); { var row = rows[0]; @@ -2614,8 +2615,11 @@ private class Issue137ExcelRow public void Issue138() { const string path = "../../../../../samples/xlsx/TestIssue138.xlsx"; + var config = new OpenXmlConfiguration { Culture = new CultureInfo("zh") }; + config.Culture.NumberFormat.NumberDecimalSeparator = ","; + { - var rows = _excelImporter.Query(path, true).ToList(); + var rows = _excelImporter.Query(path, true, configuration: config).ToList(); Assert.Equal(6, rows.Count); foreach (var index in new[] { 0, 2, 5 }) @@ -2640,7 +2644,7 @@ public void Issue138() } { - var rows = _excelImporter.Query(path).ToList(); + var rows = _excelImporter.Query(path, configuration: config).ToList(); Assert.Equal(6, rows.Count); Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); @@ -2723,6 +2727,30 @@ public void IssueI50VD5() } } + private class Issues409_881 + { + public string Units { get; set; } + public double Quantity { get; set; } + } + + [Fact] + public void TestIssue409() + { + var path = PathHelper.GetFile("xlsx/TestIssue409.xlsx"); + var config = new OpenXmlConfiguration { Culture = new CultureInfo("ru") }; + config.Culture.NumberFormat.NumberDecimalSeparator = ","; + + var query = _excelImporter.Query(path, configuration: config).ToList(); + + Assert.Equal(0.002886, query[0].Quantity); + Assert.Equal(4.1E-05, query[1].Quantity); + Assert.Equal(0.02586, query[2].Quantity); + Assert.Equal(0.000217, query[3].Quantity); + Assert.Equal(17.4024812, query[4].Quantity); + Assert.Equal(1.43E-06, query[5].Quantity); + Assert.Equal(9.9E-06, query[6].Quantity); + } + private class Issue422Enumerable(IEnumerable inner) : IEnumerable { private readonly IEnumerable _inner = inner; @@ -3728,4 +3756,13 @@ public void TestIssue880_ShouldThrowNotSerializableException() _excelExporter.Export(ms, toExport); }); } + + [Fact] + public void TestIssue881() + { + Assert.Throws(() => + { + _ = _excelImporter.Query(PathHelper.GetFile("xlsx/TestIssue881.xlsx")).ToList(); + }); + } } \ No newline at end of file diff --git a/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs index 90f894bb..dee3097b 100644 --- a/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs @@ -317,78 +317,78 @@ private class TestIEnumerableTypePoco [Fact] public async Task TestIEnumerableType() { - { - const string templatePath = "../../../../../samples/xlsx/TestIEnumerableType.xlsx"; - using var path = AutoDeletingPath.Create(); + const string templatePath = "../../../../../samples/xlsx/TestIEnumerableType.xlsx"; + using var path = AutoDeletingPath.Create(); - var poco = new TestIEnumerableTypePoco - { - @string = "string", - @int = 123, - @decimal = 123.45m, - @double = 123.33, - datetime = new DateTime(2021, 4, 1), - @bool = true, - Guid = Guid.NewGuid() - }; + var poco = new TestIEnumerableTypePoco + { + @string = "string", + @int = 123, + @decimal = 123.45m, + @double = 123.33, + datetime = new DateTime(2021, 4, 1), + @bool = true, + Guid = Guid.NewGuid() + }; - var value = new - { - Ts = new[] { - poco, - new TestIEnumerableTypePoco(), - null, - new TestIEnumerableTypePoco(), - poco - } - }; - await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); + var value = new + { + Ts = new[] { + poco, + new TestIEnumerableTypePoco(), + null, + new TestIEnumerableTypePoco(), + poco + } + }; + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal(poco.@string, rows[0].@string); - Assert.Equal(poco.@int, rows[0].@int); - Assert.Equal(poco.@double, rows[0].@double); - Assert.Equal(poco.@decimal, rows[0].@decimal); - Assert.Equal(poco.@bool, rows[0].@bool); - Assert.Equal(poco.datetime, rows[0].datetime); - Assert.Equal(poco.Guid, rows[0].Guid); - - Assert.Null(rows[1].@string); - Assert.Null(rows[1].@int); - Assert.Null(rows[1].@double); - Assert.Null(rows[1].@decimal); - Assert.Null(rows[1].@bool); - Assert.Null(rows[1].datetime); - Assert.Null(rows[1].Guid); - - // special input null but query is empty vo - Assert.Null(rows[2].@string); - Assert.Null(rows[2].@int); - Assert.Null(rows[2].@double); - Assert.Null(rows[2].@decimal); - Assert.Null(rows[2].@bool); - Assert.Null(rows[2].datetime); - Assert.Null(rows[2].Guid); - - Assert.Null(rows[3].@string); - Assert.Null(rows[3].@int); - Assert.Null(rows[3].@double); - Assert.Null(rows[3].@decimal); - Assert.Null(rows[3].@bool); - Assert.Null(rows[3].datetime); - Assert.Null(rows[3].Guid); - - Assert.Equal(poco.@string, rows[4].@string); - Assert.Equal(poco.@int, rows[4].@int); - Assert.Equal(poco.@double, rows[4].@double); - Assert.Equal(poco.@decimal, rows[4].@decimal); - Assert.Equal(poco.@bool, rows[4].@bool); - Assert.Equal(poco.datetime, rows[4].datetime); - Assert.Equal(poco.Guid, rows[4].Guid); + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; + + var rows = _excelImporter.Query(path.ToString(), configuration: config).ToList(); + Assert.Equal(poco.@string, rows[0].@string); + Assert.Equal(poco.@int, rows[0].@int); + Assert.Equal(poco.@double, rows[0].@double); + Assert.Equal(poco.@decimal, rows[0].@decimal); + Assert.Equal(poco.@bool, rows[0].@bool); + Assert.Equal(poco.datetime, rows[0].datetime); + Assert.Equal(poco.Guid, rows[0].Guid); + + Assert.Null(rows[1].@string); + Assert.Null(rows[1].@int); + Assert.Null(rows[1].@double); + Assert.Null(rows[1].@decimal); + Assert.Null(rows[1].@bool); + Assert.Null(rows[1].datetime); + Assert.Null(rows[1].Guid); + + // special input null but query is empty vo + Assert.Null(rows[2].@string); + Assert.Null(rows[2].@int); + Assert.Null(rows[2].@double); + Assert.Null(rows[2].@decimal); + Assert.Null(rows[2].@bool); + Assert.Null(rows[2].datetime); + Assert.Null(rows[2].Guid); + + Assert.Null(rows[3].@string); + Assert.Null(rows[3].@int); + Assert.Null(rows[3].@double); + Assert.Null(rows[3].@decimal); + Assert.Null(rows[3].@bool); + Assert.Null(rows[3].datetime); + Assert.Null(rows[3].Guid); + + Assert.Equal(poco.@string, rows[4].@string); + Assert.Equal(poco.@int, rows[4].@int); + Assert.Equal(poco.@double, rows[4].@double); + Assert.Equal(poco.@decimal, rows[4].@decimal); + Assert.Equal(poco.@bool, rows[4].@bool); + Assert.Equal(poco.datetime, rows[4].datetime); + Assert.Equal(poco.Guid, rows[4].Guid); - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:G6", dimension); - } + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:G6", dimension); } [Fact] @@ -409,7 +409,9 @@ public async Task TestTemplateTypeMapping() }; await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; + + var rows = _excelImporter.Query(path.ToString(), configuration: config).ToList(); Assert.Equal(value.@string, rows[0].@string); Assert.Equal(value.@int, rows[0].@int); Assert.Equal(value.@double, rows[0].@double); diff --git a/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs index 6dcabfaa..09a84754 100644 --- a/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs +++ b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs @@ -519,7 +519,9 @@ public void TestIEnumerableType() }; _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; + + var rows = _excelImporter.Query(path.ToString(), configuration: config).ToList(); Assert.Equal(poco.@string, rows[0].@string); Assert.Equal(poco.@int, rows[0].@int); Assert.Equal(poco.@double, rows[0].@double); @@ -587,7 +589,9 @@ public void TestTemplateTypeMapping() }; _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); + var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") }; + + var rows = _excelImporter.Query(path.ToString(), configuration: config).ToList(); Assert.Equal(value.@string, rows[0].@string); Assert.Equal(value.@int, rows[0].@int); Assert.Equal(value.@double, rows[0].@double);