From 2ec630ec3a2e43143612685345f33f506c0ab2a7 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Wed, 15 Oct 2025 23:41:57 +0200 Subject: [PATCH 1/2] Fixes two edge cases with the mapping of double types - The mapping process will now try to apply the culture provided into the MiniExcelConfiguration when parsing doubles - When the parsing of double types fails an MiniExcelInvalidCastException will be thrown instead of mapping the value to double.NaN --- samples/xlsx/TestIssue409.xlsx | Bin 0 -> 6498 bytes samples/xlsx/TestIssue881.xlsx | Bin 0 -> 6402 bytes .../Reflection/MiniExcelMapper.cs | 32 +++- .../MiniExcelIssueAsyncTests.cs | 21 +-- .../MiniExcelIssueTests.cs | 47 +++++- .../MiniExcelTemplateAsyncTests.cs | 140 +++++++++--------- .../SaveByTemplate/MiniExcelTemplateTests.cs | 8 +- 7 files changed, 158 insertions(+), 90 deletions(-) create mode 100644 samples/xlsx/TestIssue409.xlsx create mode 100644 samples/xlsx/TestIssue881.xlsx diff --git a/samples/xlsx/TestIssue409.xlsx b/samples/xlsx/TestIssue409.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9361134e37f8bb06207d5487a0816a7f6c0ee985 GIT binary patch literal 6498 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 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); From 65291be10edca69126fe70aed064457bc96fa467 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Thu, 16 Oct 2025 23:51:18 +0200 Subject: [PATCH 2/2] Changed a ToString call to Convert.ToString and fixed a couple mistakes in the readme --- README-V2.md | 2 +- README.md | 2 -- src/MiniExcel.Core/Reflection/MiniExcelMapper.cs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) 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/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs index 0433844e..ad2223af 100644 --- a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs +++ b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs @@ -215,7 +215,7 @@ public static partial class MiniExcelMapper else if (pInfo.ExcludeNullableType == typeof(double)) { - if (double.TryParse(itemValue?.ToString(), NumberStyles.Any, config.Culture, out var doubleValue)) + if (double.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var doubleValue)) { newValue = doubleValue; }