Skip to content

Commit 0380907

Browse files
Fixed two edge cases with the mapping of double types (#886)
- 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
1 parent 3f40cd3 commit 0380907

File tree

9 files changed

+159
-93
lines changed

9 files changed

+159
-93
lines changed

README-V2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1411,7 +1411,7 @@ var data = new TestEntity
14111411
Points = 123
14121412
};
14131413

1414-
var termplater = MiniExcel.Templaters.GetMappingExporter(registry);
1414+
var termplater = MiniExcel.Templaters.GetMappingTemplater(registry);
14151415
await termplater.ApplyTemplateAsync(outputPath, templatePath, new[] { data });
14161416
```
14171417

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ If you do, make sure to also check out the [new docs](README-V2.md) and the [upg
8181

8282
- [Excel Column Name/Index/Ignore Attribute](#getstart4)
8383

84-
- [Fluent Cell Mapping](#getstart4.5)
85-
8684
- [Examples](#getstart5)
8785

8886

samples/xlsx/TestIssue409.xlsx

6.35 KB
Binary file not shown.

samples/xlsx/TestIssue881.xlsx

6.25 KB
Binary file not shown.

src/MiniExcel.Core/Reflection/MiniExcelMapper.cs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@ public static partial class MiniExcelMapper
8484
try
8585
{
8686
object? newValue = null;
87+
8788
if (pInfo.Nullable && string.IsNullOrWhiteSpace(itemValue?.ToString()))
8889
{
90+
// value is null, no transformation required
8991
}
92+
9093
else if (pInfo.ExcludeNullableType == typeof(Guid))
9194
{
9295
newValue = Guid.Parse(itemValue?.ToString() ?? Guid.Empty.ToString());
9396
}
97+
9498
else if (pInfo.ExcludeNullableType == typeof(DateTimeOffset))
9599
{
96100
var vs = itemValue?.ToString();
@@ -110,6 +114,7 @@ public static partial class MiniExcelMapper
110114
throw new InvalidCastException($"{vs} cannot be cast to DateTime");
111115
}
112116
}
117+
113118
else if (pInfo.ExcludeNullableType == typeof(DateTime))
114119
{
115120
// fix issue 257 https://github.com/mini-software/MiniExcel/issues/257
@@ -141,7 +146,8 @@ public static partial class MiniExcelMapper
141146
else
142147
throw new InvalidCastException($"{vs} cannot be cast to DateTime");
143148
}
144-
#if NET6_0_OR_GREATER
149+
150+
#if NET6_0_OR_GREATER
145151
else if (pInfo.ExcludeNullableType == typeof(DateOnly))
146152
{
147153
if (itemValue is DateOnly)
@@ -178,7 +184,8 @@ public static partial class MiniExcelMapper
178184
else
179185
throw new InvalidCastException($"{vs} cannot be cast to DateOnly");
180186
}
181-
#endif
187+
#endif
188+
182189
else if (pInfo.ExcludeNullableType == typeof(TimeSpan))
183190
{
184191
if (itemValue is TimeSpan)
@@ -205,11 +212,22 @@ public static partial class MiniExcelMapper
205212
else
206213
throw new InvalidCastException($"{vs} cannot be cast to TimeSpan");
207214
}
208-
else if (pInfo.ExcludeNullableType == typeof(double)) // && (!Regex.IsMatch(itemValue.ToString(), @"^-?\d+(\.\d+)?([eE][-+]?\d+)?$") || itemValue.ToString().Trim().Equals("NaN")))
215+
216+
else if (pInfo.ExcludeNullableType == typeof(double))
209217
{
210-
var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture);
211-
newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value) ? value : double.NaN;
218+
if (double.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var doubleValue))
219+
{
220+
newValue = doubleValue;
221+
}
222+
else
223+
{
224+
var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture);
225+
newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)
226+
? value
227+
: throw new InvalidCastException();
228+
}
212229
}
230+
213231
else if (pInfo.ExcludeNullableType == typeof(bool))
214232
{
215233
var vs = itemValue?.ToString();
@@ -220,23 +238,27 @@ public static partial class MiniExcelMapper
220238
_ => bool.TryParse(vs, out var parsed) ? parsed : null
221239
};
222240
}
241+
223242
else if (pInfo.Property.Info.PropertyType == typeof(string))
224243
{
225244
newValue = XmlHelper.DecodeString(itemValue?.ToString());
226245
}
246+
227247
else if (pInfo.ExcludeNullableType.IsEnum)
228248
{
229249
var fieldInfo = pInfo.ExcludeNullableType.GetFields().FirstOrDefault(e => e.GetCustomAttribute<DescriptionAttribute>(false)?.Description == itemValue?.ToString());
230250
var value = fieldInfo?.Name ?? itemValue?.ToString() ?? "";
231251
newValue = Enum.Parse(pInfo.ExcludeNullableType, value, true);
232252
}
253+
233254
else if (pInfo.ExcludeNullableType == typeof(Uri))
234255
{
235256
var rawValue = itemValue?.ToString();
236257
if (!Uri.TryCreate(rawValue, UriKind.RelativeOrAbsolute, out var uri))
237258
throw new InvalidCastException($"Value \"{rawValue}\" cannot be converted to Uri");
238259
newValue = uri;
239260
}
261+
240262
else
241263
{
242264
// Use pInfo.ExcludeNullableType to resolve : https://github.com/mini-software/MiniExcel/issues/138

tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,12 +1326,13 @@ public async Task Issue153()
13261326
/// https://github.com/mini-software/MiniExcel/issues/137
13271327
/// </summary>
13281328
[Fact]
1329-
public async Task Issue137()
1329+
public void Issue137()
13301330
{
1331-
var path = "../../../../../samples/xlsx/TestIssue137.xlsx";
1332-
1331+
const string path = "../../../../../samples/xlsx/TestIssue137.xlsx";
1332+
var config = new OpenXmlConfiguration { Culture = new CultureInfo("it")};
1333+
13331334
{
1334-
var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable();
1335+
var q = _excelImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable();
13351336
var rows = q.ToList();
13361337
var first = rows[0] as IDictionary<string, object>; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png
13371338
Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray());
@@ -1367,7 +1368,7 @@ public async Task Issue137()
13671368

13681369
// dynamic query with head
13691370
{
1370-
var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable();
1371+
var q = _excelImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable();
13711372
var rows = q.ToList();
13721373
var first = rows[0] as IDictionary<string, object>; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png
13731374
Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray());
@@ -1389,7 +1390,7 @@ public async Task Issue137()
13891390
}
13901391

13911392
{
1392-
var q = _excelImporter.QueryAsync<Issue137ExcelRow>(path).ToBlockingEnumerable();
1393+
var q = _excelImporter.QueryAsync<Issue137ExcelRow>(path, configuration: config).ToBlockingEnumerable();
13931394
var rows = q.ToList();
13941395
Assert.Equal(10, rows.Count);
13951396

@@ -1419,11 +1420,13 @@ private class Issue137ExcelRow
14191420
/// https://github.com/mini-software/MiniExcel/issues/138
14201421
/// </summary>
14211422
[Fact]
1422-
public async Task Issue138()
1423+
public void Issue138()
14231424
{
14241425
const string path = "../../../../../samples/xlsx/TestIssue138.xlsx";
1426+
var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") };
1427+
14251428
{
1426-
var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable();
1429+
var q = _excelImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable();
14271430
var rows = q.ToList();
14281431
Assert.Equal(6, rows.Count);
14291432

@@ -1449,7 +1452,7 @@ public async Task Issue138()
14491452
}
14501453
{
14511454

1452-
var q = _excelImporter.QueryAsync<Issue138ExcelRow>(path).ToBlockingEnumerable();
1455+
var q = _excelImporter.QueryAsync<Issue138ExcelRow>(path, configuration: config).ToBlockingEnumerable();
14531456
var rows = q.ToList();
14541457
Assert.Equal(6, rows.Count);
14551458
Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date);

tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2527,9 +2527,10 @@ public void Issue153()
25272527
public void Issue137()
25282528
{
25292529
const string path = "../../../../../samples/xlsx/TestIssue137.xlsx";
2530+
var config = new OpenXmlConfiguration { Culture = new CultureInfo("it") };
25302531

25312532
{
2532-
var rows = _excelImporter.Query(path).ToList();
2533+
var rows = _excelImporter.Query(path, configuration: config).ToList();
25332534
var first = rows[0] as IDictionary<string, object>; // https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png
25342535
Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], first?.Keys.ToArray());
25352536
Assert.Equal(11, rows.Count);
@@ -2563,7 +2564,7 @@ public void Issue137()
25632564

25642565
// dynamic query with head
25652566
{
2566-
var rows = _excelImporter.Query(path, true).ToList();
2567+
var rows = _excelImporter.Query(path, true, configuration: config).ToList();
25672568
var first = rows[0] as IDictionary<string, object>; //![image](https://user-images.githubusercontent.com/12729184/113266322-ba06e400-9307-11eb-9521-d36abfda75cc.png)
25682569
Assert.Equal(["比例", "商品", "滿倉口數", "0", "1為港幣 0為台幣"], first?.Keys.ToArray());
25692570
Assert.Equal(10, rows.Count);
@@ -2583,7 +2584,7 @@ public void Issue137()
25832584
}
25842585

25852586
{
2586-
var rows = _excelImporter.Query<Issue137ExcelRow>(path).ToList();
2587+
var rows = _excelImporter.Query<Issue137ExcelRow>(path, configuration: config).ToList();
25872588
Assert.Equal(10, rows.Count);
25882589
{
25892590
var row = rows[0];
@@ -2614,8 +2615,11 @@ private class Issue137ExcelRow
26142615
public void Issue138()
26152616
{
26162617
const string path = "../../../../../samples/xlsx/TestIssue138.xlsx";
2618+
var config = new OpenXmlConfiguration { Culture = new CultureInfo("zh") };
2619+
config.Culture.NumberFormat.NumberDecimalSeparator = ",";
2620+
26172621
{
2618-
var rows = _excelImporter.Query(path, true).ToList();
2622+
var rows = _excelImporter.Query(path, true, configuration: config).ToList();
26192623
Assert.Equal(6, rows.Count);
26202624

26212625
foreach (var index in new[] { 0, 2, 5 })
@@ -2640,7 +2644,7 @@ public void Issue138()
26402644
}
26412645
{
26422646

2643-
var rows = _excelImporter.Query<Issue138ExcelRow>(path).ToList();
2647+
var rows = _excelImporter.Query<Issue138ExcelRow>(path, configuration: config).ToList();
26442648
Assert.Equal(6, rows.Count);
26452649
Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date);
26462650

@@ -2723,6 +2727,30 @@ public void IssueI50VD5()
27232727
}
27242728
}
27252729

2730+
private class Issues409_881
2731+
{
2732+
public string Units { get; set; }
2733+
public double Quantity { get; set; }
2734+
}
2735+
2736+
[Fact]
2737+
public void TestIssue409()
2738+
{
2739+
var path = PathHelper.GetFile("xlsx/TestIssue409.xlsx");
2740+
var config = new OpenXmlConfiguration { Culture = new CultureInfo("ru") };
2741+
config.Culture.NumberFormat.NumberDecimalSeparator = ",";
2742+
2743+
var query = _excelImporter.Query<Issues409_881>(path, configuration: config).ToList();
2744+
2745+
Assert.Equal(0.002886, query[0].Quantity);
2746+
Assert.Equal(4.1E-05, query[1].Quantity);
2747+
Assert.Equal(0.02586, query[2].Quantity);
2748+
Assert.Equal(0.000217, query[3].Quantity);
2749+
Assert.Equal(17.4024812, query[4].Quantity);
2750+
Assert.Equal(1.43E-06, query[5].Quantity);
2751+
Assert.Equal(9.9E-06, query[6].Quantity);
2752+
}
2753+
27262754
private class Issue422Enumerable(IEnumerable inner) : IEnumerable
27272755
{
27282756
private readonly IEnumerable _inner = inner;
@@ -3728,4 +3756,13 @@ public void TestIssue880_ShouldThrowNotSerializableException()
37283756
_excelExporter.Export(ms, toExport);
37293757
});
37303758
}
3759+
3760+
[Fact]
3761+
public void TestIssue881()
3762+
{
3763+
Assert.Throws<MiniExcelInvalidCastException>(() =>
3764+
{
3765+
_ = _excelImporter.Query<Issues409_881>(PathHelper.GetFile("xlsx/TestIssue881.xlsx")).ToList();
3766+
});
3767+
}
37313768
}

0 commit comments

Comments
 (0)