diff --git a/MiniExcel.slnx b/MiniExcel.slnx index 290ecf86..60cbb969 100644 --- a/MiniExcel.slnx +++ b/MiniExcel.slnx @@ -6,7 +6,6 @@ - diff --git a/README-V2.md b/README-V2.md index 575e78d4..e41f213d 100644 --- a/README-V2.md +++ b/README-V2.md @@ -6,9 +6,6 @@ - - Build status - star diff --git a/README.md b/README.md index 81d25bee..0bb01399 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

NuGet -Build status star GitHub stars version Ask DeepWiki diff --git a/README.zh-CN.md b/README.zh-CN.md index fc99c457..8ef763f8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,6 +1,5 @@

NuGet -Build status star GitHub stars version Ask DeepWiki diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index b65ae523..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: build-{build} -skip_tags: true -image: Visual Studio 2019 -configuration: Debug -build_script: -- ps: |- - #cd src - If ($Env:APPVEYOR_REPO_BRANCH -eq "master") { - $Env:VERSION_SUFFIX="" - } Else { - $Env:VERSION_SUFFIX=$Env:APPVEYOR_REPO_BRANCH + $Env:APPVEYOR_BUILD_NUMBER.PadLeft(5, "0") - } - msbuild /t:Restore /p:VersionSuffix=$Env:VERSION_SUFFIX - msbuild MiniExcel.sln /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /p:Configuration=$Env:CONFIGURATION /p:VersionSuffix=$Env:VERSION_SUFFIX /p:TreatWarningsAsErrors=true -test_script: -- ps: |- - # run tests - dotnet test tests\MiniExcelTests\MiniExcelTests.csproj --no-build --logger "trx" -c $Env:CONFIGURATION - # upload results to AppVeyor - $wc = New-Object 'System.Net.WebClient' - Resolve-Path tests\MiniExcelTests\TestResults\*.trx | % { $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", "$_") } -#artifacts: -#- path: '**\*.nupkg' \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index b5fe2077..ad71fa48 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,6 @@

NuGet -Build status star GitHub stars version

diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 7b9d4fe6..8dc2494b 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -5,7 +5,6 @@

NuGet -Build status star GitHub stars version

diff --git a/docs/README.zh-Hant.md b/docs/README.zh-Hant.md index b019e584..cf2cdcec 100644 --- a/docs/README.zh-Hant.md +++ b/docs/README.zh-Hant.md @@ -4,7 +4,6 @@

NuGet -Build status star GitHub stars version

diff --git a/src/README.md b/src/README.md index d9cb2979..cbfe70bf 100644 --- a/src/README.md +++ b/src/README.md @@ -7,9 +7,6 @@ - - Build status - star diff --git a/tests/MiniExcel.Csv.Tests/Issues/GiteeIssuesTests.cs b/tests/MiniExcel.Csv.Tests/Issues/GiteeIssuesTests.cs new file mode 100644 index 00000000..ce071a15 --- /dev/null +++ b/tests/MiniExcel.Csv.Tests/Issues/GiteeIssuesTests.cs @@ -0,0 +1,103 @@ +namespace MiniExcelLib.Csv.Tests.Issues; + +public class GiteeIssuesTests +{ + private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); + + // https://gitee.com/dotnetchina/MiniExcel/issues/I4X92G + [Fact] + public void TestIssueI4X92G() + { + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + + { + var value = new[] + { + new { ID = 1, Name = "Jack", InDate = new DateTime(2021,01,03)}, + new { ID = 2, Name = "Henry", InDate = new DateTime(2020,05,03)} + }; + _csvExporter.Export(path, value); + var content = File.ReadAllText(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + + """, + content); + } + { + var value = new { ID = 3, Name = "Mike", InDate = new DateTime(2021, 04, 23) }; + var rowsWritten = _csvExporter.Append(path, value); + Assert.Equal(1, rowsWritten); + + var content = File.ReadAllText(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + + """, + content); + } + { + var value = new[] + { + new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)}, + new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)}, + }; + var rowsWritten = _csvExporter.Append(path, value); + Assert.Equal(2, rowsWritten); + + var content = File.ReadAllText(path); + Assert.Equal( + """ + ID,Name,InDate + 1,Jack,"2021-01-03 00:00:00" + 2,Henry,"2020-05-03 00:00:00" + 3,Mike,"2021-04-23 00:00:00" + 4,Frank,"2021-06-07 00:00:00" + 5,Gloria,"2022-05-03 00:00:00" + + """, + content); + } + } + + [Fact] + public void TestIssueI4Wda9() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + var value = new DataTable(); + { + value.Columns.Add("\"name\""); + value.Rows.Add("\"Jack\""); + } + + _csvExporter.Export(path.ToString(), value); + Assert.Equal("\"\"\"name\"\"\"\r\n\"\"\"Jack\"\"\"\r\n", File.ReadAllText(path.ToString())); + } + + // Using stream.SaveAs will close the Stream automatically when Specifying excelType + // https://gitee.com/dotnetchina/MiniExcel/issues/I57WMM + [Fact] + public void TestIssueGiteeI57() + { + Dictionary[] sheets = [new() { ["ID"] = "0001", ["Name"] = "Jack" }]; + using var stream = new MemoryStream(); + + var config = new CsvConfiguration { StreamWriterFunc = x => new StreamWriter(x, Encoding.Default, leaveOpen: true) }; + _csvExporter.Export(stream, sheets, configuration: config); + stream.Seek(0, SeekOrigin.Begin); + + // convert stream to string + using var reader = new StreamReader(stream); + var text = reader.ReadToEnd(); + + Assert.Equal("ID,Name\r\n0001,Jack\r\n", text); + } +} \ No newline at end of file diff --git a/tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs b/tests/MiniExcel.Csv.Tests/Issues/GithubIssuesAsyncTests.cs similarity index 72% rename from tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs rename to tests/MiniExcel.Csv.Tests/Issues/GithubIssuesAsyncTests.cs index 72252161..b9ab20f7 100644 --- a/tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs +++ b/tests/MiniExcel.Csv.Tests/Issues/GithubIssuesAsyncTests.cs @@ -1,110 +1,11 @@ -namespace MiniExcelLib.Csv.Tests; +namespace MiniExcelLib.Csv.Tests.Issues; -public class AsyncIssueTests +public class GithubIssuesAsyncTests { private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); - /// - /// Csv SaveAs by datareader with encoding default show messy code #253 - /// - [Fact] - public async Task Issue253() - { - { - var value = new[] { new { col1 = "世界你好" } }; - using var path = AutoDeletingPath.Create(ExcelType.Csv); - - await _csvExporter.ExportAsync(path.ToString(), value); - const string expected = - """ - col1 - 世界你好 - - """; - - Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); - } - - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - var value = new[] { new { col1 = "世界你好" } }; - using var path = AutoDeletingPath.Create(ExcelType.Csv); - - var config = new CsvConfiguration - { - StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) - }; - - await _csvExporter.ExportAsync(path.ToString(), value, configuration: config); - const string expected = - """ - col1 - ������� - - """; - - Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); - } - - await using var cn = Db.GetConnection(); - - { - var value = await cn.ExecuteReaderAsync("select '世界你好' col1"); - using var path = AutoDeletingPath.Create(ExcelType.Csv); - await _csvExporter.ExportAsync(path.ToString(), value); - const string expected = - """ - col1 - 世界你好 - - """; - - Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); - } - } - - /// - /// [CSV SaveAs support datareader · Issue #251 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/251) - /// - [Fact] - public async Task Issue251() - { - await using var cn = Db.GetConnection(); - var reader = await cn.ExecuteReaderAsync(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); - - using var path = AutoDeletingPath.Create(ExcelType.Csv); - var rowsWritten = await _csvExporter.ExportAsync(path.ToString(), reader); - - Assert.Equal(2, rowsWritten); - - const string expected = - """" - a,b - """<>+-*//}{\\n",1234567890 - "Hello World",-1234567890 - - """"; - - Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); - } - - private class Issue89Dto - { - public WorkState State { get; set; } - - public enum WorkState - { - OnDuty, - Leave, - Fired - } - } - - /// - /// Support Enum Mapping - /// https://github.com/mini-software/MiniExcel/issues/89 - /// + // Support Enum Mapping [Fact] public async Task Issue89() { @@ -140,20 +41,6 @@ public async Task Issue89() Assert.Equal(Issue89Dto.WorkState.Leave, rows2[2].State); } - private class Issue142VoDuplicateColumnName - { - [MiniExcelColumnIndex("A")] - public int MyProperty1 { get; set; } - - [MiniExcelColumnIndex("A")] - public int MyProperty2 { get; set; } - - public int MyProperty3 { get; set; } - [MiniExcelColumnIndex("B")] - - public int MyProperty4 { get; set; } - } - [Fact] public async Task Issue142() { @@ -180,10 +67,7 @@ public async Task Issue142() Assert.Equal("MyProperty3", rows[0].MyProperty3); } - /// - /// DataTable recommended to use Caption for column name first, then use columname - /// https://github.com/mini-software/MiniExcel/issues/217 - /// + // DataTable recommended to use Caption for column name first, then use columname [Fact] public async Task Issue217() { @@ -203,10 +87,7 @@ public async Task Issue217() } - /// - /// Csv QueryAsync split comma not correct #237 - /// https://github.com/mini-software/MiniExcel/issues/237 - /// + // Csv QueryAsync split comma not correct [Fact] public async Task Issue237() { @@ -223,10 +104,7 @@ public async Task Issue237() Assert.Equal("1,2,3", rows[1].id); } - - /// - /// Support Custom Datetime format #241 - /// + // Support Custom Datetime format [Fact] public async Task Issue241() { @@ -252,9 +130,7 @@ public async Task Issue241() } - /// - /// Csv type mapping QueryAsync error "cannot be converted to xxx type" #243 - /// + // Csv type mapping QueryAsync error "cannot be converted to xxx type" [Fact] public async Task Issue243() { @@ -279,36 +155,83 @@ public async Task Issue243() Assert.Equal(new DateTime(2020, 05, 03), rows[1].InDate); } - #region Duplicated - private class Issue142Dto - { - [MiniExcelColumnName("CustomColumnName")] - public string? MyProperty1 { get; set; } //index = 1 - [MiniExcelIgnore] - public string? MyProperty7 { get; set; } //index = null - public string? MyProperty2 { get; set; } //index = 3 - [MiniExcelColumnIndex(6)] - public string? MyProperty3 { get; set; } //index = 6 - [MiniExcelColumnIndex("A")] // equal column index 0 - public string? MyProperty4 { get; set; } - [MiniExcelColumnIndex(2)] - public string? MyProperty5 { get; set; } //index = 2 - public string? MyProperty6 { get; set; } //index = 4 - } - - private class Issue241Dto + // CSV support export from datareader + [Fact] + public async Task Issue251() { - public string? Name { get; set; } + await using var cn = Db.GetConnection(); + var reader = await cn.ExecuteReaderAsync(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); + + using var path = AutoDeletingPath.Create(ExcelType.Csv); + var rowsWritten = await _csvExporter.ExportAsync(path.ToString(), reader); + + Assert.Equal(2, rowsWritten); + + const string expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 - [MiniExcelFormat("MM dd, yyyy")] - public DateTime InDate { get; set; } + """"; + + Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); } - - private class Issue243Dto + + // Csv export from datareader with default encoding shows incorrect result + [Fact] + public async Task Issue253() { - public string? Name { get; set; } - public int Age { get; set; } - public DateTime InDate { get; set; } + { + var value = new[] { new { col1 = "世界你好" } }; + using var path = AutoDeletingPath.Create(ExcelType.Csv); + + await _csvExporter.ExportAsync(path.ToString(), value); + const string expected = + """ + col1 + 世界你好 + + """; + + Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); + } + + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + var value = new[] { new { col1 = "世界你好" } }; + using var path = AutoDeletingPath.Create(ExcelType.Csv); + + var config = new CsvConfiguration + { + StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) + }; + + await _csvExporter.ExportAsync(path.ToString(), value, configuration: config); + const string expected = + """ + col1 + ������� + + """; + + Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); + } + + await using var cn = Db.GetConnection(); + + { + var value = await cn.ExecuteReaderAsync("select '世界你好' col1"); + using var path = AutoDeletingPath.Create(ExcelType.Csv); + await _csvExporter.ExportAsync(path.ToString(), value); + const string expected = + """ + col1 + 世界你好 + + """; + + Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); + } } - #endregion } diff --git a/tests/MiniExcel.Csv.Tests/IssueTests.cs b/tests/MiniExcel.Csv.Tests/Issues/GithubIssuesTests.cs similarity index 66% rename from tests/MiniExcel.Csv.Tests/IssueTests.cs rename to tests/MiniExcel.Csv.Tests/Issues/GithubIssuesTests.cs index 1a43a316..90f2dc0f 100644 --- a/tests/MiniExcel.Csv.Tests/IssueTests.cs +++ b/tests/MiniExcel.Csv.Tests/Issues/GithubIssuesTests.cs @@ -1,8 +1,6 @@ -using MiniExcelLib.OpenXml; +namespace MiniExcelLib.Csv.Tests.Issues; -namespace MiniExcelLib.Csv.Tests; - -public class IssueTests +public class GithubIssuesTests { private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); @@ -10,138 +8,165 @@ public class IssueTests private readonly OpenXmlExporter _openXmlExporter = MiniExcel.Exporters.GetOpenXmlExporter(); private readonly OpenXmlImporter _openXmlImporter = MiniExcel.Importers.GetOpenXmlImporter(); + // Support for Enum Mapping [Fact] - public void TestPullRequest10() + public void Issue89() { - var path = PathHelper.GetFile("csv/TestIssue142.csv"); - var config = new CsvConfiguration - { - SplitFn = row => Regex.Split(row, "[\t,](?=(?:[^\"]|\"[^\"]*\")*$)") - .Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")) - .ToArray() - }; - - var rows = _csvImporter.Query(path, configuration: config).ToList(); - Assert.Equal(2, rows.Count); - Assert.Equal(15, ((IDictionary)rows[0]).Count); - } + const string text = + """ + State + OnDuty + Fired + Leave + """; + + using var stream = new MemoryStream(); + using var writer = new StreamWriter(stream); + + writer.Write(text); + writer.Flush(); + stream.Position = 0; + var rows = _csvImporter.Query(stream, hasHeaderRow: true).ToList(); + + Assert.Equal(nameof(Issue89Dto.WorkState.OnDuty), rows[0].State); + Assert.Equal(nameof(Issue89Dto.WorkState.Fired), rows[1].State); + Assert.Equal(nameof(Issue89Dto.WorkState.Leave), rows[2].State); - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I49RZH - /// https://github.com/mini-software/MiniExcel/issues/305 - /// - [Fact] - public void TestIssue305() - { using var path = AutoDeletingPath.Create(ExcelType.Csv); - var value = new[] - { - new TestIssue305Dto{ Dt = DateTimeOffset.Parse("2022-01-22")}, - new TestIssue305Dto{ Dt = null} - }; - _csvExporter.Export(path.ToString(), value); + _csvExporter.Export(path.ToString(), rows); + var rows2 = _csvImporter.Query(path.ToString()).ToList(); - var rows = _csvImporter.Query(path.ToString()).ToList(); - Assert.Equal("2022-01-22", rows[1].A); + Assert.Equal(Issue89Dto.WorkState.OnDuty, rows2[0].State); + Assert.Equal(Issue89Dto.WorkState.Fired, rows2[1].State); + Assert.Equal(Issue89Dto.WorkState.Leave, rows2[2].State); } - private class TestIssue305Dto - { - [MiniExcelFormat("yyyy-MM-dd")] - public DateTimeOffset? Dt { get; set; } - } - - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I4X92G - /// [Fact] - public void TestIssueI4X92G() + public void Issue142() { - using var file = AutoDeletingPath.Create(ExcelType.Csv); - var path = file.ToString(); - { - var value = new[] + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + Issue142Dto[] values = + [ + new() + { + MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", + MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", + MyProperty7 = "MyProperty7" + } + ]; + var rowsWritten = _openXmlExporter.Export(path, values); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); + { - new { ID = 1, Name = "Jack", InDate = new DateTime(2021,01,03)}, - new { ID = 2, Name = "Henry", InDate = new DateTime(2020,05,03)} - }; - _csvExporter.Export(path, value); - var content = File.ReadAllText(path); - Assert.Equal( - """ - ID,Name,InDate - 1,Jack,"2021-01-03 00:00:00" - 2,Henry,"2020-05-03 00:00:00" + var rows = _openXmlImporter.Query(path).ToList(); + + Assert.Equal("MyProperty4", rows[0].A); + Assert.Equal("CustomColumnName", rows[0].B); + Assert.Equal("MyProperty5", rows[0].C); + Assert.Equal("MyProperty2", rows[0].D); + Assert.Equal("MyProperty6", rows[0].E); + Assert.Null(rows[0].F); + Assert.Equal("MyProperty3", rows[0].G); + + Assert.Equal("MyProperty4", rows[0].A); + Assert.Equal("CustomColumnName", rows[0].B); + Assert.Equal("MyProperty5", rows[0].C); + Assert.Equal("MyProperty2", rows[0].D); + Assert.Equal("MyProperty6", rows[0].E); + Assert.Null(rows[0].F); + Assert.Equal("MyProperty3", rows[0].G); + } - """, - content); + { + var rows = _openXmlImporter.Query(path).ToList(); + + Assert.Equal("MyProperty4", rows[0].MyProperty4); + Assert.Equal("MyProperty1", rows[0].MyProperty1); + Assert.Equal("MyProperty5", rows[0].MyProperty5); + Assert.Equal("MyProperty2", rows[0].MyProperty2); + Assert.Equal("MyProperty6", rows[0].MyProperty6); + Assert.Null(rows[0].MyProperty7); + Assert.Equal("MyProperty3", rows[0].MyProperty3); + } } + { - var value = new { ID = 3, Name = "Mike", InDate = new DateTime(2021, 04, 23) }; - var rowsWritten = _csvExporter.Append(path, value); + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + Issue142Dto[] values = + [ + new() + { + MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", + MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", + MyProperty7 = "MyProperty7" + } + ]; + var rowsWritten = _csvExporter.Export(path, values); Assert.Equal(1, rowsWritten); - var content = File.ReadAllText(path); - Assert.Equal( + const string expected = """ - ID,Name,InDate - 1,Jack,"2021-01-03 00:00:00" - 2,Henry,"2020-05-03 00:00:00" - 3,Mike,"2021-04-23 00:00:00" + MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + + """; + + Assert.Equal(expected, File.ReadAllText(path)); - """, - content); - } - { - var value = new[] { - new { ID=4,Name ="Frank",InDate=new DateTime(2021,06,07)}, - new { ID=5,Name ="Gloria",InDate=new DateTime(2022,05,03)}, - }; - var rowsWritten = _csvExporter.Append(path, value); - Assert.Equal(2, rowsWritten); + var rows = _csvImporter.Query(path).ToList(); - var content = File.ReadAllText(path); - Assert.Equal( - """ - ID,Name,InDate - 1,Jack,"2021-01-03 00:00:00" - 2,Henry,"2020-05-03 00:00:00" - 3,Mike,"2021-04-23 00:00:00" - 4,Frank,"2021-06-07 00:00:00" - 5,Gloria,"2022-05-03 00:00:00" - - """, - content); + Assert.Equal("MyProperty4", rows[0].MyProperty4); + Assert.Equal("MyProperty1", rows[0].MyProperty1); + Assert.Equal("MyProperty5", rows[0].MyProperty5); + Assert.Equal("MyProperty2", rows[0].MyProperty2); + Assert.Equal("MyProperty6", rows[0].MyProperty6); + Assert.Null(rows[0].MyProperty7); + Assert.Equal("MyProperty3", rows[0].MyProperty3); + } } - } - private class TestIssue316Dto - { - public decimal Amount { get; set; } - public DateTime CreateTime { get; set; } + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + Issue142DuplicateColumnNameDto[] input = + [ + new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } + ]; + Assert.Throws(() => _csvExporter.Export(path.ToString(), input)); + } } - /// - /// Using stream.SaveAs will close the Stream automatically when Specifying excelType - /// https://gitee.com/dotnetchina/MiniExcel/issues/I57WMM - /// [Fact] - public void TestIssueGiteeI57Dto() + public void Issue142_Query() { - Dictionary[] sheets = [new() { ["ID"] = "0001", ["Name"] = "Jack" }]; - using var stream = new MemoryStream(); - - var config = new CsvConfiguration { StreamWriterFunc = x => new StreamWriter(x, Encoding.Default, leaveOpen: true) }; - _csvExporter.Export(stream, sheets, configuration: config); - stream.Seek(0, SeekOrigin.Begin); + var path = PathHelper.GetFile("xlsx/TestIssue142.xlsx"); + var csvPath = PathHelper.GetFile("csv/TestIssue142.csv"); + + var rows = _openXmlImporter.Query(path).ToList(); + Assert.Equal(0, rows[0].MyProperty1); + Assert.Throws(() => _openXmlImporter.Query(path).ToList()); - // convert stream to string - using var reader = new StreamReader(stream); - var text = reader.ReadToEnd(); + var rowsXlsx = _openXmlImporter.Query(path).ToList(); + Assert.Equal("CustomColumnName", rowsXlsx[0].MyProperty1); + Assert.Null(rowsXlsx[0].MyProperty7); + Assert.Equal("MyProperty2", rowsXlsx[0].MyProperty2); + Assert.Equal("MyProperty103", rowsXlsx[0].MyProperty3); + Assert.Equal("MyProperty100", rowsXlsx[0].MyProperty4); + Assert.Equal("MyProperty102", rowsXlsx[0].MyProperty5); + Assert.Equal("MyProperty6", rowsXlsx[0].MyProperty6); - Assert.Equal("ID,Name\r\n0001,Jack\r\n", text); + var rowsCsv = _csvImporter.Query(csvPath).ToList(); + Assert.Equal("CustomColumnName", rowsCsv[0].MyProperty1); + Assert.Null(rowsCsv[0].MyProperty7); + Assert.Equal("MyProperty2", rowsCsv[0].MyProperty2); + Assert.Equal("MyProperty103", rowsCsv[0].MyProperty3); + Assert.Equal("MyProperty100", rowsCsv[0].MyProperty4); + Assert.Equal("MyProperty102", rowsCsv[0].MyProperty5); + Assert.Equal("MyProperty6", rowsCsv[0].MyProperty6); } [Fact] @@ -153,8 +178,6 @@ public void Issue217() table.Columns.Add("CreditLimit").Caption = "Limit"; table.Rows.Add(1, "Jonathan", 23.44); table.Rows.Add(2, "Bill", 56.87); - - using var path = AutoDeletingPath.Create(ExcelType.Csv); _csvExporter.Export(path.ToString(), table); @@ -164,10 +187,7 @@ public void Issue217() Assert.Equal("Limit", rows[0].C); } - /// - /// Csv Query split comma not correct #237 - /// https://github.com/mini-software/MiniExcel/issues/237 - /// + /// Csv Query split comma not correct [Fact] public void Issue237() { @@ -186,9 +206,7 @@ public void Issue237() Assert.Equal("1,2,3", rows[1].id); } - /// - /// Support Custom Datetime format #241 - /// + /// Support Custom Datetime format [Fact] public void Issue241() { @@ -209,19 +227,8 @@ public void Issue241() Assert.Equal(rows2[0].InDate, new DateTime(2021, 01, 04)); Assert.Equal(rows2[1].InDate, new DateTime(2020, 04, 05)); } - - private class Issue241Dto - { - public string? Name { get; set; } - [MiniExcelFormat("MM dd, yyyy")] - public DateTime InDate { get; set; } - } - - - /// - /// Csv type mapping Query error "cannot be converted to xxx type" #243 - /// + // Csv type mapping Query error [Fact] public void Issue243() { @@ -243,181 +250,112 @@ public void Issue243() Assert.Equal(new DateTime(2020, 05, 03), rows[1].InDate); } - private class Issue243Dto - { - public string? Name { get; set; } - public int Age { get; set; } - public DateTime InDate { get; set; } - } - - - /// - /// https://github.com/mini-software/MiniExcel/issues/312 - /// + // CSV support export from datareader [Fact] - public void TestIssue312() + public void Issue251() { + using var cn = Db.GetConnection(); + using var reader = cn.ExecuteReader(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); using var path = AutoDeletingPath.Create(ExcelType.Csv); - TestIssue312Dto[] value = - [ - new() { Value = 12345.6789}, - new() { Value = null} - ]; - _csvExporter.Export(path.ToString(), value); + _csvExporter.Export(path.ToString(), reader); + const string expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 - var rows = _csvImporter.Query(path.ToString()).ToList(); - Assert.Equal("12,345.68", rows[1].A); - } + """"; - private class TestIssue312Dto - { - [MiniExcelFormat("0,0.00")] - public double? Value { get; set; } + Assert.Equal(expected, File.ReadAllText(path.ToString())); } - + + // Csv SaveAs by datareader with default encoding default shows incorrect characters [Fact] - public async Task TestIssue338() + public void Issue253() { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); { - var path = PathHelper.GetFile("csv/TestIssue338.csv"); - var row = _csvImporter.QueryAsync(path).ToBlockingEnumerable().FirstOrDefault(); - Assert.Equal("���IJ�������", row!.A); + var value = new[] { new { col1 = "世界你好" } }; + using var path = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.Export(path.ToString(), value); + const string expected = + """ + col1 + 世界你好 + + """; + Assert.Equal(expected, File.ReadAllText(path.ToString())); } + { - var path = PathHelper.GetFile("csv/TestIssue338.csv"); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + var value = new[] { new { col1 = "世界你好" } }; + using var path = AutoDeletingPath.Create(ExcelType.Csv); var config = new CsvConfiguration { - StreamReaderFunc = stream => new StreamReader(stream, Encoding.GetEncoding("gb2312")) + StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) }; - var row = _csvImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable().FirstOrDefault(); - Assert.Equal("中文测试内容", row!.A); + _csvExporter.Export(path.ToString(), value, configuration: config); + const string expected = + """ + col1 + ������� + + """; + Assert.Equal(expected, File.ReadAllText(path.ToString())); } + + using var cn = Db.GetConnection(); + { - var path = PathHelper.GetFile("csv/TestIssue338.csv"); - var config = new CsvConfiguration - { - StreamReaderFunc = stream => new StreamReader(stream, Encoding.GetEncoding("gb2312")) - }; - await using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) - { - var row = _csvImporter.QueryAsync(stream, configuration: config).ToBlockingEnumerable().FirstOrDefault(); - Assert.Equal("中文测试内容", row!.A); - } + var value = cn.ExecuteReader("select '世界你好' col1"); + using var path = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.Export(path.ToString(), value); + const string expected = + """ + col1 + 世界你好 + + """; + Assert.Equal(expected, File.ReadAllText(path.ToString())); } } [Fact] - public void TestIssueI4Wda9() + public void TestIssue261() { - using var path = AutoDeletingPath.Create(ExcelType.Csv); - var value = new DataTable(); - { - value.Columns.Add("\"name\""); - value.Rows.Add("\"Jack\""); - } + var csvPath = PathHelper.GetFile("csv/TestCsvToXlsx.csv"); + using var path = AutoDeletingPath.Create(); - _csvExporter.Export(path.ToString(), value); - Assert.Equal("\"\"\"name\"\"\"\r\n\"\"\"Jack\"\"\"\r\n", File.ReadAllText(path.ToString())); - } + MiniExcelConverter.ConvertCsvToXlsx(csvPath, path.FilePath); + var rows = _openXmlImporter.Query(path.ToString()).ToList(); - [Fact] - public void TestIssue316() - { - // CSV - { - using var file = AutoDeletingPath.Create(ExcelType.Csv); - var path = file.ToString(); - var value = new[] - { - new { Amount = 123_456.789M, CreateTime = new DateTime(2018, 1, 31) } - }; - - var config = new CsvConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - _csvExporter.Export(path, value, configuration: config); - - //Datetime error - Assert.Throws(() => - { - var conf = new CsvConfiguration - { - Culture = new CultureInfo("en-US") - }; - _ = _csvImporter.Query(path, configuration: conf).ToList(); - }); - - // dynamic - var rows = _csvImporter.Query(path, true).ToList(); - Assert.Equal("123456,789", rows[0].Amount); - Assert.Equal("31/01/2018 00:00:00", rows[0].CreateTime); - } - - // type - { - using var file = AutoDeletingPath.Create(ExcelType.Csv); - var path = file.ToString(); - - var value = new[] - { - new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-05-12", CultureInfo.InvariantCulture)} - }; - { - var config = new CsvConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - _csvExporter.Export(path, value, configuration: config); - } - - { - var rows = _csvImporter.Query(path, true).ToList(); - Assert.Equal("123456,789", rows[0].Amount); - Assert.Equal("12/05/2018 00:00:00", rows[0].CreateTime); - } - - { - var config = new CsvConfiguration - { - Culture = new CultureInfo("en-US"), - }; - var rows = _csvImporter.Query(path, configuration: config).ToList(); - - Assert.Equal("2018-12-05 00:00:00", rows[0].CreateTime.ToString("yyyy-MM-dd HH:mm:ss")); - Assert.Equal(123456789m, rows[0].Amount); - } - - { - var config = new CsvConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - var rows = _csvImporter.Query(path, configuration: config).ToList(); - - Assert.Equal("2018-05-12 00:00:00", rows[0].CreateTime.ToString("yyyy-MM-dd HH:mm:ss")); - Assert.Equal(123456.789m, rows[0].Amount); - } - } + Assert.Equal("Name", rows[0].A); + Assert.Equal("Jack", rows[1].A); + Assert.Equal("Neo", rows[2].A); + Assert.Null(rows[3].A); + Assert.Null(rows[4].A); + Assert.Equal("Age", rows[0].B); + Assert.Equal("34", rows[1].B); + Assert.Equal("26", rows[2].B); + Assert.Null(rows[3].B); + Assert.Null(rows[4].B); } - /// - /// Column '' does not belong to table when csv convert to datatable #298 - /// https://github.com/mini-software/MiniExcel/issues/298 - /// + // Csv support for QueryAsDataTable [Fact] - public void TestIssue298() + public void TestIssue279() { - var path = PathHelper.GetFile("/csv/TestIssue298.csv"); + var path = PathHelper.GetFile("/csv/TestHeader.csv"); #pragma warning disable CS0618 // Type or member is obsolete - var dt = _csvImporter.QueryAsDataTable(path); + using var dt = _csvImporter.QueryAsDataTable(path); #pragma warning restore CS0618 - Assert.Equal(["ID", "Name", "Age"], dt.Columns.Cast().Select(x => x.ColumnName)); + Assert.Equal("A1", dt.Rows[0]["Column1"]); + Assert.Equal("A2", dt.Rows[1]["Column1"]); + Assert.Equal("B1", dt.Rows[0]["Column2"]); + Assert.Equal("B2", dt.Rows[1]["Column2"]); } - /// - /// [According to the XLSX to CSV example, there will be data loss if there is no header. · Issue #292 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/292) - /// + + // According to the XLSX to CSV example, there will be data loss if there is no header. [Fact] public void TestIssue292() { @@ -455,9 +393,7 @@ public void TestIssue292() } } - /// - /// [Csv Query then SaveAs will throw "Stream was not readable." exception · Issue #293 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/293) - /// + // Csv Query then SaveAs will throw exception "Stream was not readable." [Fact] public void TestIssue293() { @@ -468,353 +404,166 @@ public void TestIssue293() _openXmlExporter.Export(tempPath.ToString(), value, printHeader: false); } - /// - /// Csv not support QueryAsDataTable #279 https://github.com/mini-software/MiniExcel/issues/279 - /// + // Column '' does not belong to table when csv is converted to datatable [Fact] - public void TestIssue279() + public void TestIssue298() { - var path = PathHelper.GetFile("/csv/TestHeader.csv"); + var path = PathHelper.GetFile("/csv/TestIssue298.csv"); #pragma warning disable CS0618 // Type or member is obsolete using var dt = _csvImporter.QueryAsDataTable(path); #pragma warning restore CS0618 - Assert.Equal("A1", dt.Rows[0]["Column1"]); - Assert.Equal("A2", dt.Rows[1]["Column1"]); - Assert.Equal("B1", dt.Rows[0]["Column2"]); - Assert.Equal("B2", dt.Rows[1]["Column2"]); - } - - /// - /// [Convert csv to xlsx · Issue #261 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/261) - /// - [Fact] - public void TestIssue261() - { - var csvPath = PathHelper.GetFile("csv/TestCsvToXlsx.csv"); - using var path = AutoDeletingPath.Create(); - - MiniExcelConverter.ConvertCsvToXlsx(csvPath, path.FilePath); - var rows = _openXmlImporter.Query(path.ToString()).ToList(); - - Assert.Equal("Name", rows[0].A); - Assert.Equal("Jack", rows[1].A); - Assert.Equal("Neo", rows[2].A); - Assert.Null(rows[3].A); - Assert.Null(rows[4].A); - Assert.Equal("Age", rows[0].B); - Assert.Equal("34", rows[1].B); - Assert.Equal("26", rows[2].B); - Assert.Null(rows[3].B); - Assert.Null(rows[4].B); - } - - /// - /// Csv SaveAs by datareader with encoding default show messy code #253 - /// - [Fact] - public void Issue253() - { - { - var value = new[] { new { col1 = "世界你好" } }; - using var path = AutoDeletingPath.Create(ExcelType.Csv); - _csvExporter.Export(path.ToString(), value); - const string expected = - """ - col1 - 世界你好 - - """; - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } - - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - var value = new[] { new { col1 = "世界你好" } }; - using var path = AutoDeletingPath.Create(ExcelType.Csv); - var config = new CsvConfiguration - { - StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) - }; - _csvExporter.Export(path.ToString(), value, configuration: config); - const string expected = - """ - col1 - ������� - - """; - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } - - using var cn = Db.GetConnection(); - - { - var value = cn.ExecuteReader("select '世界你好' col1"); - using var path = AutoDeletingPath.Create(ExcelType.Csv); - _csvExporter.Export(path.ToString(), value); - const string expected = - """ - col1 - 世界你好 - - """; - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } + Assert.Equal(["ID", "Name", "Age"], dt.Columns.Cast().Select(x => x.ColumnName)); } - /// - /// [CSV SaveAs support datareader · Issue #251 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/251) - /// [Fact] - public void Issue251() + public void TestIssue305() { - using var cn = Db.GetConnection(); - using var reader = cn.ExecuteReader(@"select '""<>+-*//}{\\n' a,1234567890 b union all select 'Hello World',-1234567890"); using var path = AutoDeletingPath.Create(ExcelType.Csv); - _csvExporter.Export(path.ToString(), reader); - const string expected = - """" - a,b - """<>+-*//}{\\n",1234567890 - "Hello World",-1234567890 - - """"; - - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } - - public class Issue89Model - { - public WorkState State { get; set; } - - public enum WorkState + var value = new[] { - OnDuty, - Leave, - Fired - } + new TestIssue305Dto{ Dt = DateTimeOffset.Parse("2022-01-22")}, + new TestIssue305Dto{ Dt = null} + }; + _csvExporter.Export(path.ToString(), value); + + var rows = _csvImporter.Query(path.ToString()).ToList(); + Assert.Equal("2022-01-22", rows[1].A); } - /// - /// Support Enum Mapping - /// https://github.com/mini-software/MiniExcel/issues/89 - /// [Fact] - public void Issue89() + public void TestIssue312() { - //csv - const string text = - """ - State - OnDuty - Fired - Leave - """; - - using var stream = new MemoryStream(); - using var writer = new StreamWriter(stream); - - writer.Write(text); - writer.Flush(); - stream.Position = 0; - var rows = _csvImporter.Query(stream, hasHeaderRow: true).ToList(); - - Assert.Equal(nameof(Issue89Model.WorkState.OnDuty), rows[0].State); - Assert.Equal(nameof(Issue89Model.WorkState.Fired), rows[1].State); - Assert.Equal(nameof(Issue89Model.WorkState.Leave), rows[2].State); - using var path = AutoDeletingPath.Create(ExcelType.Csv); - _csvExporter.Export(path.ToString(), rows); - var rows2 = _csvImporter.Query(path.ToString()).ToList(); - - Assert.Equal(Issue89Model.WorkState.OnDuty, rows2[0].State); - Assert.Equal(Issue89Model.WorkState.Fired, rows2[1].State); - Assert.Equal(Issue89Model.WorkState.Leave, rows2[2].State); - } - - private class Issue142VoDuplicateColumnName - { - [MiniExcelColumnIndex("A")] - public int MyProperty1 { get; set; } - [MiniExcelColumnIndex("A")] - public int MyProperty2 { get; set; } - - public int MyProperty3 { get; set; } - [MiniExcelColumnIndex("B")] - public int MyProperty4 { get; set; } - } + TestIssue312Dto[] value = + [ + new() { Value = 12345.6789}, + new() { Value = null} + ]; + _csvExporter.Export(path.ToString(), value); - private class Issue142Model - { - [MiniExcelColumnName("CustomColumnName")] - public string? MyProperty1 { get; set; } //index = 1 - [MiniExcelIgnore] - public string? MyProperty7 { get; set; } //index = null - public string? MyProperty2 { get; set; } //index = 3 - [MiniExcelColumnIndex(6)] - public string? MyProperty3 { get; set; } //index = 6 - [MiniExcelColumnIndex("A")] // equal column index 0 - public string? MyProperty4 { get; set; } - [MiniExcelColumnIndex(2)] - public string? MyProperty5 { get; set; } //index = 2 - public string? MyProperty6 { get; set; } //index = 4 + var rows = _csvImporter.Query(path.ToString()).ToList(); + Assert.Equal("12,345.68", rows[1].A); } [Fact] - public void Issue142() + public void TestIssue316() { { - using var file = AutoDeletingPath.Create(); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - Issue142Model[] values = - [ - new() - { - MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", - MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", - MyProperty7 = "MyProperty7" - } - ]; - var rowsWritten = _openXmlExporter.Export(path, values); - Assert.Single(rowsWritten); - Assert.Equal(1, rowsWritten[0]); - + var value = new[] { - var rows = _openXmlImporter.Query(path).ToList(); - - Assert.Equal("MyProperty4", rows[0].A); - Assert.Equal("CustomColumnName", rows[0].B); - Assert.Equal("MyProperty5", rows[0].C); - Assert.Equal("MyProperty2", rows[0].D); - Assert.Equal("MyProperty6", rows[0].E); - Assert.Null(rows[0].F); - Assert.Equal("MyProperty3", rows[0].G); + new { Amount = 123_456.789M, CreateTime = new DateTime(2018, 1, 31) } + }; - Assert.Equal("MyProperty4", rows[0].A); - Assert.Equal("CustomColumnName", rows[0].B); - Assert.Equal("MyProperty5", rows[0].C); - Assert.Equal("MyProperty2", rows[0].D); - Assert.Equal("MyProperty6", rows[0].E); - Assert.Null(rows[0].F); - Assert.Equal("MyProperty3", rows[0].G); - } + var config = new CsvConfiguration + { + Culture = new CultureInfo("fr-FR"), + }; + _csvExporter.Export(path, value, configuration: config); + //Datetime error + Assert.Throws(() => { - var rows = _openXmlImporter.Query(path).ToList(); + var conf = new CsvConfiguration + { + Culture = new CultureInfo("en-US") + }; + _ = _csvImporter.Query(path, configuration: conf).ToList(); + }); - Assert.Equal("MyProperty4", rows[0].MyProperty4); - Assert.Equal("MyProperty1", rows[0].MyProperty1); - Assert.Equal("MyProperty5", rows[0].MyProperty5); - Assert.Equal("MyProperty2", rows[0].MyProperty2); - Assert.Equal("MyProperty6", rows[0].MyProperty6); - Assert.Null(rows[0].MyProperty7); - Assert.Equal("MyProperty3", rows[0].MyProperty3); - } + // dynamic + var rows = _csvImporter.Query(path, true).ToList(); + Assert.Equal("123456,789", rows[0].Amount); + Assert.Equal("31/01/2018 00:00:00", rows[0].CreateTime); } + // type { using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - Issue142Model[] values = - [ - new() + + var value = new[] + { + new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-05-12", CultureInfo.InvariantCulture)} + }; + { + var config = new CsvConfiguration { - MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", - MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", - MyProperty7 = "MyProperty7" - } - ]; - var rowsWritten = _csvExporter.Export(path, values); - Assert.Equal(1, rowsWritten); + Culture = new CultureInfo("fr-FR"), + }; + _csvExporter.Export(path, value, configuration: config); + } - const string expected = - """ - MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 - MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + { + var rows = _csvImporter.Query(path, true).ToList(); + Assert.Equal("123456,789", rows[0].Amount); + Assert.Equal("12/05/2018 00:00:00", rows[0].CreateTime); + } - """; + { + var config = new CsvConfiguration + { + Culture = new CultureInfo("en-US"), + }; + var rows = _csvImporter.Query(path, configuration: config).ToList(); - Assert.Equal(expected, File.ReadAllText(path)); + Assert.Equal("2018-12-05 00:00:00", rows[0].CreateTime.ToString("yyyy-MM-dd HH:mm:ss")); + Assert.Equal(123456789m, rows[0].Amount); + } { - var rows = _csvImporter.Query(path).ToList(); + var config = new CsvConfiguration + { + Culture = new CultureInfo("fr-FR"), + }; + var rows = _csvImporter.Query(path, configuration: config).ToList(); - Assert.Equal("MyProperty4", rows[0].MyProperty4); - Assert.Equal("MyProperty1", rows[0].MyProperty1); - Assert.Equal("MyProperty5", rows[0].MyProperty5); - Assert.Equal("MyProperty2", rows[0].MyProperty2); - Assert.Equal("MyProperty6", rows[0].MyProperty6); - Assert.Null(rows[0].MyProperty7); - Assert.Equal("MyProperty3", rows[0].MyProperty3); + Assert.Equal("2018-05-12 00:00:00", rows[0].CreateTime.ToString("yyyy-MM-dd HH:mm:ss")); + Assert.Equal(123456.789m, rows[0].Amount); } } - - { - using var path = AutoDeletingPath.Create(ExcelType.Csv); - Issue142VoDuplicateColumnName[] input = - [ - new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } - ]; - Assert.Throws(() => _csvExporter.Export(path.ToString(), input)); - } } - + [Fact] - public void Issue142_Query() - { - var path = PathHelper.GetFile("xlsx/TestIssue142.xlsx"); - var csvPath = PathHelper.GetFile("csv/TestIssue142.csv"); - - var rows = _openXmlImporter.Query(path).ToList(); - Assert.Equal(0, rows[0].MyProperty1); - Assert.Throws(() => _openXmlImporter.Query(path).ToList()); - - var rowsXlsx = _openXmlImporter.Query(path).ToList(); - Assert.Equal("CustomColumnName", rowsXlsx[0].MyProperty1); - Assert.Null(rowsXlsx[0].MyProperty7); - Assert.Equal("MyProperty2", rowsXlsx[0].MyProperty2); - Assert.Equal("MyProperty103", rowsXlsx[0].MyProperty3); - Assert.Equal("MyProperty100", rowsXlsx[0].MyProperty4); - Assert.Equal("MyProperty102", rowsXlsx[0].MyProperty5); - Assert.Equal("MyProperty6", rowsXlsx[0].MyProperty6); - - var rowsCsv = _csvImporter.Query(csvPath).ToList(); - Assert.Equal("CustomColumnName", rowsCsv[0].MyProperty1); - Assert.Null(rowsCsv[0].MyProperty7); - Assert.Equal("MyProperty2", rowsCsv[0].MyProperty2); - Assert.Equal("MyProperty103", rowsCsv[0].MyProperty3); - Assert.Equal("MyProperty100", rowsCsv[0].MyProperty4); - Assert.Equal("MyProperty102", rowsCsv[0].MyProperty5); - Assert.Equal("MyProperty6", rowsCsv[0].MyProperty6); - } - - private class Issue142VoOverIndex - { - [MiniExcelColumnIndex("Z")] - public int MyProperty1 { get; set; } - } - - private class Issue142VoExcelColumnNameNotFound - { - [MiniExcelColumnIndex("B")] - public int MyProperty1 { get; set; } - } - - private class Issue507V01 + public async Task TestIssue338() { - public string? A { get; set; } - public DateTime B { get; set; } - public string? C { get; set; } - public int D { get; set; } + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + { + var path = PathHelper.GetFile("csv/TestIssue338.csv"); + var row = _csvImporter.QueryAsync(path).ToBlockingEnumerable().FirstOrDefault(); + Assert.Equal("���IJ�������", row!.A); + } + { + var path = PathHelper.GetFile("csv/TestIssue338.csv"); + var config = new CsvConfiguration + { + StreamReaderFunc = stream => new StreamReader(stream, Encoding.GetEncoding("gb2312")) + }; + var row = _csvImporter.QueryAsync(path, configuration: config).ToBlockingEnumerable().FirstOrDefault(); + Assert.Equal("中文测试内容", row!.A); + } + { + var path = PathHelper.GetFile("csv/TestIssue338.csv"); + var config = new CsvConfiguration + { + StreamReaderFunc = stream => new StreamReader(stream, Encoding.GetEncoding("gb2312")) + }; + await using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + var row = _csvImporter.QueryAsync(stream, configuration: config).ToBlockingEnumerable().FirstOrDefault(); + Assert.Equal("中文测试内容", row!.A); + } + } } - [Fact] public void Issue507_1() { //Problem with multi-line when using Query func //https://github.com/mini-software/MiniExcel/issues/507 - var path = Path.Combine(Path.GetTempPath(), string.Concat(nameof(IssueTests), "_", nameof(Issue507_1), ".csv")); + var path = Path.Combine(Path.GetTempPath(), string.Concat(nameof(GithubIssuesTests), "_", nameof(Issue507_1), ".csv")); var values = new Issue507V01[] { new() { A = "Github", B = DateTime.Parse("2021-01-01"), C = "abcd", D = 123 }, @@ -826,7 +575,7 @@ public void Issue507_1() var config = new CsvConfiguration { //AlwaysQuote = true, - ReadLineBreaksWithinQuotes = true, + ReadLineBreaksWithinQuotes = true }; // create @@ -855,19 +604,10 @@ public void Issue507_1() File.Delete(path); } - private class Issue507V02 - { - public DateTime B { get; set; } - public int D { get; set; } - } - [Fact] public void Issue507_2() { - //Problem with multi-line when using Query func - //https://github.com/mini-software/MiniExcel/issues/507 - - var path = Path.Combine(Path.GetTempPath(), string.Concat(nameof(IssueTests), "_", nameof(Issue507_2), ".csv")); + var path = Path.Combine(Path.GetTempPath(), string.Concat(nameof(GithubIssuesTests), "_", nameof(Issue507_2), ".csv")); var values = new Issue507V02[] { new() { B = DateTime.Parse("2021-01-01"), D = 123 }, @@ -895,12 +635,10 @@ public void Issue507_2() File.Delete(path); } + //Problem with multi-line when using Query func [Fact] public void Issue507_3_MismatchedQuoteCsv() { - //Problem with multi-line when using Query func - //https://github.com/mini-software/MiniExcel/issues/507 - var config = new CsvConfiguration { //AlwaysQuote = true, @@ -914,12 +652,6 @@ public void Issue507_3_MismatchedQuoteCsv() var getRowsInfo = _csvImporter.Query(stream, configuration: config).ToArray(); Assert.Equal(2, getRowsInfo.Length); } - - class NameAgeTuple - { - public string? Name { get; set; } - public int Age { get; set; } - } [Fact] public void Issue914() @@ -937,10 +669,10 @@ public void Issue914() """u8; using var ms = new MemoryStream([..csv]); - var result = _csvImporter.Query(ms).ToList(); + var result = _csvImporter.Query(ms, hasHeaderRow: true).ToList(); Assert.Equal(3, result.Count); Assert.Equal("Sam", result[1].Name); - Assert.Equal(44, result[2].Age); + Assert.Equal("44", result[2].Age); } } diff --git a/tests/MiniExcel.Csv.Tests/Issues/Models.cs b/tests/MiniExcel.Csv.Tests/Issues/Models.cs new file mode 100644 index 00000000..3cfc1ea4 --- /dev/null +++ b/tests/MiniExcel.Csv.Tests/Issues/Models.cs @@ -0,0 +1,100 @@ +namespace MiniExcelLib.Csv.Tests.Issues; + +internal class Issue89Dto +{ + public WorkState State { get; set; } + + public enum WorkState + { + OnDuty, + Leave, + Fired + } +} + +internal class Issue142Dto +{ + [MiniExcelColumnName("CustomColumnName")] + public string? MyProperty1 { get; set; } //index = 1 + [MiniExcelIgnore] + public string? MyProperty7 { get; set; } //index = null + public string? MyProperty2 { get; set; } //index = 3 + [MiniExcelColumnIndex(6)] + public string? MyProperty3 { get; set; } //index = 6 + [MiniExcelColumnIndex("A")] // equal column index 0 + public string? MyProperty4 { get; set; } + [MiniExcelColumnIndex(2)] + public string? MyProperty5 { get; set; } //index = 2 + public string? MyProperty6 { get; set; } //index = 4 +} + +internal class Issue142DuplicateColumnNameDto +{ + [MiniExcelColumnIndex("A")] + public int MyProperty1 { get; set; } + [MiniExcelColumnIndex("A")] + public int MyProperty2 { get; set; } + + public int MyProperty3 { get; set; } + [MiniExcelColumnIndex("B")] + public int MyProperty4 { get; set; } +} + +internal class Issue142OverIndexDto +{ + [MiniExcelColumnIndex("Z")] + public int MyProperty1 { get; set; } +} + +internal class Issue142ExcelColumnNameNotFoundDto +{ + [MiniExcelColumnIndex("B")] + public int MyProperty1 { get; set; } +} + +internal class Issue241Dto +{ + public string? Name { get; set; } + + [MiniExcelFormat("MM dd, yyyy")] + public DateTime InDate { get; set; } +} + +internal class Issue243Dto +{ + public string? Name { get; set; } + public int Age { get; set; } + public DateTime InDate { get; set; } +} + +internal class TestIssue305Dto +{ + [MiniExcelFormat("yyyy-MM-dd")] + public DateTimeOffset? Dt { get; set; } +} + +internal class TestIssue312Dto +{ + [MiniExcelFormat("0,0.00")] + public double? Value { get; set; } +} + +internal class TestIssue316Dto +{ + public decimal Amount { get; set; } + public DateTime CreateTime { get; set; } +} + +internal class Issue507V01 +{ + public string? A { get; set; } + public DateTime B { get; set; } + public string? C { get; set; } + public int D { get; set; } +} + +internal class Issue507V02 +{ + public DateTime B { get; set; } + public int D { get; set; } +} diff --git a/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsyncTests.cs b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs similarity index 97% rename from tests/MiniExcel.Csv.Tests/MiniExcelCsvAsyncTests.cs rename to tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs index 0c5b7b57..e4d90c2d 100644 --- a/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsyncTests.cs +++ b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvAsyncTests.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLib.Csv.Tests; +namespace MiniExcelLib.Csv.Tests.Main; public class MiniExcelCsvAsyncTests { diff --git a/tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs similarity index 96% rename from tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs rename to tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs index c9467d58..1d017a98 100644 --- a/tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs +++ b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLib.Csv.Tests; +namespace MiniExcelLib.Csv.Tests.Main; public class MiniExcelCsvTests { diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelAutoAdjustWidthTests.cs b/tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelAutoAdjustWidthTests.cs similarity index 80% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelAutoAdjustWidthTests.cs rename to tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelAutoAdjustWidthTests.cs index 7c201925..12449313 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelAutoAdjustWidthTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelAutoAdjustWidthTests.cs @@ -3,7 +3,7 @@ using MiniExcelLib.OpenXml.Models; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Configuration; public class MiniExcelAutoAdjustWidthTests { @@ -17,7 +17,7 @@ public async Task AutoAdjustWidthThrowsExceptionWithoutFastMode_Async() await Assert.ThrowsAsync(() => _excelExporter.ExportAsync(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: new OpenXmlConfiguration { - EnableAutoWidth = true, + EnableAutoWidth = true })); } @@ -164,39 +164,4 @@ private static void AssertExpectedWidth(string path, OpenXmlConfiguration config Assert.Equal(ExcelColumnWidth.GetWidthFromTextLength(expectedWidth), Math.Round(column.Width!.Value, 8)); } } - - private static class AutoAdjustTestParameters - { - internal const int Column1MaLen = 32; - internal const int Column2MaxLen = 16; - private const int Column3MaxLen = 2; - private const int Column4MaxLen = 100; - - public static List GetTestData() => [ - [ - new('1', Column1MaLen), - new('2', Column2MaxLen / 2), - new('3', Column3MaxLen / 2), - new('4', Column4MaxLen) - ], - [ - new('1', Column1MaLen / 2), - new('2', Column2MaxLen), - new('3', Column3MaxLen), - new('4', Column4MaxLen) - ] ]; - - public static List> GetDictionaryTestData() => GetTestData() - .Select(row => row - .Select((value, i) => (value, i)) - .ToDictionary(x => $"Column{x.i}", object (x) => x.value)) - .ToList(); - - public static OpenXmlConfiguration GetConfiguration() => new() - { - EnableAutoWidth = true, - FastMode = true, - MaxWidth = 50 - }; - } } diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlConfigurationTest.cs b/tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelOpenXmlConfigurationTests.cs similarity index 81% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlConfigurationTest.cs rename to tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelOpenXmlConfigurationTests.cs index ca540042..321986a0 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlConfigurationTest.cs +++ b/tests/MiniExcel.OpenXml.Tests/Configuration/MiniExcelOpenXmlConfigurationTests.cs @@ -1,7 +1,6 @@ -using MiniExcelLib.Core.Helpers; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Configuration; public class MiniExcelOpenXmlConfigurationTest { @@ -26,12 +25,4 @@ public async Task DisableWriteFilePathTest() var rows = await _excelImporter.QueryAsync(path).ToListAsync(); Assert.True(rows.All(x => x.Img is null or [])); } - - private class ImgExportTestDto - { - public string? Name { get; set; } - - [MiniExcelColumn(Name = "图片", Width = 100)] - public byte[]? Img { get; set; } - } } diff --git a/tests/MiniExcel.OpenXml.Tests/Configuration/Models.cs b/tests/MiniExcel.OpenXml.Tests/Configuration/Models.cs new file mode 100644 index 00000000..7e032ae2 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Configuration/Models.cs @@ -0,0 +1,44 @@ +namespace MiniExcelLib.OpenXml.Tests.Configuration; + +internal static class AutoAdjustTestParameters +{ + internal const int Column1MaLen = 32; + internal const int Column2MaxLen = 16; + private const int Column3MaxLen = 2; + private const int Column4MaxLen = 100; + + public static List GetTestData() => [ + [ + new('1', Column1MaLen), + new('2', Column2MaxLen / 2), + new('3', Column3MaxLen / 2), + new('4', Column4MaxLen) + ], + [ + new('1', Column1MaLen / 2), + new('2', Column2MaxLen), + new('3', Column3MaxLen), + new('4', Column4MaxLen) + ] ]; + + public static List> GetDictionaryTestData() => GetTestData() + .Select(row => row + .Select((value, i) => (value, i)) + .ToDictionary(x => $"Column{x.i}", object (x) => x.value)) + .ToList(); + + public static OpenXmlConfiguration GetConfiguration() => new() + { + EnableAutoWidth = true, + FastMode = true, + MaxWidth = 50 + }; +} + +internal class ImgExportTestDto +{ + public string? Name { get; set; } + + [MiniExcelColumn(Name = "图片", Width = 100)] + public byte[]? Img { get; set; } +} diff --git a/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesAsyncTests.cs new file mode 100644 index 00000000..40aeb229 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesAsyncTests.cs @@ -0,0 +1,29 @@ +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.Issues; + +public class MiniExcelGiteeIssuesAsyncTests +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + + // https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV + // When exporting, the pure numeric string will be forcibly converted to a numeric type, resulting in the loss of the end data + [Fact] + public async Task IssueI3OSKV() + { + using var path1 = AutoDeletingPath.Create(); + var value1 = new[] { new { Test = "12345678901234567890" } }; + await _excelExporter.ExportAsync(path1.ToString(), value1); + + var result1 = await _excelImporter.QueryAsync(path1.ToString(), true).FirstAsync(); + Assert.Equal("12345678901234567890", result1.Test); + + using var path2 = AutoDeletingPath.Create(); + var value2 = new[] { new { Test = 123456.789 } }; + await _excelExporter.ExportAsync(path2.ToString(), value2); + + var result2 = await _excelImporter.QueryAsync(path2.ToString(), true).FirstAsync(); + Assert.Equal(123456.789, result2.Test); + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesTests.cs b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesTests.cs new file mode 100644 index 00000000..fdbd8dcb --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGiteeIssuesTests.cs @@ -0,0 +1,329 @@ +using MiniExcelLib.OpenXml.Tests.Utils; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.Issues; + +public class MiniExcelGiteeIssuesTests +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + + [Fact] + public void TestIssueI4ZYUU() + { + using var path = AutoDeletingPath.Create(); + + var dt = new DateTime(2022, 10, 15); + TestIssueI4ZYUUDto[] value = [new() { MyProperty = "1", MyProperty2 = dt }]; + _excelExporter.Export(path.ToString(), value); + + using var workbook = new ClosedXML.Excel.XLWorkbook(path.ToString()); + var ws = workbook.Worksheet(1); + + Assert.Equal(dt, ws.Cell(2, "B").Value.GetDateTime()); + Assert.Equal("2022-10", ws.Cell(2, "B").GetFormattedString()); + Assert.True(ws.Column("A").Width > 0); + Assert.True(ws.Column("B").Width > 0); + } + + [Fact] + public void TestIssueI4YCLQ_2() + { + var path = PathHelper.GetFile("xlsx/TestIssueI4YCLQ_2.xlsx"); + var rows = _excelImporter.Query(path, startCell: "B2").ToList(); + + Assert.Null(rows[0].站点编码); + Assert.Equal("N1", rows[0].站址名称); + Assert.Equal("a", rows[0].值1); + Assert.Equal("b", rows[0].值2); + Assert.Equal("c", rows[0].值3); + Assert.Equal("A1", rows[0].资源ID); + Assert.Equal("A", rows[0].值4); + Assert.Equal("B", rows[0].值5); + Assert.Equal("C", rows[0].值6); + Assert.Null(rows[0].值7); + Assert.Null(rows[0].值8); + } + + [Fact] + public void TestIssueI4WM67() + { + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WM67.xlsx"); + var value = new Dictionary + { + ["users"] = Array.Empty() + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Single(rows); + } + + [Fact] + public void TestIssueI4WXFB() + { + { + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WXFB.xlsx"); + var value = new Dictionary + { + ["Name"] = "Jack", + ["Amount"] = 1000, + ["Department"] = "HR" + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + } + + { + var config = new OpenXmlConfiguration + { + IgnoreTemplateParameterMissing = false + }; + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WXFB.xlsx"); + var value = new Dictionary + { + ["Name"] = "Jack", + ["Amount"] = 1000, + ["Department"] = "HR" + }; + Assert.Throws(() => _excelTemplater.FillTemplate(path.ToString(), templatePath, value, configuration: config)); + } + } + + [Fact] + public void TestIssueI4TXGT() + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + var value = new[] { new TestIssueI4TXGTDto { ID = 1, Name = "Apple", Spc = "X", Up = 6999 } }; + + _excelExporter.Export(path, value); + var rows1 = _excelImporter.Query(path).ToList(); + Assert.Equal("ID", rows1[0].A); + Assert.Equal("Name", rows1[0].B); + Assert.Equal("Specification", rows1[0].C); + Assert.Equal("Unit Price", rows1[0].D); + + var rows2 = _excelImporter.Query(path).ToList(); + Assert.Equal(1, rows2[0].ID); + Assert.Equal("Apple", rows2[0].Name); + Assert.Equal("X", rows2[0].Spc); + Assert.Equal(6999, rows2[0].Up); + } + + // https://gitee.com/dotnetchina/MiniExcel/issues/I4HL54 + [Fact] + public void TestIssueI4HL54() + { + using var cn = Db.GetConnection(); + + using var reader = cn.ExecuteReader(@"select 'Hello World1' Text union all select 'Hello World2'"); + var templatePath = PathHelper.GetFile("xlsx/TestIssueI4HL54_Template.xlsx"); + using var path = AutoDeletingPath.Create(); + var value = new Dictionary + { + { "Texts",reader} + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + + var rows = _excelImporter.Query(path.ToString(), true).ToList(); + Assert.Equal("Hello World1", rows[0].Text); + Assert.Equal("Hello World2", rows[1].Text); + } + + // SaveAsByTemplate if there is & in the cell value, it will be & + // https://gitee.com/dotnetchina/MiniExcel/issues/I4DQUN + [Fact] + public void TestIssueI4DQUN() + { + var templatePath = PathHelper.GetFile("xlsx/TestIssueI4DQUN.xlsx"); + using var path = AutoDeletingPath.Create(); + var value = new Dictionary + { + { "Title", "Hello & World < , > , \" , '" }, + { "Details", new[] { new { Value = "Hello & Value < , > , \" , '" } } }, + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + // Template now uses inlineStr format (...) instead of SharedStrings (...) + Assert.Contains("Hello & World < , > , \" , '", sheetXml); + Assert.Contains("Hello & Value < , > , \" , '", sheetXml); + } + + [Fact] + public void TestIssueI49RYZ() + { + DescriptionEnumDto[] values = + [ + new() { Name = "Jack", UserType = DescriptionEnum.V1 }, + new() { Name = "Leo", UserType = DescriptionEnum.V2 }, + new() { Name = "Henry", UserType = DescriptionEnum.V3 }, + new() { Name = "Lisa", UserType = null } + ]; + + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), values); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); + Assert.Equal("General User", rows[0].UserType); + Assert.Equal("General Administrator", rows[1].UserType); + Assert.Equal("Super Administrator", rows[2].UserType); + Assert.Null(rows[3].UserType); + } + + // https://gitee.com/dotnetchina/MiniExcel/issues/I40QA5 + [Fact] + public void TestIssueI40QA5() + { + { + var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_1.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal("E001", rows[0].Empno); + Assert.Equal("E002", rows[1].Empno); + } + { + var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_2.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal("E001", rows[0].Empno); + Assert.Equal("E002", rows[1].Empno); + } + { + var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_3.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal("E001", rows[0].Empno); + Assert.Equal("E002", rows[1].Empno); + } + { + var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_4.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + Assert.Null(rows[0].Empno); + Assert.Null(rows[1].Empno); + } + } + + // Semicolon expected + [Fact] + public void TestIssueI45TF5_2() + { + var value = new[] { new Dictionary { { "Col1&Col2", "V1&V2" } } }; + + using var path1 = AutoDeletingPath.Create(); + _excelExporter.Export(path1.ToString(), value); + //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. + SheetHelper.GetZipFileContent(path1.ToString(), "xl/worksheets/sheet1.xml"); //check illegal format or not + + using var dt = new DataTable(); + dt.Columns.Add("Col1&Col2"); + dt.Rows.Add("V1&V2"); + + using var path2 = AutoDeletingPath.Create(); + _excelExporter.Export(path2.FilePath, dt); + //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. + SheetHelper.GetZipFileContent(path2.FilePath, "xl/worksheets/sheet1.xml"); //check illegal format or not + } + + [Fact] + public void TestIssueI45TF5() + { + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), new[] { new { C1 = "1&2;3,4", C2 = "1&2;3,4" } }); + var sheet1Xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.DoesNotContain("", sheet1Xml); + } + + [Fact] + public void TestIssueI3X2ZL() + { + try + { + var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_datetime_error.xlsx"); + var rows = _excelImporter.Query(path, startCell: "B3").ToList(); + } + catch (InvalidCastException ex) + { + Assert.Equal( + "The value error cannot be assigned to type DateTime.", + ex.Message + ); + } + + try + { + var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_int_error.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + } + catch (InvalidCastException ex) + { + Assert.Equal( + "The value error cannot be assigned to type Int32.", + ex.Message + ); + } + } + + // https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV + // When exporting, the pure numeric string will be forcibly converted to a numeric type, resulting in the loss of the end data + [Fact] + public void IssueI3OSKV() + { + using var path1 = AutoDeletingPath.Create(); + var value1 = new[] { new { Test = "12345678901234567890" } }; + _excelExporter.Export(path1.ToString(), value1); + + var result1 = _excelImporter.Query(path1.ToString(), true).First(); + Assert.Equal("12345678901234567890", result1.Test); + + using var path2 = AutoDeletingPath.Create(); + var value2 = new[] { new { Test = 123456.789 } }; + _excelExporter.Export(path2.ToString(), value2); + + var result2 = _excelImporter.Query(path2.ToString(), true).First(); + Assert.Equal(123456.789, result2.Test); + } + + // https://gitee.com/dotnetchina/MiniExcel/issues/I50VD5 + [Fact] + public void IssueI50VD5() + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + + List list1 = + [ + new { Name = "github", Image = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")) }, + new { Name = "google", Image = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")) }, + new { Name = "microsoft", Image = File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png")) }, + new { Name = "reddit", Image = File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png")) }, + new { Name = "stackoverflow", Image = File.ReadAllBytes(PathHelper.GetFile("images/stackoverflow_logo.png")) } + ]; + + List list2 = + [ + new { Id = 1, Name = "github", Image = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")) }, + new { Id = 2, Name = "google", Image = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")) } + ]; + + var sheets = new Dictionary + { + ["A"] = list1, + ["B"] = list2, + }; + _excelExporter.Export(path, sheets); + + { + Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); + Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing1.xml")); + Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); + Assert.Contains("drawing r:id=\"drawing1\"", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); + Assert.Contains("../drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); + + Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing2.xml.rels")); + Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing2.xml")); + Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); + Assert.Contains("drawing r:id=\"drawing2\"", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet2.xml")); + Assert.Contains("../drawings/drawing2.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet2.xml.rels")); + } + } +} \ No newline at end of file diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesAsyncTests.cs similarity index 57% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs rename to tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesAsyncTests.cs index be0bad0c..abeda8d8 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueAsyncTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesAsyncTests.cs @@ -2,9 +2,9 @@ using MiniExcelLib.OpenXml.Tests.Utils; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Issues; -public class MiniExcelIssueAsyncTests(ITestOutputHelper output) +public class MiniExcelGithubIssuesAsyncTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; @@ -12,142 +12,80 @@ public class MiniExcelIssueAsyncTests(ITestOutputHelper output) private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); - static MiniExcelIssueAsyncTests() + static MiniExcelGithubIssuesAsyncTests() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; } - /// - /// [SaveAsByTemplate support DateTime custom format · Issue #255 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/255) - /// [Fact] - public async Task Issue255() + public async Task EmptyDataReaderIssue() { - var dt1 = new DateTime(2021, 01, 01); - var dt2 = new DateTime(2022, 01, 01); - - //template - { - var templatePath = PathHelper.GetFile("xlsx/TestsIssue255_Template.xlsx"); - await using var ms = new MemoryStream(); - var value = new - { - Issue255DTO = new[] { new Issue255DTO { Time = dt1, Time2 = dt2 } } - }; - - await _excelTemplater.FillTemplateAsync(ms, templatePath, value); - - ms.Seek(0, SeekOrigin.Begin); - using var package = new ExcelPackage(ms); - var cells = package.Workbook.Worksheets[0].Cells; + using var path = AutoDeletingPath.Create(); + using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - Assert.Equal("2021", cells["A2"].Text); - Assert.Equal("2022", cells["B2"].Text); - } - //export + await using (var connection1 = new SQLiteConnection(connectionString)) { - await using var ms = new MemoryStream(); - Issue255DTO[] value = - [ - new() { Time = dt1, Time2 = dt2 } - ]; - - var rowsWritten = await _excelExporter.ExportAsync(ms, value); - Assert.Single(rowsWritten); - Assert.Equal(1, rowsWritten[0]); - - ms.Seek(0, SeekOrigin.Begin); - using var package = new ExcelPackage(ms); - - var cells = package.Workbook.Worksheets[0].Cells; - Assert.Equal(dt1, DateTime.FromOADate((double)cells["A2"].Value)); - Assert.Equal("2021", cells["A2"].Text); - Assert.Equal(dt2, DateTime.FromOADate((double)cells["B2"].Value)); - Assert.Equal("2022", cells["B2"].Text); + await connection1.ExecuteAsync("CREATE TABLE test (id int PRIMARY KEY, name TEXT)"); } - } - private class Issue255DTO - { - [MiniExcelFormat("yyyy")] - public DateTime Time { get; set; } + await using var connection2 = new SQLiteConnection(connectionString); + await using var reader = await connection2.ExecuteReaderAsync("SELECT * FROM test"); - [MiniExcelColumn(Format = "yyyy")] - public DateTime Time2 { get; set; } - } + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); + Assert.Single(rowsWritten); + Assert.Equal(0, rowsWritten[0]); - /// - /// [Dynamic QueryAsync custom format not using mapping format · Issue #256] - /// (https://github.com/mini-software/MiniExcel/issues/256) - /// - [Fact] - public async Task Issue256() - { - var path = PathHelper.GetFile("xlsx/TestIssue256.xlsx"); - var q = await _excelImporter.QueryAsync(path).ToListAsync(); - var rows = q.ToList(); - - Assert.Equal(new DateTime(2003, 4, 16), rows[1].A); - Assert.Equal(new DateTime(2004, 4, 16), rows[1].B); + var rows = await _excelImporter.QueryAsync(path.ToString(), true).ToListAsync(); + Assert.Empty(rows); } - - /// - /// No error exception throw when reading xls file #242 - /// [Fact] - public async Task Issue242() + public async Task Issue87() { - var path = PathHelper.GetFile("xls/TestIssue242.xls"); - Assert.Throws(() => _ = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList()); + var templatePath = PathHelper.GetFile("xlsx/TestTemplateCenterEmpty.xlsx"); + using var path = AutoDeletingPath.Create(); + var value = new + { + Tests = Enumerable.Range(1, 5).Select((_, i) => new { test1 = i, test2 = i }) + }; - await using var stream = File.OpenRead(path); - Assert.Throws(() => _ = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().ToList()); + await using var stream = File.OpenRead(templatePath); + _ = await _excelImporter.QueryAsync(templatePath).ToListAsync(); + await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); } - /// - /// Support Custom Datetime format #241 - /// + // QueryAsync Merge cells data [Fact] - public async Task Issue241() + public async Task Issue122() { - var date1 = new DateTime(2021, 01, 04); - var date2 = new DateTime(2020, 04, 05); - - Issue241Dto[] value = - [ - new() { Name = "Jack", InDate = date1 }, - new() { Name = "Henry", InDate = date2 } - ]; - - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - var rowsWritten = await _excelExporter.ExportAsync(path, value); + var config = new OpenXmlConfiguration + { + FillMergedCells = true + }; - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - using var package = new ExcelPackage(path); - var cells = package.Workbook.Worksheets[0].Cells; - - Assert.Equal(date1, DateTime.FromOADate((double)cells["B2"].Value)); - Assert.Equal("01 04, 2021", cells["B2"].Text); - Assert.Equal(date2, DateTime.FromOADate((double)cells["B3"].Value)); - Assert.Equal("04 05, 2020", cells["B3"].Text); - } + var path1 = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); + var rows1 = await _excelImporter.QueryAsync(path1, hasHeaderRow: true, configuration: config).ToListAsync(); + + Assert.Equal("HR", rows1[0].Department); + Assert.Equal("HR", rows1[1].Department); + Assert.Equal("HR", rows1[2].Department); + Assert.Equal("IT", rows1[3].Department); + Assert.Equal("IT", rows1[4].Department); + Assert.Equal("IT", rows1[5].Department); - private class Issue241Dto - { - public string Name { get; set; } + var path2 = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); + var rows2 = await _excelImporter.QueryAsync(path2, hasHeaderRow: true, configuration: config).ToListAsync(); - [MiniExcelFormat("MM dd, yyyy")] - public DateTime InDate { get; set; } + Assert.Equal("V1", rows2[2].Test1); + Assert.Equal("V2", rows2[5].Test2); + Assert.Equal("V3", rows2[1].Test3); + Assert.Equal("V4", rows2[2].Test4); + Assert.Equal("V5", rows2[3].Test5); + Assert.Equal("V6", rows2[5].Test5); } - /// - /// SaveAs Default Template #132 - /// + // SaveAs Default Template [Fact] public async Task Issue132() { @@ -195,276 +133,365 @@ public async Task Issue132() Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); } - } + } - /// - /// Support SaveAs by DataSet #235 - /// [Fact] - public async Task Issue235() + public async Task Issue137() { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); + var path = PathHelper.GetFile("xlsx/TestIssue137.xlsx"); + { + var rows = await _excelImporter.QueryAsync(path).Cast>().ToListAsync(); + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], rows[0].Keys.ToArray()); + Assert.Equal(11, rows.Count); - var users = new DataTable { TableName = "users" }; - users.Columns.Add("Name", typeof(string)); - users.Columns.Add("Age", typeof(int)); - users.Rows.Add("Jack", 25); - users.Rows.Add("Mike", 44); + var row1 = rows[0]; + Assert.Equal("比例", row1["A"]); + Assert.Equal("商品", row1["B"]); + Assert.Equal("滿倉口數", row1["C"]); + Assert.Equal(" ", row1["D"]); + Assert.Equal(" ", row1["E"]); + Assert.Equal(" ", row1["F"]); + Assert.Equal(0.0, row1["G"]); + Assert.Equal("1為港幣 0為台幣", row1["H"]); + + var row2 = rows[1]; + Assert.Equal(1.0, row2["A"]); + Assert.Equal("MTX", row2["B"]); + Assert.Equal(10.0, row2["C"]); + Assert.Null(row2["D"]); + Assert.Null(row2["E"]); + Assert.Null(row2["F"]); + Assert.Null(row2["G"]); + Assert.Null(row2["H"]); + + var row3 = rows[2]; + Assert.Equal(0.95, row3["A"]); + } - var departments = new DataTable { TableName = "departments" }; - departments.Columns.Add("ID"); - departments.Columns.Add("Name"); - departments.Rows.Add("01", "HR"); - departments.Rows.Add("02", "IT"); + // dynamic query with head + { + var rows = await _excelImporter.QueryAsync(path, true).Cast>().ToListAsync(); + var first = rows[0]; // 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); + + var row1 = rows[0]; + Assert.Equal(1.0, row1["比例"]); + Assert.Equal("MTX", row1["商品"]); + Assert.Equal(10.0, row1["滿倉口數"]); + Assert.Null(row1["0"]); + Assert.Null(row1["1為港幣 0為台幣"]); + + var row2 = rows[1]; + Assert.Equal(0.95, row2["比例"]); + } - DataSet sheets = new(); - sheets.Tables.Add(users); - sheets.Tables.Add(departments); + { + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal(10, rows.Count); + + var row1 = rows[0]; + Assert.Equal(1, row1.比例); + Assert.Equal("MTX", row1.商品); + Assert.Equal(10, row1.滿倉口數); - var rowsWritten = await _excelExporter.ExportAsync(path, sheets); - Assert.Equal(2, rowsWritten.Length); - Assert.Equal(2, rowsWritten[0]); + var row2 = rows[1]; + Assert.Equal(0.95, row2.比例); + } + } - var sheetNames = await _excelImporter.GetSheetNamesAsync(path); - Assert.Equal("users", sheetNames[0]); - Assert.Equal("departments", sheetNames[1]); + [Fact] + public async Task Issue138() + { + var path = PathHelper.GetFile("xlsx/TestIssue138.xlsx"); + { + var rows = await _excelImporter.QueryAsync(path, true).ToListAsync(); + Assert.Equal(6, rows.Count); - var rows1 = await _excelImporter.QueryAsync(path, true, sheetName: "users").ToListAsync(); - Assert.Equal("Jack", rows1[0].Name); - Assert.Equal(25, rows1[0].Age); - Assert.Equal("Mike", rows1[1].Name); - Assert.Equal(44, rows1[1].Age); + foreach (var index in new[] { 0, 2, 5 }) + { + Assert.Equal(1, rows[index].實單每日損益); + Assert.Equal(2, rows[index].程式每日損益); + Assert.Equal("測試商品1", rows[index].商品); + Assert.Equal(111.11, rows[index].滿倉口數); + Assert.Equal(111.11, rows[index].波段); + Assert.Equal(111.11, rows[index].當沖); + } - var rows2 = await _excelImporter.QueryAsync(path, true, sheetName: "departments").ToListAsync(); - Assert.Equal("01", rows2[0].ID); - Assert.Equal("HR", rows2[0].Name); - Assert.Equal("02", rows2[1].ID); - Assert.Equal("IT", rows2[1].Name); + foreach (var index in new[] { 1, 3, 4 }) + { + Assert.Null(rows[index].實單每日損益); + Assert.Null(rows[index].程式每日損益); + Assert.Null(rows[index].商品); + Assert.Null(rows[index].滿倉口數); + Assert.Null(rows[index].波段); + Assert.Null(rows[index].當沖); + } + } + { + + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal(6, rows.Count); + Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); + + foreach (var index in new[] { 0, 2, 5 }) + { + Assert.Equal(1, rows[index].實單每日損益); + Assert.Equal(2, rows[index].程式每日損益); + Assert.Equal("測試商品1", rows[index].商品); + Assert.Equal(111.11, rows[index].滿倉口數); + Assert.Equal(111.11, rows[index].波段); + Assert.Equal(111.11, rows[index].當沖); + } + + foreach (var index in new[] { 1, 3, 4 }) + { + Assert.Null(rows[index].實單每日損益); + Assert.Null(rows[index].程式每日損益); + Assert.Null(rows[index].商品); + Assert.Null(rows[index].滿倉口數); + Assert.Null(rows[index].波段); + Assert.Null(rows[index].當沖); + } + } } - /// - /// QueryAsDataTable A2=5.5 , A3=0.55/1.1 will case double type check error #233 - /// [Fact] - public async Task Issue233() + public async Task Issue142() { - var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); + var path = PathHelper.GetFile("xlsx/TestIssue142.xlsx"); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); - var dt = await _excelImporter.QueryAsDataTableAsync(path); - var rows = dt.Rows; - - Assert.Equal(0.55, rows[0]["Size"]); - Assert.Equal("0.55/1.1", rows[1]["Size"]); + Assert.Equal(0, rows[0].MyProperty1); + await Assert.ThrowsAsync(async () => + { + _ = await _excelImporter.QueryAsync(path).ToListAsync(); + }); + + var rows1 = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal("CustomColumnName", rows1[0].MyProperty1); + Assert.Null(rows1[0].MyProperty7); + Assert.Equal("MyProperty2", rows1[0].MyProperty2); + Assert.Equal("MyProperty103", rows1[0].MyProperty3); + Assert.Equal("MyProperty100", rows1[0].MyProperty4); + Assert.Equal("MyProperty102", rows1[0].MyProperty5); + Assert.Equal("MyProperty6", rows1[0].MyProperty6); + + var rows2 = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal("CustomColumnName", rows2[0].MyProperty1); + Assert.Null(rows2[0].MyProperty7); + Assert.Equal("MyProperty2", rows2[0].MyProperty2); + Assert.Equal("MyProperty103", rows2[0].MyProperty3); + Assert.Equal("MyProperty100", rows2[0].MyProperty4); + Assert.Equal("MyProperty102", rows2[0].MyProperty5); + Assert.Equal("MyProperty6", rows2[0].MyProperty6); } - /// - /// SaveAs support multiple sheets #234 - /// + // QueryAsync Support StartCell [Fact] - public async Task Issue234() + public async Task Issue147() { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - var users = new[] - { - new { Name = "Jack", Age = 25 }, - new { Name = "Mike", Age = 44 } - }; - var department = new[] - { - new { ID = "01", Name = "HR" }, - new { ID = "02", Name = "IT" } - }; - var sheets = new Dictionary + var path1 = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); + var rows1 = await _excelImporter.QueryAsync(path1, hasHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToListAsync(); + + Assert.Equal(["C", "D", "E"], (rows1[0] as IDictionary)?.Keys); + Assert.Equal(new[]{ "Column1", "Column2", "Column3" }, new[] { rows1[0].C as string, rows1[0].D as string, rows1[0].E as string }); + Assert.Equal(new[]{ "C4", "D4", "E4" }, new[] { rows1[1].C as string, rows1[1].D as string, rows1[1].E as string }); + Assert.Equal(new[]{ "C9", "D9", "E9" }, new[] { rows1[6].C as string, rows1[6].D as string, rows1[6].E as string }); + Assert.Equal(new[]{ "C12", "D12", "E12" }, new[] { rows1[9].C as string, rows1[9].D as string, rows1[9].E as string }); + Assert.Equal(new[]{ "C13", "D13", "E13" }, new[] { rows1[10].C as string, rows1[10].D as string, rows1[10].E as string }); + + foreach (var i in new[] { 4, 5, 7, 8 }) { - ["users"] = users, - ["department"] = department - }; - var rowsWritten = await _excelExporter.ExportAsync(path, sheets); - - Assert.Equal(2, rowsWritten.Length); - Assert.Equal(2, rowsWritten[0]); - + Assert.Equal(new string?[]{null, null, null}, new[] { rows1[i].C as string, rows1[i].D as string, rows1[i].E as string }); + } + Assert.Equal(11, rows1.Count); - var sheetNames = _excelImporter.GetSheetNames(path); - Assert.Equal("users", sheetNames[0]); - Assert.Equal("department", sheetNames[1]); + var columns1 = await _excelImporter.GetColumnNamesAsync(path1, startCell: "C3"); + Assert.Equal(["C", "D", "E"], columns1); - { - var q = _excelImporter.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable(); - var rows = q.ToList(); + + var path2 = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); + var rows2 = await _excelImporter.QueryAsync(path2, hasHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToListAsync(); - Assert.Equal("Jack", rows[0].Name); - Assert.Equal(25, rows[0].Age); - Assert.Equal("Mike", rows[1].Name); - Assert.Equal(44, rows[1].Age); - } - { - var q = _excelImporter.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable(); - var rows = q.ToList(); + Assert.Equal(["Column1", "Column2", "Column3"], (rows2[0] as IDictionary)?.Keys); + Assert.Equal(new[]{"C4", "D4", "E4"}, new[] { rows2[0].Column1 as string, rows2[0].Column2 as string, rows2[0].Column3 as string }); + Assert.Equal(new[]{"C9", "D9", "E9"}, new[] { rows2[5].Column1 as string, rows2[5].Column2 as string, rows2[5].Column3 as string }); + Assert.Equal(new[]{"C12", "D12", "E12"}, new[] { rows2[8].Column1 as string, rows2[8].Column2 as string, rows2[8].Column3 as string }); + Assert.Equal(new[]{"C13", "D13", "E13"}, new[] { rows2[9].Column1 as string, rows2[9].Column2 as string, rows2[9].Column3 as string }); - Assert.Equal("01", rows[0].ID); - Assert.Equal("HR", rows[0].Name); - Assert.Equal("02", rows[1].ID); - Assert.Equal("IT", rows[1].Name); + foreach (var i in new[] { 3, 4, 6, 7 }) + { + Assert.Equal(new string?[]{null, null, null}, new[] { rows2[i].Column1 as string, rows2[i].Column2 as string, rows2[i].Column3 as string }); } + Assert.Equal(10, rows2.Count); + + var columns2 = await _excelImporter.GetColumnNamesAsync(path2, hasHeaderRow: true, startCell: "C3"); + Assert.Equal(["Column1", "Column2", "Column3"], columns2); } - /// - /// SaveAs By Reader Closed error : 'Error! Invalid attempt to call FieldCount when reader is closed' #230 - /// https://github.com/mini-software/MiniExcel/issues/230 - /// [Fact] - public async Task Issue230() + public async Task Issue149() { - await using var conn = Db.GetConnection("Data Source=:memory:"); - await conn.OpenAsync(); - await using var cmd = conn.CreateCommand(); - cmd.CommandText = "select 1 id union all select 2"; + char[] chars = + [ + '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', + '\u0009', // + '\u000A', // + '\u000B', '\u000C', + '\u000D', // + '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', + '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', '\u007F' + ]; + var strings = chars.Select(s => s.ToString()).ToArray(); - await using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + var path1 = PathHelper.GetFile("xlsx/TestIssue149.xlsx"); + var rows1 = await _excelImporter.QueryAsync(path1).Select(s => (string)s.A).ToListAsync(); + + for (int i = 0; i < chars.Length; i++) { - while (await reader.ReadAsync()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - _output.WriteLine(result); - } - } + if (i != 13) + Assert.Equal(strings[i], rows1[i]); } - await using var conn2 = Db.GetConnection("Data Source=:memory:"); - await conn2.OpenAsync(); - await using var cmd2 = conn2.CreateCommand(); - cmd2.CommandText = "select 1 id union all select 2"; - - await using (var reader = await cmd2.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + using var file1 = AutoDeletingPath.Create(); + var path2 = file1.ToString(); + + var input1 = chars.Select(s => new { Test = s.ToString() }); + await _excelExporter.ExportAsync(path2, input1); + + var rows2 = await _excelImporter.QueryAsync(path2, true).Select(s => (string)s.Test).ToListAsync(); + for (int i = 0; i < chars.Length; i++) { - while (await reader.ReadAsync()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - _output.WriteLine(result); - } - } + _output.WriteLine($"{i}, {chars[i]}, {rows2[i]}"); + if (i is not (9 or 10 or 13)) + Assert.Equal(strings[i], rows2[i]); } - await using var conn3 = Db.GetConnection("Data Source=:memory:"); - await conn3.OpenAsync(); - await using var cmd3 = conn3.CreateCommand(); - cmd3.CommandText = "select 1 id union all select 2"; - - await using (var reader = await cmd3.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + using var file2 = AutoDeletingPath.Create(); + var path3 = file2.ToString(); + + var input2 = chars.Select(s => new { Test = s.ToString() }); + await _excelExporter.ExportAsync(path3, input2); + + var rows = await _excelImporter.QueryAsync(path3).Select(s => s.Test).ToListAsync(); + for (int i = 0; i < chars.Length; i++) { - using var path = AutoDeletingPath.Create(); - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader, printHeader: true); - - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(1, rows[0].id); - Assert.Equal(2, rows[1].id); + _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); + if (i is not (13 or 9 or 10)) + Assert.Equal(strings[i], rows[i]); } } - /// - /// v0.14.3 QueryAsDataTable error "Cannot set Column to be null" #229 - /// https://github.com/mini-software/MiniExcel/issues/229 - /// [Fact] - public async Task Issue229() + public async Task Issue150() { - var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); - - var dt = await _excelImporter.QueryAsDataTableAsync(path); - - foreach (DataColumn column in dt.Columns) - { - var v = dt.Rows[3][column]; - Assert.Equal(DBNull.Value, v); - } + using var filePath = AutoDeletingPath.Create(); + var path = filePath.ToString(); + + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { 1, 2 }, overwriteFile: true)); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { "1", "2" }, overwriteFile: true)); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { '1', '2' }, overwriteFile: true)); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { DateTime.Now }, overwriteFile: true)); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { Guid.NewGuid() }, overwriteFile: true)); } - /// - /// [QueryAsync Merge cells data · Issue #122 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/122) - /// [Fact] - public Task Issue122() + public async Task Issue153() { - var config = new OpenXmlConfiguration - { - FillMergedCells = true - }; - - var path1 = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); - var rows1 = _excelImporter.QueryAsync(path1, hasHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); - - Assert.Equal("HR", rows1[0].Department); - Assert.Equal("HR", rows1[1].Department); - Assert.Equal("HR", rows1[2].Department); - Assert.Equal("IT", rows1[3].Department); - Assert.Equal("IT", rows1[4].Department); - Assert.Equal("IT", rows1[5].Department); - - var path2 = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); - var rows2 = _excelImporter.QueryAsync(path2, hasHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); + var path = PathHelper.GetFile("xlsx/TestIssue153.xlsx"); + var row = (IDictionary)await _excelImporter.QueryAsync(path, true).FirstAsync(); - Assert.Equal("V1", rows2[2].Test1); - Assert.Equal("V2", rows2[5].Test2); - Assert.Equal("V3", rows2[1].Test3); - Assert.Equal("V4", rows2[2].Test4); - Assert.Equal("V5", rows2[3].Test5); - Assert.Equal("V6", rows2[5].Test5); - - return Task.CompletedTask; + Assert.Equal( + [ + "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", + "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" + ], row.Keys); } - /// - /// [Support Xlsm AutoCheck · Issue #227 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/227) - /// [Fact] - public async Task Issue227() + public async Task Issue157() { { - var path = PathHelper.GetTempPath("xlsm"); - Assert.Throws(() => _excelExporter.Export(path, new[] { new { V = "A1" }, new { V = "A2" } })); - File.Delete(path); - } + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + + List data = + [ + new() + { + ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"), + Name = "Wade", + BoD = new DateTime(2020, 9, 27), + Points = 5019.12m + }, + new() + { + ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"), + Name = "Felix", + BoD = new DateTime(2020, 10, 25), + Points = 7028.46m + }, + new() + { + ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"), + Name = "Phelan", + BoD = new DateTime(2020, 10, 25), + Points = 3835.7m, + VIP = true + }, + new() + { + ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"), + Name = "Samuel", + BoD = new DateTime(2020, 6, 21), + Points = 9351.71m + }, + new() + { + ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"), + Name = "Raymond", + BoD = new DateTime(2021, 7, 12), + Points = 8209.76m, + VIP = true + } + ]; + + var rowsWritten = await _excelExporter.ExportAsync(path, data); + Assert.Single(rowsWritten); + Assert.Equal(5, rowsWritten[0]); + + var q = await _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToListAsync(); + var rows = q.ToList(); + Assert.Equal(6, rows.Count); + Assert.Equal("Sheet1", (await _excelImporter.GetSheetNamesAsync(path))[0]); + using var p = new ExcelPackage(new FileInfo(path)); + var ws = p.Workbook.Worksheets[0]; + Assert.Equal("Sheet1", ws.Name); + Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); + } { - var path = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); + var path = PathHelper.GetFile("xlsx/TestIssue157.xlsx"); { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); + var rows = await _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToListAsync(); + Assert.Equal(6, rows.Count); + Assert.Equal("Sheet1", (await _excelImporter.GetSheetNamesAsync(path))[0]); + } + using (var p = new ExcelPackage(new FileInfo(path))) + { + var ws = p.Workbook.Worksheets.First(); + Assert.Equal("Sheet1", ws.Name); + Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); } + { - await using var stream = File.OpenRead(path); - var q = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(100, rows.Count); + var rows = await _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToListAsync(); + Assert.Equal(5, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); Assert.Equal("Wade", rows[0].Name); Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); Assert.False(rows[0].VIP); Assert.Equal(5019.12m, rows[0].Points); Assert.Equal(1, rows[0].IgnoredProperty); @@ -472,311 +499,148 @@ public async Task Issue227() } } - /// - /// https://github.com/mini-software/MiniExcel/issues/226 - /// Fix SaveAsByTemplate single column demension index error #226 - /// [Fact] - public async Task Issue226() + public async Task Issue193() { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); - await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); - Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); - } + { + var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - /// - /// ASP.NET Webform gridview datasource can't use miniexcel queryasdatatable · Issue #223] - /// (https://github.com/mini-software/MiniExcel/issues/223) - /// - [Fact] - public async Task Issue223() - { - List> value = - [ - new() { { "A", null }, { "B", null } }, - new() { { "A", 123 }, { "B", new DateTime(2021, 1, 1) } }, - new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } - ]; - using var path = AutoDeletingPath.Create(); - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); - Assert.Single(rowsWritten); - Assert.Equal(3, rowsWritten[0]); + // 1. By Class + var value = new + { + title = "FooCompany", + managers = new[] + { + new {name="Jack",department="HR"}, + new {name="Loan",department="IT"} + }, + employees = new[] + { + new {name="Wade",department="HR"}, + new {name="Felix",department="HR"}, + new {name="Eric",department="IT"}, + new {name="Keaton",department="IT"} + } + }; + await _excelTemplater.FillTemplateAsync(path, templatePath, value); + foreach (var sheetName in await _excelImporter.GetSheetNamesAsync(path)) + { + var rows = await _excelImporter.QueryAsync(path, sheetName: sheetName).ToListAsync(); + Assert.Equal(9, rows.Count); - var dt = await _excelImporter.QueryAsDataTableAsync(path.ToString()); -#pragma warning restore CS0618 - var columns = dt.Columns; - Assert.Equal(typeof(object), columns[0].DataType); - Assert.Equal(typeof(object), columns[1].DataType); + 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(123.0, dt.Rows[1]["A"]); - Assert.Equal("HelloWorld", dt.Rows[2]["B"]); - } + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); - /// - /// [Custom yyyy-MM-dd format not convert datetime · Issue #222] - /// (https://github.com/mini-software/MiniExcel/issues/222) - /// - [Fact] - public async Task Issue222() - { - var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(typeof(DateTime), rows[1].A.GetType()); - Assert.Equal(new DateTime(2021, 4, 29), rows[1].A); - } - - /// - /// QueryAsync Support StartCell #147 - /// https://github.com/mini-software/MiniExcel/issues/147 - /// - [Fact] - public async Task Issue147() - { - { - var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var q = _excelImporter.QueryAsync(path, hasHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(["C", "D", "E"], (rows[0] as IDictionary)?.Keys); - Assert.Equal(["Column1", "Column2", "Column3"], new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); - Assert.Equal(["C4", "D4", "E4"], new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); - Assert.Equal(["C9", "D9", "E9"], new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); - Assert.Equal(["C12", "D12", "E12"], new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); - Assert.Equal(["C13", "D13", "E13"], new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); - - foreach (var i in new[] { 4, 5, 7, 8 }) - { - Assert.Equal([null, null, null], new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); - } - Assert.Equal(11, rows.Count); + 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 columns = await _excelImporter.GetColumnNamesAsync(path, startCell: "C3"); - Assert.Equal(["C", "D", "E"], columns); - } + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C9", demension); - { - var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var q = _excelImporter.QueryAsync(path, hasHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(["Column1", "Column2", "Column3"], (rows[0] as IDictionary)?.Keys); - Assert.Equal(["C4", "D4", "E4"], new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); - Assert.Equal(["C9", "D9", "E9"], new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); - Assert.Equal(["C12", "D12", "E12"], new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); - Assert.Equal(["C13", "D13", "E13"], new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); - - foreach (var i in new[] { 3, 4, 6, 7 }) - { - Assert.Equal([null, null, null], new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); + //TODO:row can't contain xmlns + //![image](https://user-images.githubusercontent.com/12729184/114998840-ead44500-9ed3-11eb-8611-58afb98faed9.png) } - Assert.Equal(10, rows.Count); - - var columns = await _excelImporter.GetColumnNamesAsync(path, hasHeaderRow: true, startCell: "C3"); - Assert.Equal(["Column1", "Column2", "Column3"], columns); } - } - - /// - /// [Can SaveAs support iDataReader export to avoid the dataTable consuming too much memory · Issue #211 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/211) - /// - [Fact] - public async Task Issue211() - { - using var path = AutoDeletingPath.Create(); - using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); - var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - - await using var connection = new SQLiteConnection(connectionString); - using var reader = await connection.ExecuteReaderAsync("select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); - - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); - Assert.Single(rowsWritten); - Assert.Equal(3, rowsWritten[0]); - - var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(1.0, rows[0].Test1); - Assert.Equal(2.0, rows[0].Test2); - Assert.Equal(3.0, rows[1].Test1); - Assert.Equal(4.0, rows[1].Test2); - } - - [Fact] - public async Task EmptyDataReaderIssue() - { - using var path = AutoDeletingPath.Create(); - using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); - var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - - await using (var connection1 = new SQLiteConnection(connectionString)) { - await connection1.ExecuteAsync("CREATE TABLE test (id int PRIMARY KEY, name TEXT)"); - } - - await using var connection2 = new SQLiteConnection(connectionString); - using var reader = await connection2.ExecuteReaderAsync("SELECT * FROM test"); + var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplex.xlsx"); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); - Assert.Single(rowsWritten); - Assert.Equal(0, rowsWritten[0]); + // 2. By Dictionary + var value = new Dictionary + { + ["title"] = "FooCompany", + ["managers"] = new[] + { + new {name="Jack",department="HR"}, + new {name="Loan",department="IT"} + }, + ["employees"] = new[] + { + new {name="Wade",department="HR"}, + new {name="Felix",department="HR"}, + new {name="Eric",department="IT"}, + new {name="Keaton",department="IT"} + } + }; + await _excelTemplater.FillTemplateAsync(path, templatePath, value); - var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Empty(rows); - } + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + 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); - /// - /// [When reading Excel, can return IDataReader and DataTable to facilitate the import of database. Like ExcelDataReader provide reader.AsDataSet() · Issue #216 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/216) - /// - [Fact] - public async Task Issue216() - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Test1 = "1", Test2 = 2 }, new { Test1 = "3", Test2 = 4 } }; - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); - { - var table = await _excelImporter.QueryAsDataTableAsync(path.ToString()); - Assert.Equal("Test1", table.Columns[0].ColumnName); - Assert.Equal("Test2", table.Columns[1].ColumnName); - Assert.Equal("1", table.Rows[0]["Test1"]); - Assert.Equal(2.0, table.Rows[0]["Test2"]); - Assert.Equal("3", table.Rows[1]["Test1"]); - Assert.Equal(4.0, table.Rows[1]["Test2"]); - } + 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 dt = await _excelImporter.QueryAsDataTableAsync(path.ToString(), false); - Assert.Equal("Test1", dt.Rows[0]["A"]); - Assert.Equal("Test2", dt.Rows[0]["B"]); - Assert.Equal("1", dt.Rows[1]["A"]); - Assert.Equal(2.0, dt.Rows[1]["B"]); - Assert.Equal("3", dt.Rows[2]["A"]); - Assert.Equal(4.0, dt.Rows[2]["B"]); + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C9", demension); } } - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV - /// When exporting, the pure numeric string will be forcibly converted to a numeric type, resulting in the loss of the end data - /// [Fact] - public async Task IssueI3OSKV() + public async Task Issue206() { + var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); { using var path = AutoDeletingPath.Create(); - var value = new[] { new { Test = "12345678901234567890" } }; - await _excelExporter.ExportAsync(path.ToString(), value); - var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var A2 = q.First().Test; - Assert.Equal("12345678901234567890", A2); + var dt = new DataTable(); + { + dt.Columns.Add("name"); + dt.Columns.Add("department"); + } + var value = new Dictionary + { + ["employees"] = dt + }; + await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); + + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:B2", dimension); } { using var path = AutoDeletingPath.Create(); - var value = new[] { new { Test = 123456.789 } }; - await _excelExporter.ExportAsync(path.ToString(), value); - - var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var A2 = q.First().Test; - Assert.Equal(123456.789, A2); - } - } - /// - /// [Dynamic QueryAsync can't summary numeric cell value default, need to cast · Issue #220 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/220) - /// - [Fact] - public async Task Issue220() - { - var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); - var rows = _excelImporter.QueryAsync(path, hasHeaderRow: true).ToBlockingEnumerable(); - var result = rows - .GroupBy(s => s.PRT_ID) - .Select(g => new + var dt = new DataTable(); { - PRT_ID = g.Key, - Apr = g.Sum(d => (double?)d.Apr), - May = g.Sum(d => (double?)d.May), - Jun = g.Sum(d => (double?)d.Jun), - }) - .ToList(); - - Assert.Equal(91843.25, result[0].Jun); - Assert.Equal(50000.99, result[1].Jun); - } - - /// - /// Optimize stream excel type check - /// https://github.com/mini-software/MiniExcel/issues/215 - /// - [Fact] - public async Task Issue215() - { - await using var stream = new MemoryStream(); - await _excelExporter.ExportAsync(stream, new[] { new { V = "test1" }, new { V = "test2" } }); - - var q = _excelImporter.QueryAsync(stream, true).ToBlockingEnumerable().Cast>(); - var rows = q.ToList(); - - Assert.Equal("test1", rows[0]["V"]); - Assert.Equal("test2", rows[1]["V"]); - } - - /// - /// DataTable recommended to use Caption for column name first, then use columname - /// https://github.com/mini-software/MiniExcel/issues/217 - /// - [Fact] - public async Task Issue217() - { - using var table = new DataTable(); - table.Columns.Add("CustomerID"); - table.Columns.Add("CustomerName").Caption = "Name"; - table.Columns.Add("CreditLimit").Caption = "Limit"; - table.Rows.Add(1, "Jonathan", 23.44); - table.Rows.Add(2, "Bill", 56.87); - - using var path = AutoDeletingPath.Create(); - var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), table); - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - var q = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); - } - - /// - /// _ _exporter.ExportXlsx(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 - /// https://github.com/mini-software/MiniExcel/issues/212 - /// - [Fact] - public async Task Issue212() - { - const string sheetName = "Demo"; - - using var path = AutoDeletingPath.Create(); - await _excelExporter.ExportAsync(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); + dt.Columns.Add("name"); + dt.Columns.Add("department"); + dt.Rows.Add("Jack", "HR"); + } + var value = new Dictionary { ["employees"] = dt }; + await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); - var actualSheetName = _excelImporter.GetSheetNames(path.ToString()).ToList()[0]; - Assert.Equal(sheetName, actualSheetName); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:B2", dimension); + } } - /// - /// Version <= v0.13.1 Template merge row list rendering has no merge - /// https://github.com/mini-software/MiniExcel/issues/207 - /// + // Template merge row list rendering has no merge [Fact] public async Task Issue207() { @@ -797,8 +661,7 @@ public async Task Issue207() }; await _excelTemplater.FillTemplateAsync(path, templatePath, value); - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); Assert.Equal("項目1", rows[0].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].B); @@ -844,8 +707,7 @@ public async Task Issue207() await _excelTemplater.FillTemplateAsync(path, templatePath, value); - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); Assert.Equal("項目1", rows[0].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].C); Assert.Equal("項目2", rows[3].A); @@ -860,621 +722,465 @@ public async Task Issue207() } } - /// - /// https://github.com/mini-software/MiniExcel/issues/87 - /// + // SaveAs support for IDataReader [Fact] - public async Task Issue87() + public async Task Issue211() { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateCenterEmpty.xlsx"); using var path = AutoDeletingPath.Create(); - var value = new - { - Tests = Enumerable.Range(1, 5).Select((s, i) => new { test1 = i, test2 = i }) - }; + using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - await using var stream = File.OpenRead(templatePath); - var q = _excelImporter.QueryAsync(templatePath).ToBlockingEnumerable(); - var rows = q.ToList(); + await using var connection = new SQLiteConnection(connectionString); + await using var reader = await connection.ExecuteReaderAsync("select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); + + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); - await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); + var rows = await _excelImporter.QueryAsync(path.ToString(), true).ToListAsync(); + Assert.Equal(1.0, rows[0].Test1); + Assert.Equal(2.0, rows[0].Test2); + Assert.Equal(3.0, rows[1].Test1); + Assert.Equal(4.0, rows[1].Test2); } - /// - /// https://github.com/mini-software/MiniExcel/issues/206 - /// + // _exporter.ExportXlsx(path, table,sheetName:“Name”) ,final sheetName is incorrectly Sheet1 [Fact] - public async Task Issue206() + public async Task Issue212() { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); - { - using var path = AutoDeletingPath.Create(); + const string sheetName = "Demo"; + + using var path = AutoDeletingPath.Create(); + await _excelExporter.ExportAsync(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); - var dt = new DataTable(); - { - dt.Columns.Add("name"); - dt.Columns.Add("department"); - } - var value = new Dictionary - { - ["employees"] = dt - }; - await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); + var actualSheetName = (await _excelImporter.GetSheetNamesAsync(path.ToString()))[0]; + Assert.Equal(sheetName, actualSheetName); + } - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:B2", dimension); - } + // When reading Excel, can return IDataReader and DataTable to facilitate the import of database. Like ExcelDataReader provide reader.AsDataSet() + [Fact] + public async Task Issue216() + { + using var path = AutoDeletingPath.Create(); - { - using var path = AutoDeletingPath.Create(); + var value = new[] { new { Test1 = "1", Test2 = 2 }, new { Test1 = "3", Test2 = 4 } }; + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); - var dt = new DataTable(); - { - dt.Columns.Add("name"); - dt.Columns.Add("department"); - dt.Rows.Add("Jack", "HR"); - } - var value = new Dictionary { ["employees"] = dt }; - await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); - - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:B2", dimension); - } + using var table = await _excelImporter.QueryAsDataTableAsync(path.ToString()); + Assert.Equal("Test1", table.Columns[0].ColumnName); + Assert.Equal("Test2", table.Columns[1].ColumnName); + Assert.Equal("1", table.Rows[0]["Test1"]); + Assert.Equal(2.0, table.Rows[0]["Test2"]); + Assert.Equal("3", table.Rows[1]["Test1"]); + Assert.Equal(4.0, table.Rows[1]["Test2"]); + + using var dt = await _excelImporter.QueryAsDataTableAsync(path.ToString(), false); + Assert.Equal("Test1", dt.Rows[0]["A"]); + Assert.Equal("Test2", dt.Rows[0]["B"]); + Assert.Equal("1", dt.Rows[1]["A"]); + Assert.Equal(2.0, dt.Rows[1]["B"]); + Assert.Equal("3", dt.Rows[2]["A"]); + Assert.Equal(4.0, dt.Rows[2]["B"]); } - - /// - /// https://github.com/mini-software/MiniExcel/issues/193 - /// + // DataTable recommended to use Caption for column name first, then use columname [Fact] - public async Task Issue193() + public async Task Issue217() { - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"); - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - // 1. By Class - var value = new - { - title = "FooCompany", - managers = new[] - { - new {name="Jack",department="HR"}, - new {name="Loan",department="IT"} - }, - employees = new[] - { - new {name="Wade",department="HR"}, - new {name="Felix",department="HR"}, - new {name="Eric",department="IT"}, - new {name="Keaton",department="IT"} - } - }; - await _excelTemplater.FillTemplateAsync(path, templatePath, value); - - foreach (var sheetName in await _excelImporter.GetSheetNamesAsync(path)) - { - var rows = await _excelImporter.QueryAsync(path, sheetName: sheetName).ToListAsync(); - 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 = SheetHelper.GetFirstSheetDimensionRefValue(path); - Assert.Equal("A1:C9", demension); - - //TODO:row can't contain xmlns - //![image](https://user-images.githubusercontent.com/12729184/114998840-ead44500-9ed3-11eb-8611-58afb98faed9.png) - } - } + using var table = new DataTable(); + table.Columns.Add("CustomerID"); + table.Columns.Add("CustomerName").Caption = "Name"; + table.Columns.Add("CreditLimit").Caption = "Limit"; + table.Rows.Add(1, "Jonathan", 23.44); + table.Rows.Add(2, "Bill", 56.87); - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplex.xlsx"); - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); + using var path = AutoDeletingPath.Create(); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), table); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + var rows = await _excelImporter.QueryAsync(path.ToString()).ToListAsync(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); + } - // 2. By Dictionary - var value = new Dictionary + // Dynamic QueryAsync can't summary numeric cell value default, need to cast + [Fact] + public async Task Issue220() + { + var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); + var rows = _excelImporter.QueryAsync(path, hasHeaderRow: true); + var result = await rows + .GroupBy(s => s.PRT_ID) + .Select(g => new { - ["title"] = "FooCompany", - ["managers"] = new[] - { - new {name="Jack",department="HR"}, - new {name="Loan",department="IT"} - }, - ["employees"] = new[] - { - new {name="Wade",department="HR"}, - new {name="Felix",department="HR"}, - new {name="Eric",department="IT"}, - new {name="Keaton",department="IT"} - } - }; - await _excelTemplater.FillTemplateAsync(path, templatePath, value); - - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - 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); + PRT_ID = g.Key, + Apr = g.Sum(d => (double?)d.Apr), + May = g.Sum(d => (double?)d.May), + Jun = g.Sum(d => (double?)d.Jun), + }) + .ToListAsync(); + + Assert.Equal(91843.25, result[0].Jun); + Assert.Equal(50000.99, result[1].Jun); + } - var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); - Assert.Equal("A1:C9", demension); - } + // Custom yyyy-MM-dd format is not converted to datetime + [Fact] + public async Task Issue222() + { + var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); + var rows = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal(typeof(DateTime), rows[1].A.GetType()); + Assert.Equal(new DateTime(2021, 4, 29), rows[1].A); } + // ASP.NET Webform gridview datasource can't use miniexcel queryasdatatable [Fact] - public async Task Issue142_Query() + public async Task Issue223() { - var path = PathHelper.GetFile("xlsx/TestIssue142.xlsx"); - var pathCsv = PathHelper.GetFile("xlsx/TestIssue142.csv"); - { - var rows = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); - Assert.Equal(0, rows[0].MyProperty1); - } + List> value = + [ + new() { { "A", null }, { "B", null } }, + new() { { "A", 123 }, { "B", new DateTime(2021, 1, 1) } }, + new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } + ]; + using var path = AutoDeletingPath.Create(); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); - { - await Assert.ThrowsAsync(async () => - { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); - }); - } - { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("CustomColumnName", rows[0].MyProperty1); - Assert.Null(rows[0].MyProperty7); - Assert.Equal("MyProperty2", rows[0].MyProperty2); - Assert.Equal("MyProperty103", rows[0].MyProperty3); - Assert.Equal("MyProperty100", rows[0].MyProperty4); - Assert.Equal("MyProperty102", rows[0].MyProperty5); - Assert.Equal("MyProperty6", rows[0].MyProperty6); - } + using var dt = await _excelImporter.QueryAsDataTableAsync(path.ToString()); +#pragma warning restore CS0618 + var columns = dt.Columns; + Assert.Equal(typeof(object), columns[0].DataType); + Assert.Equal(typeof(object), columns[1].DataType); - { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("CustomColumnName", rows[0].MyProperty1); - Assert.Null(rows[0].MyProperty7); - Assert.Equal("MyProperty2", rows[0].MyProperty2); - Assert.Equal("MyProperty103", rows[0].MyProperty3); - Assert.Equal("MyProperty100", rows[0].MyProperty4); - Assert.Equal("MyProperty102", rows[0].MyProperty5); - Assert.Equal("MyProperty6", rows[0].MyProperty6); - } + Assert.Equal(123.0, dt.Rows[1]["A"]); + Assert.Equal("HelloWorld", dt.Rows[2]["B"]); } - private class Issue142VO + /// SaveAsByTemplate single column demension index error + [Fact] + public async Task Issue226() { - [MiniExcelColumnName("CustomColumnName")] - public string MyProperty1 { get; set; } //index = 1 - [MiniExcelIgnore] - public string MyProperty7 { get; set; } //index = null - public string MyProperty2 { get; set; } //index = 3 - [MiniExcelColumnIndex(6)] - public string MyProperty3 { get; set; } //index = 6 - [MiniExcelColumnIndex("A")] // equal column index 0 - public string MyProperty4 { get; set; } - [MiniExcelColumnIndex(2)] - public string MyProperty5 { get; set; } //index = 2 - public string MyProperty6 { get; set; } //index = 4 + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); + await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } - private class Issue142VoOverIndex + // Support Xlsm AutoCheck + [Fact] + public async Task Issue227() { - [MiniExcelColumnIndex("Z")] - public int MyProperty1 { get; set; } - } + var xlsmPath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsm"); + Assert.Throws(() => _excelExporter.Export(xlsmPath.FilePath, new[] { new { V = "A1" }, new { V = "A2" } })); - private class Issue142VoExcelColumnNameNotFound - { - [MiniExcelColumnIndex("B")] - public int MyProperty1 { get; set; } + var path = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); + var rows1 = await _excelImporter.QueryAsync(path).ToListAsync(); + Assert.Equal(100, rows1.Count); + + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows1[0].ID); + Assert.Equal("Wade", rows1[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows1[0].BoD); + Assert.Equal(36, rows1[0].Age); + Assert.False(rows1[0].VIP); + Assert.Equal(5019.12m, rows1[0].Points); + Assert.Equal(1, rows1[0].IgnoredProperty); + + + await using var stream = File.OpenRead(path); + var rows2 = await _excelImporter.QueryAsync(stream).ToListAsync(); + Assert.Equal(100, rows2.Count); + + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows2[0].ID); + Assert.Equal("Wade", rows2[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows2[0].BoD); + Assert.Equal(36, rows2[0].Age); + Assert.False(rows2[0].VIP); + Assert.Equal(5019.12m, rows2[0].Points); + Assert.Equal(1, rows2[0].IgnoredProperty); } - /// - /// https://github.com/mini-software/MiniExcel/issues/150 - /// + // QueryAsDataTable error "Cannot set Column to be null" [Fact] - public async Task Issue150() + public async Task Issue229() { - var path = PathHelper.GetTempFilePath(); - - await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { 1, 2 })); - File.Delete(path); - - await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { "1", "2" })); - File.Delete(path); - - await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { '1', '2' })); - File.Delete(path); - - await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { DateTime.Now })); - File.Delete(path); - - await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { Guid.NewGuid() })); - File.Delete(path); + var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); + + using var dt = await _excelImporter.QueryAsDataTableAsync(path); + + foreach (DataColumn column in dt.Columns) + { + var v = dt.Rows[3][column]; + Assert.Equal(DBNull.Value, v); + } } - /// - /// https://github.com/mini-software/MiniExcel/issues/157 - /// + // SaveAs By data reader error : 'Invalid attempt to call FieldCount when reader is closed' [Fact] - public async Task Issue157() + public async Task Issue230() { + await using var conn = Db.GetConnection("Data Source=:memory:"); + await conn.OpenAsync(); + await using var cmd = conn.CreateCommand(); + cmd.CommandText = "select 1 id union all select 2"; + + await using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - List data = - [ - new() - { - ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"), - Name = "Wade", - BoD = new DateTime(2020, 9, 27), - Points = 5019.12m - }, - new() - { - ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"), - Name = "Felix", - BoD = new DateTime(2020, 10, 25), - Points = 7028.46m - }, - new() - { - ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"), - Name = "Phelan", - BoD = new DateTime(2020, 10, 25), - Points = 3835.7m, - VIP = true - }, - new() - { - ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"), - Name = "Samuel", - BoD = new DateTime(2020, 6, 21), - Points = 9351.71m - }, - new() + while (await reader.ReadAsync()) + { + for (int i = 0; i < reader.FieldCount; i++) { - ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"), - Name = "Raymond", - BoD = new DateTime(2021, 7, 12), - Points = 8209.76m, - VIP = true + var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; + _output.WriteLine(result); } - ]; - - var rowsWritten = await _excelExporter.ExportAsync(path, data); - Assert.Single(rowsWritten); - Assert.Equal(5, rowsWritten[0]); - - var q = await _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToListAsync(); - var rows = q.ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", (await _excelImporter.GetSheetNamesAsync(path))[0]); - - using var p = new ExcelPackage(new FileInfo(path)); - var ws = p.Workbook.Worksheets[0]; - Assert.Equal("Sheet1", ws.Name); - Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); + } } + + await using var conn2 = Db.GetConnection("Data Source=:memory:"); + await conn2.OpenAsync(); + await using var cmd2 = conn2.CreateCommand(); + cmd2.CommandText = "select 1 id union all select 2"; + + await using (var reader = await cmd2.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { - var path = PathHelper.GetFile("xlsx/TestIssue157.xlsx"); - { - var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", (await _excelImporter.GetSheetNamesAsync(path))[0]); - } - using (var p = new ExcelPackage(new FileInfo(path))) + while (await reader.ReadAsync()) { - var ws = p.Workbook.Worksheets.First(); - Assert.Equal("Sheet1", ws.Name); - Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); + for (int i = 0; i < reader.FieldCount; i++) + { + var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; + _output.WriteLine(result); + } } + } - { - var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(5, rows.Count); + await using var conn3 = Db.GetConnection("Data Source=:memory:"); + await conn3.OpenAsync(); + await using var cmd3 = conn3.CreateCommand(); + cmd3.CommandText = "select 1 id union all select 2"; + + await using (var reader = await cmd3.ExecuteReaderAsync(CommandBehavior.CloseConnection)) + { + using var path = AutoDeletingPath.Create(); - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader, printHeader: true); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + var rows = await _excelImporter.QueryAsync(path.ToString(), true).ToListAsync(); + Assert.Equal(1, rows[0].id); + Assert.Equal(2, rows[1].id); } } - /// - /// https://github.com/mini-software/MiniExcel/issues/149 - /// + // QueryAsDataTable A2=5.5 , A3=0.55/1.1 will case double type check error [Fact] - public async Task Issue149() + public async Task Issue233() { - char[] chars = - [ - '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', - '\u0009', // - '\u000A', // - '\u000B', '\u000C', - '\u000D', // - '\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', - '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', '\u007F' - ]; - var strings = chars.Select(s => s.ToString()).ToArray(); + var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); + + using var dt = await _excelImporter.QueryAsDataTableAsync(path); + var rows = dt.Rows; + + Assert.Equal(0.55, rows[0]["Size"]); + Assert.Equal("0.55/1.1", rows[1]["Size"]); + } + + // SaveAs support multiple sheets + [Fact] + public async Task Issue234() + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + var users = new[] { - var path = PathHelper.GetFile("xlsx/TestIssue149.xlsx"); - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.Select(s => (string)s.A).ToList(); - - for (int i = 0; i < chars.Length; i++) - { - if (i == 13) - continue; - - Assert.Equal(strings[i], rows[i]); - } - } - + new { Name = "Jack", Age = 25 }, + new { Name = "Mike", Age = 44 } + }; + var department = new[] { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); + new { ID = "01", Name = "HR" }, + new { ID = "02", Name = "IT" } + }; + var sheets = new Dictionary + { + ["users"] = users, + ["department"] = department + }; + var rowsWritten = await _excelExporter.ExportAsync(path, sheets); + + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); - var input = chars.Select(s => new { Test = s.ToString() }); - await _excelExporter.ExportAsync(path, input); - var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); + var sheetNames = await _excelImporter.GetSheetNamesAsync(path); + Assert.Equal("users", sheetNames[0]); + Assert.Equal("department", sheetNames[1]); - var rows = q.Select(s => (string)s.Test).ToList(); - for (int i = 0; i < chars.Length; i++) - { - _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); - if (i is 13 or 9 or 10) - continue; - - Assert.Equal(strings[i], rows[i]); - } + { + var rows = await _excelImporter.QueryAsync(path, true, sheetName: "users").ToListAsync(); + Assert.Equal("Jack", rows[0].Name); + Assert.Equal(25, rows[0].Age); + Assert.Equal("Mike", rows[1].Name); + Assert.Equal(44, rows[1].Age); } - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - var input = chars.Select(s => new { Test = s.ToString() }); - await _excelExporter.ExportAsync(path, input); - - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.Select(s => s.Test).ToList(); - - for (int i = 0; i < chars.Length; i++) - { - _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); - if (i is 13 or 9 or 10) - continue; - - Assert.Equal(strings[i], rows[i]); - } + var rows = await _excelImporter.QueryAsync(path, true, sheetName: "department").ToListAsync(); + Assert.Equal("01", rows[0].ID); + Assert.Equal("HR", rows[0].Name); + Assert.Equal("02", rows[1].ID); + Assert.Equal("IT", rows[1].Name); } } - private class Issue149VO - { - public string Test { get; set; } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/153 - /// + /// Support SaveAs by DataSet [Fact] - public async Task Issue153() + public async Task Issue235() { - var path = PathHelper.GetFile("xlsx/TestIssue153.xlsx"); - var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); - var rows = q.First() as IDictionary; + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - Assert.Equal( - [ - "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", - "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" - ], rows?.Keys); + var users = new DataTable { TableName = "users" }; + users.Columns.Add("Name", typeof(string)); + users.Columns.Add("Age", typeof(int)); + users.Rows.Add("Jack", 25); + users.Rows.Add("Mike", 44); + + var departments = new DataTable { TableName = "departments" }; + departments.Columns.Add("ID"); + departments.Columns.Add("Name"); + departments.Rows.Add("01", "HR"); + departments.Rows.Add("02", "IT"); + + DataSet sheets = new(); + sheets.Tables.Add(users); + sheets.Tables.Add(departments); + + var rowsWritten = await _excelExporter.ExportAsync(path, sheets); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); + + var sheetNames = await _excelImporter.GetSheetNamesAsync(path); + Assert.Equal("users", sheetNames[0]); + Assert.Equal("departments", sheetNames[1]); + + var rows1 = await _excelImporter.QueryAsync(path, true, sheetName: "users").ToListAsync(); + Assert.Equal("Jack", rows1[0].Name); + Assert.Equal(25, rows1[0].Age); + Assert.Equal("Mike", rows1[1].Name); + Assert.Equal(44, rows1[1].Age); + + var rows2 = await _excelImporter.QueryAsync(path, true, sheetName: "departments").ToListAsync(); + Assert.Equal("01", rows2[0].ID); + Assert.Equal("HR", rows2[0].Name); + Assert.Equal("02", rows2[1].ID); + Assert.Equal("IT", rows2[1].Name); } - /// - /// https://github.com/mini-software/MiniExcel/issues/137 - /// + /// Support Custom Datetime format [Fact] - public void Issue137() + public async Task Issue241() { - var path = PathHelper.GetFile("xlsx/TestIssue137.xlsx"); - { - var q = _excelImporter.QueryAsync(path).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()); - Assert.Equal(11, rows.Count); + var date1 = new DateTime(2021, 01, 04); + var date2 = new DateTime(2020, 04, 05); - { - var row = rows[0] as IDictionary; - Assert.Equal("比例", row!["A"]); - Assert.Equal("商品", row["B"]); - Assert.Equal("滿倉口數", row["C"]); - Assert.Equal(" ", row["D"]); - Assert.Equal(" ", row["E"]); - Assert.Equal(" ", row["F"]); - Assert.Equal(0.0, row["G"]); - Assert.Equal("1為港幣 0為台幣", row["H"]); - } - { - var row = rows[1] as IDictionary; - Assert.Equal(1.0, row!["A"]); - Assert.Equal("MTX", row["B"]); - Assert.Equal(10.0, row["C"]); - Assert.Null(row["D"]); - Assert.Null(row["E"]); - Assert.Null(row["F"]); - Assert.Null(row["G"]); - Assert.Null(row["H"]); - } - { - var row = rows[2] as IDictionary; - Assert.Equal(0.95, row!["A"]); - } - } + Issue241Dto[] value = + [ + new() { Name = "Jack", InDate = date1 }, + new() { Name = "Henry", InDate = date2 } + ]; - // dynamic query with head - { - var q = _excelImporter.QueryAsync(path, true).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()); - Assert.Equal(10, rows.Count); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + var rowsWritten = await _excelExporter.ExportAsync(path, value); - { - var row = rows[0] as IDictionary; - Assert.Equal(1.0, row!["比例"]); - Assert.Equal("MTX", row["商品"]); - Assert.Equal(10.0, row["滿倉口數"]); - Assert.Null(row["0"]); - Assert.Null(row["1為港幣 0為台幣"]); - } - - { - var row = rows[1] as IDictionary; - Assert.Equal(0.95, row!["比例"]); - } - } + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); - { - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(10, rows.Count); - - { - var row = rows[0]; - Assert.Equal(1, row.比例); - Assert.Equal("MTX", row.商品); - Assert.Equal(10, row.滿倉口數); - } + using var package = new ExcelPackage(path); + var cells = package.Workbook.Worksheets[0].Cells; - { - var row = rows[1]; - Assert.Equal(0.95, row.比例); - } - } + Assert.Equal(date1, DateTime.FromOADate((double)cells["B2"].Value)); + Assert.Equal("01 04, 2021", cells["B2"].Text); + Assert.Equal(date2, DateTime.FromOADate((double)cells["B3"].Value)); + Assert.Equal("04 05, 2020", cells["B3"].Text); } - private class Issue137ExcelRow + /// No error exception throw when reading xls file + [Fact] + public async Task Issue242() { - public double? 比例 { get; set; } - public string 商品 { get; set; } - public int? 滿倉口數 { get; set; } - } + var path = PathHelper.GetFile("xls/TestIssue242.xls"); + await Assert.ThrowsAsync(async () => _ = await _excelImporter.QueryAsync(path).ToListAsync()); + await using var stream = File.OpenRead(path); + await Assert.ThrowsAsync(async () => _ = await _excelImporter.QueryAsync(stream).ToListAsync()); + } - /// - /// https://github.com/mini-software/MiniExcel/issues/138 - /// + // SaveAsByTemplate support DateTime custom format [Fact] - public void Issue138() + public async Task Issue255() { - var path = PathHelper.GetFile("xlsx/TestIssue138.xlsx"); + var dt1 = new DateTime(2021, 01, 01); + var dt2 = new DateTime(2022, 01, 01); + + //template { - var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(6, rows.Count); - - foreach (var index in new[] { 0, 2, 5 }) + var templatePath = PathHelper.GetFile("xlsx/TestsIssue255_Template.xlsx"); + await using var ms = new MemoryStream(); + var value = new { - Assert.Equal(1, rows[index].實單每日損益); - Assert.Equal(2, rows[index].程式每日損益); - Assert.Equal("測試商品1", rows[index].商品); - Assert.Equal(111.11, rows[index].滿倉口數); - Assert.Equal(111.11, rows[index].波段); - Assert.Equal(111.11, rows[index].當沖); - } + Issue255DTO = new[] { new Issue255DTO { Time = dt1, Time2 = dt2 } } + }; + + await _excelTemplater.FillTemplateAsync(ms, templatePath, value); - foreach (var index in new[] { 1, 3, 4 }) - { - Assert.Null(rows[index].實單每日損益); - Assert.Null(rows[index].程式每日損益); - Assert.Null(rows[index].商品); - Assert.Null(rows[index].滿倉口數); - Assert.Null(rows[index].波段); - Assert.Null(rows[index].當沖); - } + ms.Seek(0, SeekOrigin.Begin); + using var package = new ExcelPackage(ms); + var cells = package.Workbook.Worksheets[0].Cells; + + Assert.Equal("2021", cells["A2"].Text); + Assert.Equal("2022", cells["B2"].Text); } + //export { + await using var ms = new MemoryStream(); + Issue255DTO[] value = + [ + new() { Time = dt1, Time2 = dt2 } + ]; - var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); + var rowsWritten = await _excelExporter.ExportAsync(ms, value); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); - foreach (var index in new[] { 0, 2, 5 }) - { - Assert.Equal(1, rows[index].實單每日損益); - Assert.Equal(2, rows[index].程式每日損益); - Assert.Equal("測試商品1", rows[index].商品); - Assert.Equal(111.11, rows[index].滿倉口數); - Assert.Equal(111.11, rows[index].波段); - Assert.Equal(111.11, rows[index].當沖); - } + ms.Seek(0, SeekOrigin.Begin); + using var package = new ExcelPackage(ms); - foreach (var index in new[] { 1, 3, 4 }) - { - Assert.Null(rows[index].實單每日損益); - Assert.Null(rows[index].程式每日損益); - Assert.Null(rows[index].商品); - Assert.Null(rows[index].滿倉口數); - Assert.Null(rows[index].波段); - Assert.Null(rows[index].當沖); - } + var cells = package.Workbook.Worksheets[0].Cells; + Assert.Equal(dt1, DateTime.FromOADate((double)cells["A2"].Value)); + Assert.Equal("2021", cells["A2"].Text); + Assert.Equal(dt2, DateTime.FromOADate((double)cells["B2"].Value)); + Assert.Equal("2022", cells["B2"].Text); } } - private class Issue138ExcelRow + // Dynamic QueryAsync custom format not using mapping format + [Fact] + public async Task Issue256() { - public DateTime? Date { get; set; } - public int? 實單每日損益 { get; set; } - public int? 程式每日損益 { get; set; } - public string 商品 { get; set; } - public double? 滿倉口數 { get; set; } - public double? 波段 { get; set; } - public double? 當沖 { get; set; } + var path = PathHelper.GetFile("xlsx/TestIssue256.xlsx"); + var q = await _excelImporter.QueryAsync(path).ToListAsync(); + var rows = q.ToList(); + + Assert.Equal(new DateTime(2003, 4, 16), rows[1].A); + Assert.Equal(new DateTime(2004, 4, 16), rows[1].B); } [Fact] @@ -1495,18 +1201,6 @@ public async Task Issue520() Assert.Equal(300.0, cells["C2"].Value); } - class Issue520Dto(long l1, DateTime dt, long l2) - { - [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] - public long PaymentValue { get; set; } = l1; - - [MiniExcelColumn(Format = "dd/MM/yyyy", Width = 15)] - public DateTime PaymentDate { get; set; } = dt; - - [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] - public long ValueToSettle { get; set; } = l2; - } - [Fact] public async Task TestIssue627() { @@ -1531,13 +1225,43 @@ public async Task TestIssue627() Assert.Equal("@", cell.Style.Numberformat.Format); } + [Fact] + public async Task TestIssue658() + { + static IEnumerable GetTestData() + { + yield return new() { FirstName = "Unit", LastName = "Test" }; + yield return new() { FirstName = "Unit1", LastName = "Test1" }; + yield return new() { FirstName = "Unit2", LastName = "Test2" }; + } + + using var memoryStream = new MemoryStream(); + var testData = GetTestData().ToList(); + await _excelExporter.ExportAsync(memoryStream, testData, configuration: new OpenXmlConfiguration + { + FastMode = true, + }); + + memoryStream.Position = 0; + var queryData = await _excelImporter.QueryAsync(memoryStream).ToListAsync(); + Assert.Equal(testData.Count, queryData.Count); + + var i = 0; + foreach (var data in testData) + { + Assert.Equal(data.FirstName, queryData[i].FirstName); + Assert.Equal(data.LastName, queryData[i].LastName); + i++; + } + } + [Fact] public async Task TestIssue951() { var templatePath = PathHelper.GetFile("xlsx/TestTemplateEasyFill.xlsx"); using var path = AutoDeletingPath.Create(); - var value = new Issue951 + var value = new Issue951Dto { Name = "Jack", CreateDate = new DateTime(2021, 01, 01), @@ -1548,14 +1272,4 @@ public async Task TestIssue951() // must not throw because of indexer await _excelTemplater.FillTemplateAsync(path.ToString(), templatePath, value); } - - class Issue951 - { - public string? Name { get; set; } - public DateTime CreateDate { get; set; } - public bool VIP { get; set; } - public double Points { get; set; } - - public object this[string test] => new(); - } } diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs similarity index 66% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs rename to tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs index ecb99e1f..529531ab 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Issues/MiniExcelGithubIssuesTests.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using MiniExcelLib.Core.Enums; using MiniExcelLib.Core.Exceptions; using MiniExcelLib.OpenXml.Picture; @@ -8,9 +7,9 @@ using NPOI.XSSF.UserModel; using LicenseContext = OfficeOpenXml.LicenseContext; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Issues; -public class MiniExcelIssueTests(ITestOutputHelper output) +public class MiniExcelGithubIssuesTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; @@ -18,134 +17,12 @@ public class MiniExcelIssueTests(ITestOutputHelper output) private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); - static MiniExcelIssueTests() + static MiniExcelGithubIssuesTests() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; } - /// - /// https://github.com/mini-software/MiniExcel/issues/549 - /// - [Fact] - public void TestIssue549() - { - var data = new[] - { - new{ id = 1, name = "jack" }, - new{ id = 2, name = "mike" } - }; - - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - _excelExporter.Export(path, data); - var rows = _excelImporter.Query(path, true).ToList(); - { - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - using var workbook = new XSSFWorkbook(stream); - - var sheet = workbook.GetSheetAt(0); - var a2 = sheet.GetRow(1).GetCell(0); - var b2 = sheet.GetRow(1).GetCell(1); - Assert.Equal((string)rows[0].id.ToString(), a2.NumericCellValue.ToString(CultureInfo.InvariantCulture)); - Assert.Equal((string)rows[0].name.ToString(), b2.StringCellValue); - } - } - - [Fact] - public void TestIssue24020201() - { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssue24020201.xlsx"); - var data = new Dictionary - { - ["title"] = "This's title", - ["B"] = new List> - { - new() { { "specialMark", 1 } }, - new() { { "specialMark", 2 } }, - new() { { "specialMark", 3 } }, - } - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, data); - } - - [Fact] - public void TestIssue553() - { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssue553.xlsx"); - var data = new - { - B = new[] - { - new{ ITM=1 }, - new{ ITM=2 }, - new{ ITM=3 } - } - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, data); - - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal(rows[2].A, 1); - Assert.Equal(rows[3].A, 2); - Assert.Equal(rows[4].A, 3); - } - - [Fact] - public void TestIssue289() - { - using var path = AutoDeletingPath.Create(); - DescriptionEnumDto[] value = - [ - new() { Name="0001", UserType=DescriptionEnum.V1 }, - new() { Name="0002", UserType=DescriptionEnum.V2 }, - new() { Name="0003", UserType=DescriptionEnum.V3 } - ]; - _excelExporter.Export(path.ToString(), value); - - var rows = _excelImporter.Query(path.ToString()).ToList(); - - Assert.Equal(DescriptionEnum.V1, rows[0].UserType); - Assert.Equal(DescriptionEnum.V2, rows[1].UserType); - Assert.Equal(DescriptionEnum.V3, rows[2].UserType); - } - - private class DescriptionEnumDto - { - public string Name { get; set; } - public DescriptionEnum UserType { get; set; } - } - - private enum DescriptionEnum - { - [Description("General User")] V1, - [Description("General Administrator")] V2, - [Description("Super Administrator")] V3 - } - - /// - /// Exception : MiniExcelLibs.Core.Exceptions.ExcelInvalidCastException: 'ColumnName : Date, CellRow : 2, Value : 2021-01-31 10:03:00 +08:00, it can't cast to DateTimeOffset type.' - /// - [Fact] - public void TestIssue430() - { - using var path = AutoDeletingPath.Create(); - TestIssue430Dto[] value = - [ - new() { Date = new DateTimeOffset(2021, 1, 31, 10, 3, 0, TimeSpan.FromHours(5)) } - ]; - _excelExporter.Export(path.ToString(), value); - - var testValue = _excelImporter.Query(path.ToString(), hasHeaderRow: true).First(); - Assert.Equal("2021-01-31 10:03:00", testValue.Date.ToString("yyyy-MM-dd HH:mm:ss")); - } - - private class TestIssue430Dto - { - [MiniExcelFormat("yyyy-MM-dd HH:mm:ss")] - public DateTimeOffset Date { get; set; } - } + private static bool IsDateFormatString(string formatCode) => DateTimeHelper.IsDateTimeFormat(formatCode); [Fact] public void TestIssue_DataReaderSupportDimension() @@ -167,1247 +44,1201 @@ public void TestIssue_DataReaderSupportDimension() Assert.Contains("", xml); } - /// - /// [ · Issue #413 · MiniExcel/MiniExcel] - /// (https://github.com/MiniExcel/MiniExcel/issues/413) - /// [Fact] - public void TestIssue413() + public void Issue87() { + var templatePath = PathHelper.GetFile("xlsx/TestTemplateCenterEmpty.xlsx"); using var path = AutoDeletingPath.Create(); var value = new { - list = new List> - { - new() { { "id","001"},{ "time",new DateTime(2022,12,25)} }, - new() { { "id","002"},{ "time",new DateTime(2022,9,23)} }, - } + Tests = Enumerable.Range(1, 5).Select((_, i) => new { test1 = i, test2 = i }) }; - var templatePath = PathHelper.GetFile("xlsx/TestIssue413.xlsx"); - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal("2022-12-25 00:00:00", rows[1].B); - Assert.Equal("2022-09-23 00:00:00", rows[2].B); + var rows = _excelImporter.Query(templatePath).ToList(); + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); } - /// - /// [SaveAs Support empty sharedstring · Issue #405 · MiniExcel/MiniExcel] - /// (https://github.com/MiniExcel/MiniExcel/issues/405) - /// [Fact] - public void TestIssue405() + public void TestIssue117() { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { id = 1, name = "test" } }; - _excelExporter.Export(path.ToString(), value); + var cacheFull = new SharedStringsDiskCache(Path.GetTempPath()); + for (int i = 0; i < 100; i++) + { + cacheFull[i] = i.ToString(); + } + for (int i = 0; i < 100; i++) + { + Assert.Equal(i.ToString(), cacheFull[i]); + } + Assert.Equal(100, cacheFull.Count); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/sharedStrings.xml"); - Assert.StartsWith("> value = - [ - new() - { - ["Id"] = 1, - ["Name"] = "Jack", - ["Date"] = new DateTime(2022, 04, 12), - ["Point"] = 123.456 - } - ]; - _excelExporter.Export(path.ToString(), value, configuration: config); - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal("Date", rows[0].A); - Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Jack", rows[1].B); - Assert.Equal("Account Point", rows[0].C); - Assert.Equal(123.456, rows[1].C); + var path1 = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); + var rows1 = _excelImporter.Query(path1, hasHeaderRow: true, configuration: config).ToList(); + Assert.Equal("HR", rows1[0].Department); + Assert.Equal("HR", rows1[1].Department); + Assert.Equal("HR", rows1[2].Department); + Assert.Equal("IT", rows1[3].Department); + Assert.Equal("IT", rows1[4].Department); + Assert.Equal("IT", rows1[5].Department); + + var path2 = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); + var rows2 = _excelImporter.Query(path2, hasHeaderRow: true, configuration: config).ToList(); + Assert.Equal("V1", rows2[2].Test1); + Assert.Equal("V2", rows2[5].Test2); + Assert.Equal("V3", rows2[1].Test3); + Assert.Equal("V4", rows2[2].Test4); + Assert.Equal("V5", rows2[3].Test5); + Assert.Equal("V6", rows2[5].Test5); } [Fact] - public void TestIssue369() + public void Issue132() { - var config = new OpenXmlConfiguration { - DynamicColumns = - [ - new DynamicExcelColumn("id") { Ignore=true }, - new DynamicExcelColumn("name") { Index=1, Width=10 }, - new DynamicExcelColumn("createdate") { Index=0, Format="yyyy-MM-dd", Width=15 }, - new DynamicExcelColumn("point") { Index=2, Name="Account Point" } - ] - }; - using var path = AutoDeletingPath.Create(); - var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12), point = 123.456 } }; - _excelExporter.Export(path.ToString(), value, configuration: config); + using var path = AutoDeletingPath.Create(); + var value = new[] { + new { name = "Jack", Age = 25, InDate = new DateTime(2021,01,03)}, + new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, + }; - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal("createdate", rows[0].A); - Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); - Assert.Equal("name", rows[0].B); - Assert.Equal("Jack", rows[1].B); - Assert.Equal("Account Point", rows[0].C); - Assert.Equal(123.456, rows[1].C); - } + _excelExporter.Export(path.ToString(), value); + } - [Fact] - public void TestIssueI4ZYUU() - { - using var path = AutoDeletingPath.Create(); - - var dt = new DateTime(2022, 10, 15); - TestIssueI4ZYUUDto[] value = [new() { MyProperty = "1", MyProperty2 = dt }]; - _excelExporter.Export(path.ToString(), value); + { + using var path = AutoDeletingPath.Create(); + var value = new[] + { + new { name = "Jack", Age = 25, InDate = new DateTime(2021,01,03)}, + new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, + }; + var config = new OpenXmlConfiguration + { + TableStyles = TableStyles.None + }; + _excelExporter.Export(path.ToString(), value, configuration: config); + } - using var workbook = new ClosedXML.Excel.XLWorkbook(path.ToString()); - var ws = workbook.Worksheet(1); + { + using var path = AutoDeletingPath.Create(); - Assert.Equal(dt, ws.Cell(2, "B").Value.GetDateTime()); - Assert.Equal("2022-10", ws.Cell(2, "B").GetFormattedString()); - Assert.True(ws.Column("A").Width > 0); - Assert.True(ws.Column("B").Width > 0); - } + var dt = new DataTable(); + dt.Columns.Add("Name"); + dt.Columns.Add("Age"); + dt.Columns.Add("Date"); - private class TestIssueI4ZYUUDto - { - [MiniExcelColumn(Name = "ID", Index = 0)] - public string MyProperty { get; set; } - [MiniExcelColumn(Name = "CreateDate", Index = 1, Format = "yyyy-MM", Width = 100)] - public DateTime MyProperty2 { get; set; } - } + dt.Rows.Add("Jack", 25, new DateTime(2021, 01, 03)); + dt.Rows.Add("Henry", 36, new DateTime(2021, 01, 03)); - [Fact] - public void TestIssue360() - { - var path = PathHelper.GetFile("xlsx/NotDuplicateSharedStrings_10x100.xlsx"); - var config = new OpenXmlConfiguration { SharedStringCacheSize = 1 }; - var sheets = _excelImporter.GetSheetNames(path); - foreach (var sheetName in sheets) - { - var dt = _excelImporter.QueryAsDataTable(path, hasHeaderRow: true, sheetName: sheetName, configuration: config); + _excelExporter.Export(path.ToString(), dt); } } [Fact] - public void TestIssue117() + public void TestIssues133() { - var cacheFull = new SharedStringsDiskCache(Path.GetTempPath()); - for (int i = 0; i < 100; i++) - { - cacheFull[i] = i.ToString(); - } - for (int i = 0; i < 100; i++) { - Assert.Equal(i.ToString(), cacheFull[i]); - } - Assert.Equal(100, cacheFull.Count); + using var path = AutoDeletingPath.Create(); - var cacheEmpty = new SharedStringsDiskCache(Path.GetTempPath()); - Assert.Empty(cacheEmpty); - } - - [Fact] - public void TestIssue352() - { - { - using var table = new DataTable(); - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); + var value = new DataTable(); + value.Columns.Add("Id"); + value.Columns.Add("Name"); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToList(); - using var path = AutoDeletingPath.Create(); - var reader = table.CreateDataReader(); - _excelExporter.Export(path.ToString(), reader); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; + Assert.Equal("Id", rows[0].A); + Assert.Equal("Name", rows[0].B); + Assert.Single(rows); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } - { - using var table = new DataTable(); - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - using var path = AutoDeletingPath.Create(); - var reader = table.CreateDataReader(); - _excelExporter.Export(path.ToString(), reader, false); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; - } { - using var table = new DataTable(); - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - using var path = AutoDeletingPath.Create(); - var reader = table.CreateDataReader(); - _excelExporter.Export(path.ToString(), reader); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; + + var value = Array.Empty(); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToList(); + + Assert.Equal("Id", rows[0].A); + Assert.Equal("Name", rows[0].B); + Assert.Single(rows); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } } - [Theory] - [InlineData(true, 1)] - [InlineData(false, 0)] - public void TestIssue401(bool autoFilter, int count) + [Fact] + public void Issue137() { - // Test for DataTable + var path = PathHelper.GetFile("xlsx/TestIssue137.xlsx"); { - var table = new DataTable(); + var rows = _excelImporter.Query(path).ToList(); + var first = (IDictionary)rows[0]; // 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); { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); + var row = rows[0] as IDictionary; + Assert.Equal("比例", row!["A"]); + Assert.Equal("商品", row["B"]); + Assert.Equal("滿倉口數", row["C"]); + Assert.Equal(" ", row["D"]); + Assert.Equal(" ", row["E"]); + Assert.Equal(" ", row["F"]); + Assert.Equal(0.0, row["G"]); + Assert.Equal("1為港幣 0為台幣", row["H"]); } - - var reader = table.CreateDataReader(); - using var path = AutoDeletingPath.Create(); - var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - _excelExporter.Export(path.ToString(), reader, configuration: config); - - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; - Assert.Equal(count, cnt); - } - { - var table = new DataTable(); { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); + var row = rows[1] as IDictionary; + Assert.Equal(1.0, row!["A"]); + Assert.Equal("MTX", row["B"]); + Assert.Equal(10.0, row["C"]); + Assert.Null(row["D"]); + Assert.Null(row["E"]); + Assert.Null(row["F"]); + Assert.Null(row["G"]); + Assert.Null(row["H"]); } - var reader = table.CreateDataReader(); - using var path = AutoDeletingPath.Create(); - var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - _excelExporter.Export(path.ToString(), reader, false, configuration: config); - - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; - Assert.Equal(count, cnt); - } - { - var table = new DataTable(); { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); + var row = rows[2] as IDictionary; + Assert.Equal(0.95, row!["A"]); } - var reader = table.CreateDataReader(); - using var path = AutoDeletingPath.Create(); - var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - _excelExporter.Export(path.ToString(), reader, configuration: config); - - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "").Count; - Assert.Equal(count, cnt); } - // Test for DataReader + // dynamic query with head { - using var path = AutoDeletingPath.Create(); - var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - using (var connection = Db.GetConnection("Data Source=:memory:")) + var rows = _excelImporter.Query(path, true).ToList(); + var first = (IDictionary)rows[0]; //![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); { - connection.Open(); - - using var command = connection.CreateCommand(); - command.CommandText = - """ - SELECT - 'MiniExcel' as Column1, - 1 as Column2 - - UNION ALL - SELECT 'Github', 2 - """; - - using var reader = command.ExecuteReader(); - _excelExporter.Export(path.ToString(), reader, configuration: config); + var row = rows[0] as IDictionary; + Assert.Equal(1.0, row!["比例"]); + Assert.Equal("MTX", row["商品"]); + Assert.Equal(10.0, row["滿倉口數"]); + Assert.Null(row["0"]); + Assert.Null(row["1為港幣 0為台幣"]); } - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "autoFilter").Count; - Assert.Equal(count, cnt); - } - - { - var xlsxPath = PathHelper.GetFile("xlsx/Test5x2.xlsx"); - using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); - var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - - using (var connection = new SQLiteConnection(connectionString)) { - connection.Execute("create table T (A varchar(20),B varchar(20));"); + var row = rows[1] as IDictionary; + Assert.Equal(0.95, row!["比例"]); } + } - using (var connection = new SQLiteConnection(connectionString)) + { + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal(10, rows.Count); { - connection.Open(); - using (var transaction = connection.BeginTransaction()) - using (var stream = File.OpenRead(xlsxPath)) - { - var rows = _excelImporter.Query(stream); - foreach (var row in rows) - connection.Execute( - "insert into T (A,B) values (@A,@B)", - new { row.A, row.B }, - transaction: transaction); - - transaction.Commit(); - } + var row = rows[0]; + Assert.Equal(1, row.比例); + Assert.Equal("MTX", row.商品); + Assert.Equal(10, row.滿倉口數); } - using var path = AutoDeletingPath.Create(); - var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - using (var connection = new SQLiteConnection(connectionString)) { - using var command = new SQLiteCommand("select * from T", connection); - connection.Open(); - using var reader = command.ExecuteReader(); - _excelExporter.Export(path.ToString(), reader, configuration: config); + var row = rows[1]; + Assert.Equal(0.95, row.比例); } - - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - var cnt = Regex.Matches(xml, "autoFilter").Count; - Assert.Equal(count, cnt); } } [Fact] - public async Task TestIssue307() + public void Issue138() { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - var value = new[] { new { id = 1, name = "Jack" } }; + var path = PathHelper.GetFile("xlsx/TestIssue138.xlsx"); + var rows1 = _excelImporter.Query(path, true).ToList(); + Assert.Equal(6, rows1.Count); - await _excelExporter.ExportAsync(path, value); - Assert.Throws(() => _excelExporter.Export(path, value)); + foreach (var index in new[] { 0, 2, 5 }) + { + Assert.Equal(1, rows1[index].實單每日損益); + Assert.Equal(2, rows1[index].程式每日損益); + Assert.Equal("測試商品1", rows1[index].商品); + Assert.Equal(111.11, rows1[index].滿倉口數); + Assert.Equal(111.11, rows1[index].波段); + Assert.Equal(111.11, rows1[index].當沖); + } - await _excelExporter.ExportAsync(path, value, overwriteFile: true); - await Assert.ThrowsAsync(async () => await _excelExporter.ExportAsync(path, value)); - await _excelExporter.ExportAsync(path, value, overwriteFile: true); - } + foreach (var index in new[] { 1, 3, 4 }) + { + Assert.Null(rows1[index].實單每日損益); + Assert.Null(rows1[index].程式每日損益); + Assert.Null(rows1[index].商品); + Assert.Null(rows1[index].滿倉口數); + Assert.Null(rows1[index].波段); + Assert.Null(rows1[index].當沖); + } - [Fact] - public void TestIssue310() - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new TestIssue310Dto { V1 = null }, new TestIssue310Dto { V1 = 2 } }; - _excelExporter.Export(path.ToString(), value); - var rows = _excelImporter.Query(path.ToString()).ToList(); - } + var rows2 = _excelImporter.Query(path).ToList(); + Assert.Equal(6, rows2.Count); + Assert.Equal(new DateTime(2021, 3, 1), rows2[0].Date); - [Fact] - public void TestIssue310Fix497() - { - using var path = AutoDeletingPath.Create(); - var value = new[] + foreach (var index in new[] { 0, 2, 5 }) { - new TestIssue310Dto { V1 = null }, - new TestIssue310Dto { V1 = 2 } - }; - _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { EnableWriteNullValueCell = false }); - var rows = _excelImporter.Query(path.ToString()).ToList(); - } + Assert.Equal(1, rows2[index].實單每日損益); + Assert.Equal(2, rows2[index].程式每日損益); + Assert.Equal("測試商品1", rows2[index].商品); + Assert.Equal(111.11, rows2[index].滿倉口數); + Assert.Equal(111.11, rows2[index].波段); + Assert.Equal(111.11, rows2[index].當沖); + } - private class TestIssue310Dto - { - public int? V1 { get; set; } + foreach (var index in new[] { 1, 3, 4 }) + { + Assert.Null(rows2[index].實單每日損益); + Assert.Null(rows2[index].程式每日損益); + Assert.Null(rows2[index].商品); + Assert.Null(rows2[index].滿倉口數); + Assert.Null(rows2[index].波段); + Assert.Null(rows2[index].當沖); + } } - /// - /// Excel was unable to open the file https://github.com/mini-software/MiniExcel/issues/343 - /// [Fact] - public void TestIssue343() + public void Issue149() { - var date = DateTime.Parse("2022-03-17 09:32:06.111", CultureInfo.InvariantCulture); - using var path = AutoDeletingPath.Create(); + char[] chars = + [ + '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', + '\u0009', // + '\u000A', // + '\u000B','\u000C', + '\u000D', // + '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', + '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' + ]; + var strings = chars.Select(s => s.ToString()).ToArray(); - CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ff-Latn"); - var table = new DataTable(); { - table.Columns.Add("time1", typeof(DateTime)); - table.Columns.Add("time2", typeof(DateTime)); - table.Rows.Add(date, date); - table.Rows.Add(date, date); + var path = PathHelper.GetFile("xlsx/TestIssue149.xlsx"); + var rows = _excelImporter.Query(path).Select(s => (string)s.A).ToList(); + for (int i = 0; i < chars.Length; i++) + { + //output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); + if (i == 13) + continue; + + Assert.Equal(strings[i], rows[i]); + } } - var reader = table.CreateDataReader(); - _excelExporter.Export(path.ToString(), reader); - var rows = _excelImporter.Query(path.ToString(), true).ToArray(); - Assert.Equal(date, rows[0].time1); - Assert.Equal(date, rows[0].time2); + { + using var path = AutoDeletingPath.Create(); + var input = chars.Select(s => new { Test = s.ToString() }); + _excelExporter.Export(path.ToString(), input); + + var rows = _excelImporter.Query(path.ToString(), true).Select(s => (string)s.Test).ToList(); + for (int i = 0; i < chars.Length; i++) + { + _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); + if (i is 13 or 9 or 10) + continue; + + Assert.Equal(strings[i], rows[i]); + } + } + + { + using var path = AutoDeletingPath.Create(); + var input = chars.Select(s => new { Test = s.ToString() }); + _excelExporter.Export(path.ToString(), input); + + var rows = _excelImporter.Query(path.ToString()).Select(s => s.Test).ToList(); + for (int i = 0; i < chars.Length; i++) + { + _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); + if (i is 13 or 9 or 10) + continue; + + Assert.Equal(strings[i], rows[i]); + } + } } [Fact] - public void TestIssueI4YCLQ_2() + public void Issue150() { - var path = PathHelper.GetFile("xlsx/TestIssueI4YCLQ_2.xlsx"); - var rows = _excelImporter.Query(path, startCell: "B2").ToList(); + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - Assert.Null(rows[0].站点编码); - Assert.Equal("N1", rows[0].站址名称); - Assert.Equal("a", rows[0].值1); - Assert.Equal("b", rows[0].值2); - Assert.Equal("c", rows[0].值3); - Assert.Equal("A1", rows[0].资源ID); - Assert.Equal("A", rows[0].值4); - Assert.Equal("B", rows[0].值5); - Assert.Equal("C", rows[0].值6); - Assert.Null(rows[0].值7); - Assert.Null(rows[0].值8); - } + Assert.Throws(() => _excelExporter.Export(path, new[] { 1, 2 })); + File.Delete(path); - private class TestIssueI4YCLQ_2Dto - { - [MiniExcelColumnIndex("A")] - public string 站点编码 { get; set; } - [MiniExcelColumnIndex("B")] - public string 站址名称 { get; set; } - [MiniExcelColumnIndex("C")] - public string 值1 { get; set; } - [MiniExcelColumnIndex("D")] - public string 值2 { get; set; } - [MiniExcelColumnIndex("E")] - public string 值3 { get; set; } - [MiniExcelColumnIndex("F")] - public string 资源ID { get; set; } - [MiniExcelColumnIndex("G")] - public string 值4 { get; set; } - [MiniExcelColumnIndex("H")] - public string 值5 { get; set; } - [MiniExcelColumnIndex("I")] - public string 值6 { get; set; } - public string 值7 { get; set; } - [MiniExcelColumnName("NotExist")] - public string 值8 { get; set; } - } + Assert.Throws(() => _excelExporter.Export(path, new[] { "1", "2" })); + File.Delete(path); + Assert.Throws(() => _excelExporter.Export(path, new[] { '1', '2' })); + File.Delete(path); - [Fact] - public void TestIssueI4WM67() - { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WM67.xlsx"); - var value = new Dictionary - { - ["users"] = Array.Empty() - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Single(rows); + Assert.Throws(() => _excelExporter.Export(path, new[] { DateTime.Now })); + File.Delete(path); + + Assert.Throws(() => _excelExporter.Export(path, new[] { Guid.NewGuid() })); + File.Delete(path); } - private class TestIssueI4WM67Dto + [Fact] + public void Issue153() { - public int ID { get; set; } - public string Name { get; set; } + var path = PathHelper.GetFile("xlsx/TestIssue153.xlsx"); + var rows = _excelImporter.Query(path, true).First() as IDictionary; + Assert.Equal( + [ + "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", + "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" + ], rows?.Keys); } [Fact] - public void TestIssueI4WXFB() + public void Issue157() { { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WXFB.xlsx"); - var value = new Dictionary - { - ["Name"] = "Jack", - ["Amount"] = 1000, - ["Department"] = "HR" - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - } + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + + List data = + [ + new() + { + ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"), + Name = "Wade", + BoD = new DateTime(2020, 9, 27), + Points = 5019.12m + }, + new() + { + ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"), + Name = "Felix", + BoD = new DateTime(2020, 10, 25), + Points = 7028.46m + }, + new() + { + ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"), + Name = "Phelan", + BoD = new DateTime(2020, 10, 25), + Points = 3835.7m, + VIP = true + }, + new() + { + ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"), + Name = "Samuel", + BoD = new DateTime(2020, 6, 21), + Points = 9351.71m + }, + new() + { + ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"), + Name = "Raymond", + BoD = new DateTime(2021, 7, 12), + Points = 8209.76m, + VIP = true + } + ]; + + _excelExporter.Export(path, data); + + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); + Assert.Equal(6, rows.Count); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); + using var p = new ExcelPackage(new FileInfo(path)); + var ws = p.Workbook.Worksheets.First(); + Assert.Equal("Sheet1", ws.Name); + Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); + } { - var config = new OpenXmlConfiguration + var path = PathHelper.GetFile("xlsx/TestIssue157.xlsx"); + { - IgnoreTemplateParameterMissing = false - }; - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssueI4WXFB.xlsx"); - var value = new Dictionary + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); + Assert.Equal(6, rows.Count); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); + } + using (var p = new ExcelPackage(new FileInfo(path))) { - ["Name"] = "Jack", - ["Amount"] = 1000, - ["Department"] = "HR" - }; - Assert.Throws(() => _excelTemplater.FillTemplate(path.ToString(), templatePath, value, configuration: config)); + var ws = p.Workbook.Worksheets.First(); + Assert.Equal("Sheet1", ws.Name); + Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); + } + + { + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); + Assert.Equal(5, rows.Count); + + Assert.Equal(new Guid("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); + Assert.Equal("Wade", rows[0].Name); + Assert.Equal(new DateTime(2020,9,27), rows[0].BoD); + Assert.False(rows[0].VIP); + Assert.Equal(5019.12m, rows[0].Points); + Assert.Equal(1, rows[0].IgnoredProperty); + } } } + // Query Support StartCell [Fact] - public void TestIssue331_2() + public void Issue147() { - var cln = CultureInfo.CurrentCulture.Name; - CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("cs-CZ"); - - var config = new OpenXmlConfiguration - { - Culture = CultureInfo.GetCultureInfo("cs-CZ") - }; + var path1 = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); + var rows1 = _excelImporter.Query(path1, hasHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToList(); - var rnd = new Random(); - var data = Enumerable.Range(1, 100).Select(x => new TestIssue331Dto - { - Number = x, - Text = $"Number {x}", - DecimalNumber = (decimal)rnd.NextDouble(), - DoubleNumber = rnd.NextDouble() - }); + Assert.Equal(["C", "D", "E"], (rows1[0] as IDictionary)?.Keys); + Assert.Equal(new[]{"Column1", "Column2", "Column3"}, new[] { rows1[0].C as string, rows1[0].D as string, rows1[0].E as string }); + Assert.Equal(new[]{"C4", "D4", "E4"}, new[] { rows1[1].C as string, rows1[1].D as string, rows1[1].E as string }); + Assert.Equal(new[]{"C9", "D9", "E9"}, new[] { rows1[6].C as string, rows1[6].D as string, rows1[6].E as string }); + Assert.Equal(new[]{"C12", "D12", "E12"}, new[] { rows1[9].C as string, rows1[9].D as string, rows1[9].E as string }); + Assert.Equal(new[]{"C13", "D13", "E13"}, new[] { rows1[10].C as string, rows1[10].D as string, rows1[10].E as string }); + foreach (var i in new[] { 4, 5, 7, 8 }) + Assert.Equal(expected: [null, null, null], new[] { rows1[i].C as string, rows1[i].D as string, rows1[i].E as string }); - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), data, configuration: config); - CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); - } + Assert.Equal(11, rows1.Count); - [Fact] - public void TestIssue331() - { - var cln = CultureInfo.CurrentCulture.Name; - CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("cs-CZ"); - var data = Enumerable.Range(1, 10).Select(x => new TestIssue331Dto - { - Number = x, - Text = $"Number {x}", - DecimalNumber = x / 2m, - DoubleNumber = x / 2d - }); + var columns1 = _excelImporter.GetColumnNames(path1, startCell: "C3"); + Assert.Equal(["C", "D", "E"], columns1); - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), data); + var path2 = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); + var rows2 = _excelImporter.Query(path2, hasHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToList(); - var rows = _excelImporter.Query(path.ToString(), startCell: "A2").ToArray(); - Assert.Equal(1.5, rows[2].B); - Assert.Equal(1.5, rows[2].C); + Assert.Equal(["Column1", "Column2", "Column3"], (rows2[0] as IDictionary)?.Keys); + Assert.Equal(new[]{"C4", "D4", "E4"}, new[] { rows2[0].Column1 as string, rows2[0].Column2 as string, rows2[0].Column3 as string }); + Assert.Equal(new[]{"C9", "D9", "E9"}, new[] { rows2[5].Column1 as string, rows2[5].Column2 as string, rows2[5].Column3 as string }); + Assert.Equal(new[]{"C12", "D12", "E12"}, new[] { rows2[8].Column1 as string, rows2[8].Column2 as string, rows2[8].Column3 as string }); + Assert.Equal(new[]{"C13", "D13", "E13"}, new[] { rows2[9].Column1 as string, rows2[9].Column2 as string, rows2[9].Column3 as string }); + + foreach (var i in new[] { 3, 4, 6, 7 }) + { + Assert.Equal(new string?[]{null, null, null}, new[] { rows2[i].Column1 as string, rows2[i].Column2 as string, rows2[i].Column3 as string }); + } - CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); - } + Assert.Equal(10, rows2.Count); - private class TestIssue331Dto - { - public int Number { get; set; } - public decimal DecimalNumber { get; set; } - public double DoubleNumber { get; set; } - public string Text { get; set; } + var columns2 = _excelImporter.GetColumnNames(path2, hasHeaderRow: true, startCell: "C3"); + Assert.Equal(["Column1", "Column2", "Column3"], columns2); } [Fact] - public void TestIssueI4TXGT() + public void TestIssue186() { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - var value = new[] { new TestIssueI4TXGTDto { ID = 1, Name = "Apple", Spc = "X", Up = 6999 } }; + var originPath = PathHelper.GetFile("xlsx/TestIssue186_Template.xlsx"); + using var path = AutoDeletingPath.Create(); + File.Copy(originPath, path.FilePath); - _excelExporter.Export(path, value); - { - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal("ID", rows[0].A); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Specification", rows[0].C); - Assert.Equal("Unit Price", rows[0].D); - } - { - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(1, rows[0].ID); - Assert.Equal("Apple", rows[0].Name); - Assert.Equal("X", rows[0].Spc); - Assert.Equal(6999, rows[0].Up); - } - } + MiniExcelPicture[] images = + [ + new() + { + ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")), + SheetName = null, // default null is first sheet + CellAddress = "C3", // required + }, + new() + { + ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), + PictureType = "image/png", // default PictureType = image/png + SheetName = "Demo", + CellAddress = "C9", // required + WidthPx = 100, + HeightPx = 100 + } + ]; - private class TestIssueI4TXGTDto - { - public int ID { get; set; } - public string Name { get; set; } - [DisplayName("Specification")] - public string Spc { get; set; } - [DisplayName("Unit Price")] - public decimal Up { get; set; } + _excelTemplater.AddPicture(path.FilePath, images); } + // SaveAs default theme support filter mode - https://github.com/mini-software/MiniExcel/issues/190 [Fact] - public void TestIssue328() + public void TestIssue190() { - using var path = AutoDeletingPath.Create(); - var value = new[] { - new - { - id=1, - name="Jack", - indate=new DateTime(2022,5,13), - file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")) - }, - new - { - id=2, - name="Henry", - indate=new DateTime(2022,4,10), - file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")) - }, - }; - _excelExporter.Export(path.ToString(), value); - - var rowIndx = 0; - using var reader = _excelImporter.GetDataReader(path.ToString(), true); - - Assert.Equal("id", reader.GetName(0)); - Assert.Equal("name", reader.GetName(1)); - Assert.Equal("indate", reader.GetName(2)); - Assert.Equal("file", reader.GetName(3)); + using var path = AutoDeletingPath.Create(); + var value = new TestIssue190Dto[] { }; + _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { AutoFilter = false }); - while (reader.Read()) + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.DoesNotContain("", sheetXml); + } { - for (int i = 0; i < reader.FieldCount; i++) - { - var v = reader.GetValue(i); - if (rowIndx == 0 && i == 0) Assert.Equal(1.0, v); - if (rowIndx == 0 && i == 1) Assert.Equal("Jack", v); - if (rowIndx == 0 && i == 2) Assert.Equal(new DateTime(2022, 5, 13), v); - if (rowIndx == 0 && i == 3) Assert.Equal(File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")), v); - if (rowIndx == 1 && i == 0) Assert.Equal(2.0, v); - if (rowIndx == 1 && i == 1) Assert.Equal("Henry", v); - if (rowIndx == 1 && i == 2) Assert.Equal(new DateTime(2022, 4, 10), v); - if (rowIndx == 1 && i == 3) Assert.Equal(File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")), v); - } - rowIndx++; + using var path = AutoDeletingPath.Create(); + var value = new TestIssue190Dto[] { }; + _excelExporter.Export(path.ToString(), value); + + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.Contains("", sheetXml); } + { + using var path = AutoDeletingPath.Create(); + TestIssue190Dto[] value = + [ + new() { ID = 1, Name = "Jack", Age = 32 }, + new() { ID = 2, Name = "Lisa", Age = 45 } + ]; + _excelExporter.Export(path.ToString(), value); - //TODO:How to resolve empty body sheet? + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.Contains("", sheetXml); + } } [Fact] - public void TestIssue327() + public void Issue193() { - using var path = AutoDeletingPath.Create(); - var value = new[] { - new { id = 1, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")) }, - new { id = 2, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")) }, - new { id = 3, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.html")) }, - }; - _excelExporter.Export(path.ToString(), value); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); + var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - Assert.Equal(value[0].file, rows[0].file); - Assert.Equal(value[1].file, rows[1].file); - Assert.Equal(value[2].file, rows[2].file); - Assert.Equal("Hello MiniExcel", Encoding.UTF8.GetString(rows[1].file)); - Assert.Equal("Hello MiniExcel", Encoding.UTF8.GetString(rows[2].file)); - } + // 1. By Class + var value = new + { + title = "FooCompany", + managers = new[] + { + new { name = "Jack", department = "HR" }, + new { name = "Loan", department = "IT" } + }, + employees = new[] + { + new { name = "Wade", department = "HR" }, + new { name = "Felix", department = "HR" }, + new { name = "Eric", department = "IT" }, + new { name = "Keaton", department = "IT" } + } + }; + _excelTemplater.FillTemplate(path, templatePath, value); - /// - /// https://github.com/mini-software/MiniExcel/issues/325 - /// - [Fact] - public void TestIssue325() - { - using var path = AutoDeletingPath.Create(); - var value = new Dictionary - { - { "sheet1",new[]{ new { id = 1, date = DateTime.Parse("2022-01-01") } }}, - { "sheet2",new[]{ new { id = 2, date = DateTime.Parse("2022-01-01") } }}, - }; - _excelExporter.Export(path.ToString(), value); + foreach (var sheetName in _excelImporter.GetSheetNames(path)) + { + var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); + Assert.Equal(9, rows.Count); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/_rels/sheet2.xml.rels"); - var cnt = Regex.Matches(xml, "Id=\"drawing2\"").Count; - Assert.True(cnt == 1); - } + 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); - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I49RZH - /// https://github.com/mini-software/MiniExcel/issues/305 - /// - [Fact] - public async Task TestIssueI49RZH() - { - var dt = new DateTime(2022, 01, 22); + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); - using var path = AutoDeletingPath.Create(); - TestIssueI49RZHDto[] value = - [ - new() { dd = dt }, - new() { dd = null } - ]; - await _excelExporter.ExportAsync(path.FilePath, value, overwriteFile: true); + Assert.Equal("Eric", rows[7].B); + Assert.Equal("IT", rows[7].C); + Assert.Equal("Keaton", rows[8].B); + Assert.Equal("IT", rows[8].C); - using var package = new ExcelPackage(path.ToString()); - var cells = package.Workbook.Worksheets[0].Cells; - - Assert.Equal(dt, DateTime.FromOADate((double)cells["A2"].Value)); - Assert.Equal("22-01-2022", cells["A2"].Text); - } + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C9", dimension); - private class TestIssueI49RZHDto - { - [MiniExcelFormat("dd-MM-yyyy")] - public DateTime? dd { get; set; } - } + /*TODO:row can't contain xmlns*/ + // https://user-images.githubusercontent.com/12729184/114998840-ead44500-9ed3-11eb-8611-58afb98faed9.png - /// - /// https://github.com/mini-software/MiniExcel/issues/312 - /// - [Fact] - public void TestIssue312() - { - using var path = AutoDeletingPath.Create(); - TestIssue312Dto[] value = - [ - new() { Value = 12_345.6789 }, - new() { Value = null } - ]; - _excelExporter.Export(path.ToString(), value); + } + } - using var package = new ExcelPackage(path.ToString()); - var cells = package.Workbook.Worksheets[0].Cells; + { + var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplex.xlsx"); + using var path = AutoDeletingPath.Create(); - var fmt = cells["A2"].Style.Numberformat.Format; - Assert.Equal(12_345.68.ToString(fmt), cells["A2"].Text); - Assert.Equal(12_345.6789, (double)cells["A2"].Value); - } + // 2. By Dictionary + var value = new Dictionary + { + ["title"] = "FooCompany", + ["managers"] = new[] { + new {name="Jack",department="HR"}, + new {name="Loan",department="IT"} + }, + ["employees"] = new[] { + new {name="Wade",department="HR"}, + new {name="Felix",department="HR"}, + new {name="Eric",department="IT"}, + new {name="Keaton",department="IT"} + } + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); - private class TestIssue312Dto - { - [MiniExcelFormat("0,0.00")] - public double? Value { get; set; } - } + 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); - /// - /// Query type conversion error - /// https://github.com/mini-software/MiniExcel/issues/309 - /// - [Fact] - public void TestIssue209() - { - try - { - var path = PathHelper.GetFile("xlsx/TestIssue309.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - } - catch (ValueNotAssignableException ex) - { - Assert.Equal("SEQ", ex.ColumnName); - Assert.Equal(4, ex.Row); - Assert.Equal("Error", ex.Value); - Assert.Equal(typeof(int), ex.ColumnType); - Assert.Equal("The value Error cannot be assigned to type Int32.", ex.Message); - } - } + Assert.Equal("Wade", rows[5].B); + Assert.Equal("HR", rows[5].C); + Assert.Equal("Felix", rows[6].B); + Assert.Equal("HR", rows[6].C); - private class TestIssue209Dto - { - public int ID { get; set; } - public string Name { get; set; } - public int SEQ { get; set; } + 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 dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:C9", dimension); + } } - /// - /// [SaveAs and Query support btye[] base64 converter · Issue #318 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/318) - /// [Fact] - public void TestIssue318() + public void Issue206() { - var imageByte = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")); - using var path = AutoDeletingPath.Create(); - var value = new[] { - new { Name="github", Image=imageByte}, - }; - _excelExporter.Export(path.ToString(), value); + var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); + using var path = AutoDeletingPath.Create(); + var dt = new DataTable(); + { + dt.Columns.Add("name"); + dt.Columns.Add("department"); + } + var value = new Dictionary + { + ["employees"] = dt + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - // import to byte[] - { - const string 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 rows = _excelImporter.Query(path.ToString(), true).ToList(); - var actulBase64 = Convert.ToBase64String((byte[])rows[0].Image); - Assert.Equal(expectedBase64, actulBase64); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:B2", dimension); } - // import to base64 string { - var config = new OpenXmlConfiguration { EnableConvertByteArray = false }; - var rows = _excelImporter.Query(path.ToString(), true, configuration: config).ToList(); - var image = (string)rows[0].Image; - Assert.StartsWith("@@@fileid@@@,xl/media/", image); - } + var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); + using var path = AutoDeletingPath.Create(); - } + using var dt = new DataTable(); + dt.Columns.Add("name"); + dt.Columns.Add("department"); + dt.Rows.Add("Jack", "HR"); + var value = new Dictionary { ["employees"] = dt }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - /// - /// SaveAs support Image type · Issue #304 https://github.com/mini-software/MiniExcel/issues/304 - /// + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); + Assert.Equal("A1:B2", dimension); + } + } + + // Template merge row list rendering has no merge [Fact] - public void TestIssue304() + public void Issue207() { - var path = PathHelper.GetTempFilePath(); - var value = new[] { - new { Name="github", Image=File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png"))}, - new { Name="google", Image=File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png"))}, - new { Name="microsoft", Image=File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png"))}, - new { Name="reddit", Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))}, - new { Name="stackoverflow", Image=File.ReadAllBytes(PathHelper.GetFile("images/stackoverflow_logo.png"))}, - }; - _excelExporter.Export(path, value); + var tempaltePath = PathHelper.GetFile("xlsx/TestIssue207_2.xlsx"); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing1.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); - Assert.Contains("../drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); - } + var value = new + { + project = new[] { + new {name = "項目1",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目2",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目3",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目4",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + } + }; - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I4HL54 - /// - [Fact] - public void TestIssueI4HL54() - { - using var cn = Db.GetConnection(); + _excelTemplater.FillTemplate(path, tempaltePath, value); + var rows = _excelImporter.Query(path).ToList(); - using var reader = cn.ExecuteReader(@"select 'Hello World1' Text union all select 'Hello World2'"); - var templatePath = PathHelper.GetFile("xlsx/TestIssueI4HL54_Template.xlsx"); - using var path = AutoDeletingPath.Create(); - var value = new Dictionary - { - { "Texts",reader} - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + Assert.Equal("項目1", rows[0].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].B); + Assert.Equal("項目2", rows[2].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[2].B); + Assert.Equal("項目3", rows[4].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[4].B); + Assert.Equal("項目4", rows[6].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[6].B); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); - Assert.Equal("Hello World1", rows[0].Text); - Assert.Equal("Hello World2", rows[1].Text); - } + Assert.Equal("Test1", rows[8].A); + Assert.Equal("Test2", rows[8].B); + Assert.Equal("Test3", rows[8].C); - /// - /// [Prefix and suffix blank space will lost after SaveAs · Issue #294 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/294) - /// - [Fact] - public void TestIssue294() - { - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Name = " Jack" } }; - _excelExporter.Export(path.ToString(), value); - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("xml:space=\"preserve\"", sheetXml); - } - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Name = "Ja ck" } }; - _excelExporter.Export(path.ToString(), value); - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.DoesNotContain("xml:space=\"preserve\"", sheetXml); + Assert.Equal("項目1", rows[12].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[12].B); + Assert.Equal("項目2", rows[13].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[13].B); + Assert.Equal("項目3", rows[14].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[14].B); + Assert.Equal("項目4", rows[15].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[15].B); + + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:C16", dimension); } + { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Name = "Jack " } }; - _excelExporter.Export(path.ToString(), value); - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("xml:space=\"preserve\"", sheetXml); + var tempaltePath = PathHelper.GetFile("xlsx/TestIssue207_Template_Merge_row_list_rendering_without_merge/template.xlsx"); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + + var value = new + { + project = new[] { + new {name = "項目1",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目2",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目3",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + new {name = "項目4",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, + } + }; + + _excelTemplater.FillTemplate(path, tempaltePath, value); + var rows = _excelImporter.Query(path).ToList(); + + Assert.Equal("項目1", rows[0].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].C); + Assert.Equal("項目2", rows[3].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[3].C); + Assert.Equal("項目3", rows[6].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[6].C); + Assert.Equal("項目4", rows[9].A); + Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[9].C); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); + Assert.Equal("A1:E15", dimension); } } - - /// - /// SaveAsByTemplate if there is & in the cell value, it will be & - /// https://gitee.com/dotnetchina/MiniExcel/issues/I4DQUN - /// [Fact] - public void TestIssueI4DQUN() + public void Issue208() { - var templatePath = PathHelper.GetFile("xlsx/TestIssueI4DQUN.xlsx"); - using var path = AutoDeletingPath.Create(); - var value = new Dictionary - { - { "Title", "Hello & World < , > , \" , '" }, - { "Details", new[] { new { Value = "Hello & Value < , > , \" , '" } } }, - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - // Template now uses inlineStr format (...) instead of SharedStrings (...) - Assert.Contains("Hello & World < , > , \" , '", sheetXml); - Assert.Contains("Hello & Value < , > , \" , '", sheetXml); + var path = PathHelper.GetFile("xlsx/TestIssue208.xlsx"); + var columns = _excelImporter.GetColumnNames(path).ToList(); + Assert.Equal(16384, columns.Count); + Assert.Equal("XFD", columns[16383]); } - /// - /// [SaveAs default theme support filter mode · Issue #190 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/190) - /// + // Query type conversion error - https://github.com/mini-software/MiniExcel/issues/309 [Fact] - public void TestIssue190() + public void TestIssue209() { + try { - using var path = AutoDeletingPath.Create(); - var value = new TestIssue190Dto[] { }; - _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { AutoFilter = false }); - - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.DoesNotContain("", sheetXml); - } - { - using var path = AutoDeletingPath.Create(); - var value = new TestIssue190Dto[] { }; - _excelExporter.Export(path.ToString(), value); - - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("", sheetXml); + var path = PathHelper.GetFile("xlsx/TestIssue309.xlsx"); + _ = _excelImporter.Query(path).ToList(); } + catch (ValueNotAssignableException ex) { - using var path = AutoDeletingPath.Create(); - TestIssue190Dto[] value = - [ - new() { ID = 1, Name = "Jack", Age = 32 }, - new() { ID = 2, Name = "Lisa", Age = 45 } - ]; - _excelExporter.Export(path.ToString(), value); - - var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.Contains("", sheetXml); + Assert.Equal("SEQ", ex.ColumnName); + Assert.Equal(4, ex.Row); + Assert.Equal("Error", ex.Value); + Assert.Equal(typeof(int), ex.ColumnType); + Assert.Equal("The value Error cannot be assigned to type Int32.", ex.Message); } } - private class TestIssue190Dto - { - public int ID { get; set; } - public string Name { get; set; } - public int Age { get; set; } - } - - + // SaveAs support for IDataReader based export [Fact] - public void TestIssueI49RYZ() + public void Issue211() { - I49RYZDto[] values = - [ - new() { Name="Jack", UserType=I49RYZUserType.V1 }, - new() { Name="Leo", UserType=I49RYZUserType.V2 }, - new() { Name="Henry", UserType=I49RYZUserType.V3 }, - new() { Name="Lisa", UserType=null } - ]; using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), values); + var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + var connectionString = $"Data Source={tempSqlitePath};Version=3;"; + + using var connection = new SQLiteConnection(connectionString); + var reader = connection.ExecuteReader(@"select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); + _excelExporter.Export(path.ToString(), reader); var rows = _excelImporter.Query(path.ToString(), true).ToList(); - Assert.Equal("GeneralUser", rows[0].UserType); - Assert.Equal("SuperAdministrator", rows[1].UserType); - Assert.Equal("GeneralAdministrator", rows[2].UserType); - Assert.Null(rows[3].UserType); + + Assert.Equal(1.0, rows[0].Test1); + Assert.Equal(2.0, rows[0].Test2); + Assert.Equal(3.0, rows[1].Test1); + Assert.Equal(4.0, rows[1].Test2); } + // _exporter.Export(path, table,sheetName:“Name”) sheetName is incorrectly Sheet1 [Fact] - public void TestIssue286() + public void Issue212() { - TestIssue286Dto[] values = - [ - new() { E = TestIssue286Enum.VIP1 }, - new() { E = TestIssue286Enum.VIP2 } - ]; using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), values); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); + _excelExporter.Export(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: "Demo"); - Assert.Equal("VIP1", rows[0].E); - Assert.Equal("VIP2", rows[1].E); + var actualSheetName = _excelImporter.GetSheetNames(path.ToString()).ToList()[0]; + Assert.Equal("Demo", actualSheetName); } - private class TestIssue286Dto + // Support reading Excel by IDataReader and returning DataTable + [Fact] + public void Issue216() { - public TestIssue286Enum E { get; set; } - } + using var path = AutoDeletingPath.Create(); + var value = new[] + { + new { Test1 = "1", Test2 = 2 }, + new { Test1 = "3", Test2 = 4 } + }; + _excelExporter.Export(path.ToString(), value); - private enum TestIssue286Enum { VIP1, VIP2 } + using var table = _excelImporter.QueryAsDataTable(path.ToString()); + Assert.Equal("Test1", table.Columns[0].ColumnName); + Assert.Equal("Test2", table.Columns[1].ColumnName); + Assert.Equal("1", table.Rows[0]["Test1"]); + Assert.Equal(2.0, table.Rows[0]["Test2"]); + Assert.Equal("3", table.Rows[1]["Test1"]); + Assert.Equal(4.0, table.Rows[1]["Test2"]); - private enum I49RYZUserType - { - [Description("GeneralUser")] V1 = 0, - [Description("SuperAdministrator")] V2 = 1, - [Description("GeneralAdministrator")] V3 = 2 + + using var dt = _excelImporter.QueryAsDataTable(path.ToString(), false); + Assert.Equal("Test1", dt.Rows[0]["A"]); + Assert.Equal("Test2", dt.Rows[0]["B"]); + Assert.Equal("1", dt.Rows[1]["A"]); + Assert.Equal(2.0, dt.Rows[1]["B"]); + Assert.Equal("3", dt.Rows[2]["A"]); + Assert.Equal(4.0, dt.Rows[2]["B"]); } - private class I49RYZDto + // DataTable recommended to use Caption for column name first, then use columname + [Fact] + public void Issue217() { - public string Name { get; set; } - public I49RYZUserType? UserType { get; set; } - } + using var table = new DataTable(); + table.Columns.Add("CustomerID"); + table.Columns.Add("CustomerName").Caption = "Name"; + table.Columns.Add("CreditLimit").Caption = "Limit"; + table.Rows.Add(1, "Jonathan", 23.44); + table.Rows.Add(2, "Bill", 56.87); + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), table); - /// - /// Create Multiple Sheets from IDataReader have Bug #283 - /// + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); + } + + // Dynamic Query can't summary numeric cell value default, need to cast [Fact] - public void TestIssue283() + public void Issue220() { - using var path = AutoDeletingPath.Create(); - using (var cn = Db.GetConnection()) - { - var sheets = new Dictionary + var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); + var rows = _excelImporter.Query(path, hasHeaderRow: true); + var result = rows + .GroupBy(s => s.PRT_ID) + .Select(g => new { - { "sheet01", cn.ExecuteReader("select 'v1' col1") }, - { "sheet02", cn.ExecuteReader("select 'v2' col1") } - }; - var rows = _excelExporter.Export(path.ToString(), sheets); - Assert.Equal(2, rows.Length); - } + PRT_ID = g.Key, + Apr = g.Sum(x => (double?)x.Apr), + May = g.Sum(x => (double?)x.May), + Jun = g.Sum(x => (double?)x.Jun) + }) + .ToList(); - var sheetNames = _excelImporter.GetSheetNames(path.ToString()); - Assert.Equal(["sheet01", "sheet02"], sheetNames); + Assert.Equal(91843.25, result[0].Jun); + Assert.Equal(50000.99, result[1].Jun); } - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I40QA5 - /// + // Custom yyyy-MM-dd format not converted to datetime [Fact] - public void TestIssueI40QA5() + public void Issue222() { - { - var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_1.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal("E001", rows[0].Empno); - Assert.Equal("E002", rows[1].Empno); - } - { - var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_2.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal("E001", rows[0].Empno); - Assert.Equal("E002", rows[1].Empno); - } - { - var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_3.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal("E001", rows[0].Empno); - Assert.Equal("E002", rows[1].Empno); - } - { - var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_4.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - Assert.Null(rows[0].Empno); - Assert.Null(rows[1].Empno); - } + var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + Assert.Equal(typeof(DateTime), rows[1].A.GetType()); + Assert.Equal(new DateTime(2021, 4, 29), rows[1].A); } - private class TestIssueI40QA5Dto + [Fact] + public void Issue223() { - [MiniExcelColumnName(columnName: "EmployeeNo", aliases: new[] { "EmpNo", "No" })] - public string Empno { get; set; } - public string Name { get; set; } + List> value = + [ + new() { { "A", null }, { "B", null } }, + new() { { "A", 123 }, { "B", new DateTime(2021, 1, 1) } }, + new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } + ]; + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), value); + + using var dt = _excelImporter.QueryAsDataTable(path.ToString()); + + var columns = dt.Columns; + Assert.Equal(typeof(object), columns[0].DataType); + Assert.Equal(typeof(object), columns[1].DataType); + + Assert.Equal(123.0, dt.Rows[1]["A"]); + Assert.Equal("HelloWorld", dt.Rows[2]["B"]); } + // Fix SaveAsByTemplate single column dimension index error [Fact] - public void TestIssues133() + public void Issue226() { - { - using var path = AutoDeletingPath.Create(); + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); + _excelTemplater.FillTemplate(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); + } - var value = new DataTable(); - value.Columns.Add("Id"); - value.Columns.Add("Name"); - _excelExporter.Export(path.ToString(), value); - var rows = _excelImporter.Query(path.ToString()).ToList(); + // Support Xlsm AutoCheck + [Fact] + public void Issue227() + { + var path = PathHelper.GetTempPath("xlsm"); + Assert.Throws(() => _excelExporter.Export(path, new[] { new { V = "A1" }, new { V = "A2" } })); + File.Delete(path); - Assert.Equal("Id", rows[0].A); - Assert.Equal("Name", rows[0].B); - Assert.Single(rows); - Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); - } + var path1 = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); + var rows1 = _excelImporter.Query(path1).ToList(); + Assert.Equal(100, rows1.Count); - { - using var path = AutoDeletingPath.Create(); + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows1[0].ID); + Assert.Equal("Wade", rows1[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows1[0].BoD); + Assert.Equal(36, rows1[0].Age); + Assert.False(rows1[0].VIP); + Assert.Equal(5019.12m, rows1[0].Points); + Assert.Equal(1, rows1[0].IgnoredProperty); - var value = Array.Empty(); - _excelExporter.Export(path.ToString(), value); - var rows = _excelImporter.Query(path.ToString()).ToList(); + using var stream = File.OpenRead(path1); + var rows2 = _excelImporter.Query(stream).ToList(); + Assert.Equal(100, rows2.Count); - Assert.Equal("Id", rows[0].A); - Assert.Equal("Name", rows[0].B); - Assert.Single(rows); - Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); - } + Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows2[0].ID); + Assert.Equal("Wade", rows2[0].Name); + Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows2[0].BoD); + Assert.Equal(36, rows2[0].Age); + Assert.False(rows2[0].VIP); + Assert.Equal(5019.12m, rows2[0].Points); + Assert.Equal(1, rows2[0].IgnoredProperty); } - private class TestIssues133Dto + // QueryAsDataTable error "Cannot set Column to be null" + [Fact] + public void Issue229() { - public string Id { get; set; } - public string Name { get; set; } + var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); + + using var dt = _excelImporter.QueryAsDataTable(path); + + foreach (DataColumn column in dt.Columns) + { + var v = dt.Rows[3][column]; + Assert.Equal(DBNull.Value, v); + } } - /// - /// Semicolon expected - /// + // SaveAs By data reader error : 'Invalid attempt to call FieldCount when reader is closed' [Fact] - public void TestIssueI45TF5_2() + public void Issue230() { + using var conn = Db.GetConnection("Data Source=:memory:"); + conn.Open(); + using var cmd = conn.CreateCommand(); + cmd.CommandText = "select 1 id union all select 2"; + + using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { - var value = new[] { new Dictionary { { "Col1&Col2", "V1&V2" } } }; - var path = PathHelper.GetTempPath(); - _excelExporter.Export(path, value); - //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. - SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; + _output.WriteLine(result); + } + } } + using var conn2 = Db.GetConnection("Data Source=:memory:"); + conn2.Open(); + using var cmd2 = conn2.CreateCommand(); + cmd2.CommandText = "select 1 id union all select 2"; + using (var reader = cmd2.ExecuteReader(CommandBehavior.CloseConnection)) { - using var dt = new DataTable(); - dt.Columns.Add("Col1&Col2"); - dt.Rows.Add("V1&V2"); - var path = PathHelper.GetTempPath(); - _excelExporter.Export(path, dt); - //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. - SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + var result = $"{reader.GetName(i)}, {reader.GetValue(i)}"; + _output.WriteLine(result); + } + } } - } - [Fact] - public void TestIssueI45TF5() - { - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), new[] { new { C1 = "1&2;3,4", C2 = "1&2;3,4" } }); - var sheet1Xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); - Assert.DoesNotContain("", sheet1Xml); + using var conn3 = Db.GetConnection("Data Source=:memory:"); + conn3.Open(); + using var cmd3 = conn3.CreateCommand(); + cmd3.CommandText = "select 1 id union all select 2"; + using (var reader = cmd3.ExecuteReader(CommandBehavior.CloseConnection)) + { + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), reader, printHeader: true); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); + Assert.Equal(1, rows[0].id); + Assert.Equal(2, rows[1].id); + } } - /// - /// [Support column width attribute · Issue #280 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/280) - /// + // QueryAsDataTable A2=5.5 , A3=0.55/1.1 will case double type check error [Fact] - public void TestIssue280() + public void Issue233() { - TestIssue280Dto[] value = - [ - new() { ID = 1, Name = "Jack" }, - new() { ID = 2, Name = "Mike" } - ]; - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), value); - } + var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); - private class TestIssue280Dto - { - [MiniExcelColumnWidth(20)] - public int ID { get; set; } - [MiniExcelColumnWidth(15.50)] - public string Name { get; set; } - } + var dt = _excelImporter.QueryAsDataTable(path); - /// - /// Custom excel zip can't read and show Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. #272 - /// - [Fact] - public void TestIssue272() - { - var path = PathHelper.GetFile("/xlsx/TestIssue272.xlsx"); - Assert.Throws(() => _excelImporter.Query(path).ToList()); - } + var rows = dt.Rows; - /// - /// v0.16.0-0.17.1 custom format contains specific format (eg:`#,##0.000_);[Red]\(#,##0.000\)`), automatic converter will convert double to datetime #267 - /// - [Fact] - public void TestIssue267() - { - var path = PathHelper.GetFile("/xlsx/TestIssue267.xlsx"); - var row = _excelImporter.Query(path).SingleOrDefault(); - Assert.Equal(10618, row!.A); - Assert.Equal("2021-02-23", row.B); - Assert.Equal(43.199999999999996, row.C); - Assert.Equal(1.2, row.D); - Assert.Equal(new DateTime(2021, 7, 5), row.E); - Assert.Equal(new DateTime(2021, 7, 5, 15, 2, 46), row.F); + Assert.Equal(0.55, rows[0]["Size"]); + Assert.Equal("0.55/1.1", rows[1]["Size"]); } - + /// SaveAs support for multiple sheets [Fact] - public void TestIssue268_DateFormat() + public void Issue234() { - Assert.True(IsDateFormatString("dd/mm/yyyy")); - Assert.True(IsDateFormatString("dd-mmm-yy")); - Assert.True(IsDateFormatString("dd-mmmm")); - Assert.True(IsDateFormatString("mmm-yy")); - Assert.True(IsDateFormatString("h:mm AM/PM")); - Assert.True(IsDateFormatString("h:mm:ss AM/PM")); - Assert.True(IsDateFormatString("hh:mm")); - Assert.True(IsDateFormatString("hh:mm:ss")); - Assert.True(IsDateFormatString("dd/mm/yyyy hh:mm")); - Assert.True(IsDateFormatString("mm:ss")); - Assert.True(IsDateFormatString("mm:ss.0")); - Assert.True(IsDateFormatString("[$-809]dd mmmm yyyy")); - Assert.False(IsDateFormatString("#,##0;[Red]-#,##0")); - Assert.False(IsDateFormatString(@"#,##0.000_);[Red]\(#,##0.000\)")); - Assert.False(IsDateFormatString("0_);[Red](0)")); - Assert.False(IsDateFormatString(@"0\h")); - Assert.False(IsDateFormatString("0\"h\"")); - Assert.False(IsDateFormatString("0%")); - Assert.False(IsDateFormatString("General")); - Assert.False(IsDateFormatString(@"_-* #,##0\ _P_t_s_-;\-* #,##0\ _P_t_s_-;_-* "" - ""??\ _P_t_s_-;_-@_- ")); - } - - private static bool IsDateFormatString(string formatCode) => DateTimeHelper.IsDateTimeFormat(formatCode); + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - [Fact] - public void TestIssueI3X2ZL() - { - try + var users = new[] { - var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_datetime_error.xlsx"); - var rows = _excelImporter.Query(path, startCell: "B3").ToList(); - } - catch (InvalidCastException ex) + new { Name = "Jack", Age = 25 }, + new { Name = "Mike", Age = 44 } + }; + var department = new[] { - Assert.Equal( - "The value error cannot be assigned to type DateTime.", - ex.Message - ); - } + new { ID = "01", Name = "HR" }, + new { ID = "02", Name = "IT" } + }; + var sheets = new Dictionary + { + ["users"] = users, + ["department"] = department + }; + _excelExporter.Export(path, sheets); + + var sheetNames = _excelImporter.GetSheetNames(path); + Assert.Equal("users", sheetNames[0]); + Assert.Equal("department", sheetNames[1]); - try { - var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_int_error.xlsx"); - var rows = _excelImporter.Query(path).ToList(); + var rows = _excelImporter.Query(path, true, sheetName: "users").ToList(); + Assert.Equal("Jack", rows[0].Name); + Assert.Equal(25, rows[0].Age); + Assert.Equal("Mike", rows[1].Name); + Assert.Equal(44, rows[1].Age); } - catch (InvalidCastException ex) { - Assert.Equal( - "The value error cannot be assigned to type Int32.", - ex.Message - ); + var rows = _excelImporter.Query(path, true, sheetName: "department").ToList(); + Assert.Equal("01", rows[0].ID); + Assert.Equal("HR", rows[0].Name); + Assert.Equal("02", rows[1].ID); + Assert.Equal("IT", rows[1].Name); } } - private class IssueI3X2ZLDTO - { - public int Col1 { get; set; } - public DateTime Col2 { get; set; } - } - - /// - /// [SaveAsByTemplate support DateTime custom format · Issue #255 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/255) - /// + // Support SaveAs by DataSet + [Fact] + public void Issue235() + { + using var path = AutoDeletingPath.Create(); + + var users = new DataTable { TableName = "users" }; + users.Columns.Add("Name", typeof(string)); + users.Columns.Add("Age", typeof(int)); + users.Rows.Add("Jack", 25); + users.Rows.Add("Mike", 44); + + var departments = new DataTable { TableName = "departments" }; + departments.Columns.Add("ID"); + departments.Columns.Add("Name"); + departments.Rows.Add("01", "HR"); + departments.Rows.Add("02", "IT"); + + DataSet dataSet = new(); + dataSet.Tables.Add(users); + dataSet.Tables.Add(departments); + + var rowsWritten = _excelExporter.Export(path.ToString(), dataSet); + Assert.Equal(2, rowsWritten.Length); + Assert.Equal(2, rowsWritten[0]); + + var sheetNames = _excelImporter.GetSheetNames(path.ToString()); + Assert.Equal("users", sheetNames[0]); + Assert.Equal("departments", sheetNames[1]); + + var rows1 = _excelImporter.Query(path.ToString(), true, sheetName: "users").ToList(); + Assert.Equal("Jack", rows1[0].Name); + Assert.Equal(25, rows1[0].Age); + Assert.Equal("Mike", rows1[1].Name); + Assert.Equal(44, rows1[1].Age); + + var rows2 = _excelImporter.Query(path.ToString(), true, sheetName: "departments").ToList(); + Assert.Equal("01", rows2[0].ID); + Assert.Equal("HR", rows2[0].Name); + Assert.Equal("02", rows2[1].ID); + Assert.Equal("IT", rows2[1].Name); + } + + [Fact] + public void TestIssue240() + { + using var path = AutoDeletingPath.Create(); + var templatePath = PathHelper.GetFile("xlsx/TestIssue24020201.xlsx"); + var data = new Dictionary + { + ["title"] = "This's title", + ["B"] = new List> + { + new() { { "specialMark", 1 } }, + new() { { "specialMark", 2 } }, + new() { { "specialMark", 3 } }, + } + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, data); + } + + // Support Custom Datetime format + [Fact] + public void Issue241() + { + var date1 = new DateTime(2021, 01, 04); + var date2 = new DateTime(2020, 04, 05); + + Issue241Dto[] value = + [ + new() { Name = "Jack", InDate = date1 }, + new() { Name = "Henry", InDate = date2 } + ]; + + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + var rowsWritten = _excelExporter.Export(path, value); + + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + using var package = new ExcelPackage(path); + var cells = package.Workbook.Worksheets.First().Cells; + + Assert.Equal(date1, DateTime.FromOADate((double)cells["B2"].Value)); + Assert.Equal("01 04, 2021", cells["B2"].Text); + Assert.Equal(date2, DateTime.FromOADate((double)cells["B3"].Value)); + Assert.Equal("04 05, 2020", cells["B3"].Text); + } + + // No error exception throw when reading xls file #242 + [Fact] + public void Issue242() + { + var path = PathHelper.GetFile("xls/TestIssue242.xls"); + Assert.Throws(() => _excelImporter.Query(path).ToList()); + + using var stream = File.OpenRead(path); + Assert.Throws(() => _excelImporter.Query(stream).ToList()); + } + + // SaveAsByTemplate support DateTime custom format [Fact] public void Issue255() { @@ -1455,19 +1286,7 @@ public void Issue255() } } - private class Issue255DTO - { - [MiniExcelFormat("yyyy")] - public DateTime Time { get; set; } - - [MiniExcelColumn(Format = "yyyy")] - public DateTime Time2 { get; set; } - } - - /// - /// [Dynamic Query custom format not using mapping format · Issue #256] - /// (https://github.com/mini-software/MiniExcel/issues/256) - /// + // Dynamic Query custom format not using mapping format [Fact] public void Issue256() { @@ -1477,1282 +1296,693 @@ public void Issue256() Assert.Equal(new DateTime(2004, 4, 16), rows[1].B); } - /// - /// No error exception throw when reading xls file #242 - /// + // custom format contains specific format (eg:`#,##0.000_);[Red]\(#,##0.000\)`), automatic converter will convert double to datetime [Fact] - public void Issue242() + public void TestIssue267() { - var path = PathHelper.GetFile("xls/TestIssue242.xls"); - Assert.Throws(() => _excelImporter.Query(path).ToList()); + var path = PathHelper.GetFile("/xlsx/TestIssue267.xlsx"); + var row = _excelImporter.Query(path).SingleOrDefault(); + Assert.Equal(10618, row!.A); + Assert.Equal("2021-02-23", row.B); + Assert.Equal(43.199999999999996, row.C); + Assert.Equal(1.2, row.D); + Assert.Equal(new DateTime(2021, 7, 5), row.E); + Assert.Equal(new DateTime(2021, 7, 5, 15, 2, 46), row.F); + } - using var stream = File.OpenRead(path); - Assert.Throws(() => _excelImporter.Query(stream).ToList()); + [Fact] + public void TestIssue268_DateFormat() + { + Assert.True(IsDateFormatString("dd/mm/yyyy")); + Assert.True(IsDateFormatString("dd-mmm-yy")); + Assert.True(IsDateFormatString("dd-mmmm")); + Assert.True(IsDateFormatString("mmm-yy")); + Assert.True(IsDateFormatString("h:mm AM/PM")); + Assert.True(IsDateFormatString("h:mm:ss AM/PM")); + Assert.True(IsDateFormatString("hh:mm")); + Assert.True(IsDateFormatString("hh:mm:ss")); + Assert.True(IsDateFormatString("dd/mm/yyyy hh:mm")); + Assert.True(IsDateFormatString("mm:ss")); + Assert.True(IsDateFormatString("mm:ss.0")); + Assert.True(IsDateFormatString("[$-809]dd mmmm yyyy")); + Assert.False(IsDateFormatString("#,##0;[Red]-#,##0")); + Assert.False(IsDateFormatString(@"#,##0.000_);[Red]\(#,##0.000\)")); + Assert.False(IsDateFormatString("0_);[Red](0)")); + Assert.False(IsDateFormatString(@"0\h")); + Assert.False(IsDateFormatString("0\"h\"")); + Assert.False(IsDateFormatString("0%")); + Assert.False(IsDateFormatString("General")); + Assert.False(IsDateFormatString(@"_-* #,##0\ _P_t_s_-;\-* #,##0\ _P_t_s_-;_-* "" - ""??\ _P_t_s_-;_-@_- ")); } /// - /// Support Custom Datetime format #241 + /// Custom excel zip can't read and show Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory. #272 /// [Fact] - public void Issue241() + public void TestIssue272() { - var date1 = new DateTime(2021, 01, 04); - var date2 = new DateTime(2020, 04, 05); + var path = PathHelper.GetFile("/xlsx/TestIssue272.xlsx"); + Assert.Throws(() => _excelImporter.Query(path).ToList()); + } - Issue241Dto[] value = + // Support column width attribute - https://github.com/mini-software/MiniExcel/issues/280 + [Fact] + public void TestIssue280() + { + TestIssue280Dto[] value = [ - new() { Name = "Jack", InDate = date1 }, - new() { Name = "Henry", InDate = date2 } + new() { ID = 1, Name = "Jack" }, + new() { ID = 2, Name = "Mike" } ]; + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), value); + } - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - var rowsWritten = _excelExporter.Export(path, value); - - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); + // Create Multiple Sheets from IDataReader is bugged + [Fact] + public void TestIssue283() + { + using var path = AutoDeletingPath.Create(); + using (var cn = Db.GetConnection()) + { + var sheets = new Dictionary + { + { "sheet01", cn.ExecuteReader("select 'v1' col1") }, + { "sheet02", cn.ExecuteReader("select 'v2' col1") } + }; + var rows = _excelExporter.Export(path.ToString(), sheets); + Assert.Equal(2, rows.Length); + } - using var package = new ExcelPackage(path); - var cells = package.Workbook.Worksheets.First().Cells; + var sheetNames = _excelImporter.GetSheetNames(path.ToString()); + Assert.Equal(["sheet01", "sheet02"], sheetNames); + } - Assert.Equal(date1, DateTime.FromOADate((double)cells["B2"].Value)); - Assert.Equal("01 04, 2021", cells["B2"].Text); - Assert.Equal(date2, DateTime.FromOADate((double)cells["B3"].Value)); - Assert.Equal("04 05, 2020", cells["B3"].Text); + [Fact] + public void TestIssue286() + { + TestIssue286Dto[] values = + [ + new() { E = TestIssue286Enum.VIP1 }, + new() { E = TestIssue286Enum.VIP2 } + ]; + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), values); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); + + Assert.Equal("VIP1", rows[0].E); + Assert.Equal("VIP2", rows[1].E); } - private class Issue241Dto + [Fact] + public void TestIssue289() { - public string Name { get; set; } + using var path = AutoDeletingPath.Create(); + DescriptionEnumDto[] value = + [ + new() { Name = "0001", UserType = DescriptionEnum.V1 }, + new() { Name = "0002", UserType = DescriptionEnum.V2 }, + new() { Name = "0003", UserType = DescriptionEnum.V3 } + ]; + _excelExporter.Export(path.ToString(), value); + + var rows = _excelImporter.Query(path.ToString()).ToList(); - [MiniExcelFormat("MM dd, yyyy")] - public DateTime InDate { get; set; } + Assert.Equal(DescriptionEnum.V1, rows[0].UserType); + Assert.Equal(DescriptionEnum.V2, rows[1].UserType); + Assert.Equal(DescriptionEnum.V3, rows[2].UserType); } - /// - /// SaveAs Default Template #132 - /// + /// Prefix and suffix blank space are lost after SaveAs - https://github.com/mini-software/MiniExcel/issues/294 [Fact] - public void Issue132() + public void TestIssue294() { { using var path = AutoDeletingPath.Create(); - var value = new[] { - new { name = "Jack", Age = 25, InDate = new DateTime(2021,01,03)}, - new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, - }; - + var value = new[] { new { Name = " Jack" } }; _excelExporter.Export(path.ToString(), value); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.Contains("xml:space=\"preserve\"", sheetXml); } - { using var path = AutoDeletingPath.Create(); - var value = new[] - { - new { name = "Jack", Age = 25, InDate = new DateTime(2021,01,03)}, - new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, - }; - var config = new OpenXmlConfiguration - { - TableStyles = TableStyles.None - }; - _excelExporter.Export(path.ToString(), value, configuration: config); + var value = new[] { new { Name = "Ja ck" } }; + _excelExporter.Export(path.ToString(), value); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.DoesNotContain("xml:space=\"preserve\"", sheetXml); } - { using var path = AutoDeletingPath.Create(); + var value = new[] { new { Name = "Jack " } }; + _excelExporter.Export(path.ToString(), value); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + Assert.Contains("xml:space=\"preserve\"", sheetXml); + } + } - var dt = new DataTable(); - dt.Columns.Add("Name"); - dt.Columns.Add("Age"); - dt.Columns.Add("Date"); - - dt.Rows.Add("Jack", 25, new DateTime(2021, 01, 03)); - dt.Rows.Add("Henry", 36, new DateTime(2021, 01, 03)); - - _excelExporter.Export(path.ToString(), dt); - } - } - - /// - /// Support SaveAs by DataSet #235 - /// + // SaveAs support Image type - https://github.com/mini-software/MiniExcel/issues/304 [Fact] - public void Issue235() + public void TestIssue304() { - using var path = AutoDeletingPath.Create(); - - var users = new DataTable { TableName = "users" }; - users.Columns.Add("Name", typeof(string)); - users.Columns.Add("Age", typeof(int)); - users.Rows.Add("Jack", 25); - users.Rows.Add("Mike", 44); - - var departments = new DataTable { TableName = "departments" }; - departments.Columns.Add("ID"); - departments.Columns.Add("Name"); - departments.Rows.Add("01", "HR"); - departments.Rows.Add("02", "IT"); - - DataSet dataSet = new(); - dataSet.Tables.Add(users); - dataSet.Tables.Add(departments); - - var rowsWritten = _excelExporter.Export(path.ToString(), dataSet); - Assert.Equal(2, rowsWritten.Length); - Assert.Equal(2, rowsWritten[0]); - - var sheetNames = _excelImporter.GetSheetNames(path.ToString()); - Assert.Equal("users", sheetNames[0]); - Assert.Equal("departments", sheetNames[1]); - - var rows1 = _excelImporter.Query(path.ToString(), true, sheetName: "users").ToList(); - Assert.Equal("Jack", rows1[0].Name); - Assert.Equal(25, rows1[0].Age); - Assert.Equal("Mike", rows1[1].Name); - Assert.Equal(44, rows1[1].Age); + var path = PathHelper.GetTempFilePath(); + var value = new[] + { + new { Name="github", Image=File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png"))}, + new { Name="google", Image=File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png"))}, + new { Name="microsoft", Image=File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png"))}, + new { Name="reddit", Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))}, + new { Name="stackoverflow", Image=File.ReadAllBytes(PathHelper.GetFile("images/stackoverflow_logo.png"))}, + }; + _excelExporter.Export(path, value); - var rows2 = _excelImporter.Query(path.ToString(), true, sheetName: "departments").ToList(); - Assert.Equal("01", rows2[0].ID); - Assert.Equal("HR", rows2[0].Name); - Assert.Equal("02", rows2[1].ID); - Assert.Equal("IT", rows2[1].Name); + Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); + Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing1.xml")); + Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); + Assert.Contains("drawing r:id=", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); + Assert.Contains("../drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); } - /// - /// QueryAsDataTable A2=5.5 , A3=0.55/1.1 will case double type check error #233 - /// + // https://github.com/mini-software/MiniExcel/issues/305 [Fact] - public void Issue233() + public async Task TestIssue305() { - var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); - - var dt = _excelImporter.QueryAsDataTable(path); + var dt = new DateTime(2022, 01, 22); - var rows = dt.Rows; + using var path = AutoDeletingPath.Create(); + TestIssueI49RZHDto[] value = + [ + new() { dd = dt }, + new() { dd = null } + ]; + await _excelExporter.ExportAsync(path.FilePath, value, overwriteFile: true); - Assert.Equal(0.55, rows[0]["Size"]); - Assert.Equal("0.55/1.1", rows[1]["Size"]); + using var package = new ExcelPackage(path.ToString()); + var cells = package.Workbook.Worksheets[0].Cells; + + Assert.Equal(dt, DateTime.FromOADate((double)cells["A2"].Value)); + Assert.Equal("22-01-2022", cells["A2"].Text); } - /// - /// SaveAs support multiple sheets #234 - /// [Fact] - public void Issue234() + public async Task TestIssue307() { using var file = AutoDeletingPath.Create(); var path = file.ToString(); + var value = new[] { new { id = 1, name = "Jack" } }; - var users = new[] - { - new { Name = "Jack", Age = 25 }, - new { Name = "Mike", Age = 44 } - }; - var department = new[] - { - new { ID = "01", Name = "HR" }, - new { ID = "02", Name = "IT" } - }; - var sheets = new Dictionary - { - ["users"] = users, - ["department"] = department - }; - _excelExporter.Export(path, sheets); - - var sheetNames = _excelImporter.GetSheetNames(path); - Assert.Equal("users", sheetNames[0]); - Assert.Equal("department", sheetNames[1]); + await _excelExporter.ExportAsync(path, value); + Assert.Throws(() => _excelExporter.Export(path, value)); - { - var rows = _excelImporter.Query(path, true, sheetName: "users").ToList(); - Assert.Equal("Jack", rows[0].Name); - Assert.Equal(25, rows[0].Age); - Assert.Equal("Mike", rows[1].Name); - Assert.Equal(44, rows[1].Age); - } - { - var rows = _excelImporter.Query(path, true, sheetName: "department").ToList(); - Assert.Equal("01", rows[0].ID); - Assert.Equal("HR", rows[0].Name); - Assert.Equal("02", rows[1].ID); - Assert.Equal("IT", rows[1].Name); - } + await _excelExporter.ExportAsync(path, value, overwriteFile: true); + await Assert.ThrowsAsync(async () => await _excelExporter.ExportAsync(path, value)); + await _excelExporter.ExportAsync(path, value, overwriteFile: true); } - /// - /// SaveAs By Reader Closed error : 'Error! Invalid attempt to call FieldCount when reader is closed' #230 - /// https://github.com/mini-software/MiniExcel/issues/230 - /// [Fact] - public void Issue230() + public void TestIssue310() { - using var conn = Db.GetConnection("Data Source=:memory:"); - conn.Open(); - using var cmd = conn.CreateCommand(); - cmd.CommandText = "select 1 id union all select 2"; - - using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) - { - while (reader.Read()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - var result = $"{reader.GetName(i)} , {reader.GetValue(i)}"; - _output.WriteLine(result); - } - } - } - - using var conn2 = Db.GetConnection("Data Source=:memory:"); - conn2.Open(); - using var cmd2 = conn2.CreateCommand(); - cmd2.CommandText = "select 1 id union all select 2"; - using (var reader = cmd2.ExecuteReader(CommandBehavior.CloseConnection)) - { - while (reader.Read()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - var result = $"{reader.GetName(i)}, {reader.GetValue(i)}"; - _output.WriteLine(result); - } - } - } + using var path = AutoDeletingPath.Create(); + var value = new[] { new TestIssue310Dto { V1 = null }, new TestIssue310Dto { V1 = 2 } }; + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToList(); + } - using var conn3 = Db.GetConnection("Data Source=:memory:"); - conn3.Open(); - using var cmd3 = conn3.CreateCommand(); - cmd3.CommandText = "select 1 id union all select 2"; - using (var reader = cmd3.ExecuteReader(CommandBehavior.CloseConnection)) + [Fact] + public void TestIssue310_Fix497() + { + using var path = AutoDeletingPath.Create(); + var value = new[] { - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), reader, printHeader: true); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); - Assert.Equal(1, rows[0].id); - Assert.Equal(2, rows[1].id); - } + new TestIssue310Dto { V1 = null }, + new TestIssue310Dto { V1 = 2 } + }; + _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { EnableWriteNullValueCell = false }); + var rows = _excelImporter.Query(path.ToString()).ToList(); } - /// - /// v0.14.3 QueryAsDataTable error "Cannot set Column to be null" #229 - /// https://github.com/mini-software/MiniExcel/issues/229 - /// [Fact] - public void Issue229() + public void TestIssue312() { - var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); + using var path = AutoDeletingPath.Create(); + TestIssue312Dto[] value = + [ + new() { Value = 12_345.6789 }, + new() { Value = null } + ]; + _excelExporter.Export(path.ToString(), value); - using var dt = _excelImporter.QueryAsDataTable(path); + using var package = new ExcelPackage(path.ToString()); + var cells = package.Workbook.Worksheets[0].Cells; - foreach (DataColumn column in dt.Columns) - { - var v = dt.Rows[3][column]; - Assert.Equal(DBNull.Value, v); - } + var fmt = cells["A2"].Style.Numberformat.Format; + Assert.Equal(12_345.68.ToString(fmt), cells["A2"].Text); + Assert.Equal(12_345.6789, (double)cells["A2"].Value); } - /// - /// [Query Merge cells data · Issue #122 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/122) - /// + // SaveAs and Query support btye[] base64 converter - https://github.com/mini-software/MiniExcel/issues/318 [Fact] - public void Issue122() + public void TestIssue318() { - var config = new OpenXmlConfiguration + var imageByte = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")); + using var path = AutoDeletingPath.Create(); + var value = new[] { - FillMergedCells = true + new { Name="github", Image=imageByte}, }; + _excelExporter.Export(path.ToString(), value); + + + // import to byte[] { - var path = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); - { - var rows = _excelImporter.Query(path, hasHeaderRow: true, configuration: config).ToList(); - Assert.Equal("HR", rows[0].Department); - Assert.Equal("HR", rows[1].Department); - Assert.Equal("HR", rows[2].Department); - Assert.Equal("IT", rows[3].Department); - Assert.Equal("IT", rows[4].Department); - Assert.Equal("IT", rows[5].Department); - } + const string 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 rows = _excelImporter.Query(path.ToString(), true).ToList(); + var actulBase64 = Convert.ToBase64String((byte[])rows[0].Image); + Assert.Equal(expectedBase64, actulBase64); } + // import to base64 string { - var path = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); - { - var rows = _excelImporter.Query(path, hasHeaderRow: true, configuration: config).ToList(); - Assert.Equal("V1", rows[2].Test1); - Assert.Equal("V2", rows[5].Test2); - Assert.Equal("V3", rows[1].Test3); - Assert.Equal("V4", rows[2].Test4); - Assert.Equal("V5", rows[3].Test5); - Assert.Equal("V6", rows[5].Test5); - } + var config = new OpenXmlConfiguration { EnableConvertByteArray = false }; + var rows = _excelImporter.Query(path.ToString(), true, configuration: config).ToList(); + var image = (string)rows[0].Image; + Assert.StartsWith("@@@fileid@@@,xl/media/", image); } - } - /// - /// [Support Xlsm AutoCheck · Issue #227 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/227) - /// + } + + // https://github.com/mini-software/MiniExcel/issues/325 [Fact] - public void Issue227() + public void TestIssue325() { + using var path = AutoDeletingPath.Create(); + var value = new Dictionary { - var path = PathHelper.GetTempPath("xlsm"); - Assert.Throws(() => _excelExporter.Export(path, new[] { new { V = "A1" }, new { V = "A2" } })); - File.Delete(path); - } + { "sheet1",new[]{ new { id = 1, date = DateTime.Parse("2022-01-01") } }}, + { "sheet2",new[]{ new { id = 2, date = DateTime.Parse("2022-01-01") } }}, + }; + _excelExporter.Export(path.ToString(), value); - { - var path = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); - { - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } - { - using var stream = File.OpenRead(path); - var rows = _excelImporter.Query(stream).ToList(); - Assert.Equal(100, rows.Count); - - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD); - Assert.Equal(36, rows[0].Age); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); - } - } - - - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/226 - /// Fix SaveAsByTemplate single column dimension index error #226 - /// - [Fact] - public void Issue226() - { - using var path = AutoDeletingPath.Create(); - var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); - _excelTemplater.FillTemplate(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); - Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); - } - - /// - /// ASP.NET Webform gridview datasource can't use miniexcel queryasdatatable · Issue #223] - /// (https://github.com/mini-software/MiniExcel/issues/223) - /// - [Fact] - public void Issue223() - { - List> value = - [ - new() { { "A", null }, { "B", null } }, - new() { { "A", 123 }, { "B", new DateTime(2021, 1, 1) } }, - new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } - ]; - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), value); - - using var dt = _excelImporter.QueryAsDataTable(path.ToString()); - - var columns = dt.Columns; - Assert.Equal(typeof(object), columns[0].DataType); - Assert.Equal(typeof(object), columns[1].DataType); - - Assert.Equal(123.0, dt.Rows[1]["A"]); - Assert.Equal("HelloWorld", dt.Rows[2]["B"]); - } - - /// - /// [Custom yyyy-MM-dd format not convert datetime · Issue #222] - /// (https://github.com/mini-software/MiniExcel/issues/222) - /// - [Fact] - public void Issue222() - { - var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(typeof(DateTime), rows[1].A.GetType()); - Assert.Equal(new DateTime(2021, 4, 29), rows[1].A); - } - - /// - /// Query Support StartCell #147 - /// https://github.com/mini-software/MiniExcel/issues/147 - /// - [Fact] - public void Issue147() - { - { - var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var rows = _excelImporter.Query(path, hasHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToList(); - - Assert.Equal(["C", "D", "E"], (rows[0] as IDictionary)?.Keys); - Assert.Equal(["Column1", "Column2", "Column3"], new[] { rows[0].C as string, rows[0].D as string, rows[0].E as string }); - Assert.Equal(["C4", "D4", "E4"], new[] { rows[1].C as string, rows[1].D as string, rows[1].E as string }); - Assert.Equal(["C9", "D9", "E9"], new[] { rows[6].C as string, rows[6].D as string, rows[6].E as string }); - Assert.Equal(["C12", "D12", "E12"], new[] { rows[9].C as string, rows[9].D as string, rows[9].E as string }); - Assert.Equal(["C13", "D13", "E13"], new[] { rows[10].C as string, rows[10].D as string, rows[10].E as string }); - foreach (var i in new[] { 4, 5, 7, 8 }) - Assert.Equal(expected: [null, null, null], new[] { rows[i].C as string, rows[i].D as string, rows[i].E as string }); - - Assert.Equal(11, rows.Count); - - - var columns = _excelImporter.GetColumnNames(path, startCell: "C3"); - Assert.Equal(["C", "D", "E"], columns); - } - - { - var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var rows = _excelImporter.Query(path, hasHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToList(); - - Assert.Equal(["Column1", "Column2", "Column3"], (rows[0] as IDictionary)?.Keys); - Assert.Equal(["C4", "D4", "E4"], new[] { rows[0].Column1 as string, rows[0].Column2 as string, rows[0].Column3 as string }); - Assert.Equal(["C9", "D9", "E9"], new[] { rows[5].Column1 as string, rows[5].Column2 as string, rows[5].Column3 as string }); - Assert.Equal(["C12", "D12", "E12"], new[] { rows[8].Column1 as string, rows[8].Column2 as string, rows[8].Column3 as string }); - Assert.Equal(["C13", "D13", "E13"], new[] { rows[9].Column1 as string, rows[9].Column2 as string, rows[9].Column3 as string }); - foreach (var i in new[] { 3, 4, 6, 7 }) - Assert.Equal([null, null, null], new[] { rows[i].Column1 as string, rows[i].Column2 as string, rows[i].Column3 as string }); - - Assert.Equal(10, rows.Count); - - var columns = _excelImporter.GetColumnNames(path, hasHeaderRow: true, startCell: "C3"); - Assert.Equal(["Column1", "Column2", "Column3"], columns); - } - } - - - /// - /// [Can SaveAs support iDataReader export to avoid the dataTable consuming too much memory · Issue #211 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/211) - /// - [Fact] - public void Issue211() - { - using var path = AutoDeletingPath.Create(); - var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); - var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - - using var connection = new SQLiteConnection(connectionString); - var reader = connection.ExecuteReader(@"select 1 Test1,2 Test2 union all select 3 , 4 union all select 5 ,6"); - _excelExporter.Export(path.ToString(), reader); - var rows = _excelImporter.Query(path.ToString(), true).ToList(); - - Assert.Equal(1.0, rows[0].Test1); - Assert.Equal(2.0, rows[0].Test2); - Assert.Equal(3.0, rows[1].Test1); - Assert.Equal(4.0, rows[1].Test2); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/_rels/sheet2.xml.rels"); + var cnt = Regex.Matches(xml, "Id=\"drawing2\"").Count; + Assert.True(cnt == 1); } - /// - /// [When reading Excel, can return IDataReader and DataTable to facilitate the import of database. Like ExcelDataReader provide reader.AsDataSet() · Issue #216 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/216) - /// [Fact] - public void Issue216() + public void TestIssue327() { using var path = AutoDeletingPath.Create(); var value = new[] { - new { Test1 = "1", Test2 = 2 }, - new { Test1 = "3", Test2 = 4 } + new { id = 1, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")) }, + new { id = 2, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")) }, + new { id = 3, file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.html")) }, }; _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); - { - using var table = _excelImporter.QueryAsDataTable(path.ToString()); - - Assert.Equal("Test1", table.Columns[0].ColumnName); - Assert.Equal("Test2", table.Columns[1].ColumnName); - Assert.Equal("1", table.Rows[0]["Test1"]); - Assert.Equal(2.0, table.Rows[0]["Test2"]); - Assert.Equal("3", table.Rows[1]["Test1"]); - Assert.Equal(4.0, table.Rows[1]["Test2"]); - } - - { - using var dt = _excelImporter.QueryAsDataTable(path.ToString(), false); - - Assert.Equal("Test1", dt.Rows[0]["A"]); - Assert.Equal("Test2", dt.Rows[0]["B"]); - Assert.Equal("1", dt.Rows[1]["A"]); - Assert.Equal(2.0, dt.Rows[1]["B"]); - Assert.Equal("3", dt.Rows[2]["A"]); - Assert.Equal(4.0, dt.Rows[2]["B"]); - } - } - - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I3OSKV - /// When exporting, the pure numeric string will be forcibly converted to a numeric type, resulting in the loss of the end data - /// - [Fact] - public void IssueI3OSKV() - { - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Test = "12345678901234567890" } }; - _excelExporter.Export(path.ToString(), value); - - var A2 = _excelImporter.Query(path.ToString(), true).First().Test; - Assert.Equal("12345678901234567890", A2); - } - - { - using var path = AutoDeletingPath.Create(); - var value = new[] { new { Test = 123456.789 } }; - _excelExporter.Export(path.ToString(), value); - - var A2 = _excelImporter.Query(path.ToString(), true).First().Test; - Assert.Equal(123456.789, A2); - } - } - - - /// - /// [Dynamic Query can't summary numeric cell value default, need to cast · Issue #220 · mini-software/MiniExcel] - /// (https://github.com/mini-software/MiniExcel/issues/220) - /// - [Fact] - public void Issue220() - { - var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); - var rows = _excelImporter.Query(path, hasHeaderRow: true); - var result = rows - .GroupBy(s => s.PRT_ID) - .Select(g => new - { - PRT_ID = g.Key, - Apr = g.Sum(x => (double?)x.Apr), - May = g.Sum(x => (double?)x.May), - Jun = g.Sum(x => (double?)x.Jun) - }) - .ToList(); - - Assert.Equal(91843.25, result[0].Jun); - Assert.Equal(50000.99, result[1].Jun); - } - - /// - /// Optimize stream excel type check - /// https://github.com/mini-software/MiniExcel/issues/215 - /// - [Fact] - public void Issue215() - { - using var stream = new MemoryStream(); - _excelExporter.Export(stream, new[] { new { V = "test1" }, new { V = "test2" } }); - var rows = _excelImporter.Query(stream, true).ToList(); - - Assert.Equal("test1", rows[0].V); - Assert.Equal("test2", rows[1].V); - } - - /// - /// DataTable recommended to use Caption for column name first, then use columname - /// https://github.com/mini-software/MiniExcel/issues/217 - /// - [Fact] - public void Issue217() - { - using var table = new DataTable(); - table.Columns.Add("CustomerID"); - table.Columns.Add("CustomerName").Caption = "Name"; - table.Columns.Add("CreditLimit").Caption = "Limit"; - table.Rows.Add(1, "Jonathan", 23.44); - table.Rows.Add(2, "Bill", 56.87); - - using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), table); - - var rows = _excelImporter.Query(path.ToString()).ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); + Assert.Equal(value[0].file, rows[0].file); + Assert.Equal(value[1].file, rows[1].file); + Assert.Equal(value[2].file, rows[2].file); + Assert.Equal("Hello MiniExcel", Encoding.UTF8.GetString(rows[1].file)); + Assert.Equal("Hello MiniExcel", Encoding.UTF8.GetString(rows[2].file)); } - /// - /// _ _exporter.Export(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 - /// https://github.com/mini-software/MiniExcel/issues/212 - /// [Fact] - public void Issue212() + public void TestIssue328() { - const string sheetName = "Demo"; using var path = AutoDeletingPath.Create(); - _excelExporter.Export(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); - - var actualSheetName = _excelImporter.GetSheetNames(path.ToString()).ToList()[0]; - Assert.Equal(sheetName, actualSheetName); - } - - /// - /// Version <= v0.13.1 Template merge row list rendering has no merge - /// https://github.com/mini-software/MiniExcel/issues/207 - /// - [Fact] - public void Issue207() - { + var value = new[] { - var tempaltePath = PathHelper.GetFile("xlsx/TestIssue207_2.xlsx"); - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - var value = new + new { - project = new[] { - new {name = "項目1",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目2",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目3",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目4",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - } - }; - - _excelTemplater.FillTemplate(path, tempaltePath, value); - var rows = _excelImporter.Query(path).ToList(); - - Assert.Equal("項目1", rows[0].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].B); - Assert.Equal("項目2", rows[2].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[2].B); - Assert.Equal("項目3", rows[4].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[4].B); - Assert.Equal("項目4", rows[6].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[6].B); - - Assert.Equal("Test1", rows[8].A); - Assert.Equal("Test2", rows[8].B); - Assert.Equal("Test3", rows[8].C); - - Assert.Equal("項目1", rows[12].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[12].B); - Assert.Equal("項目2", rows[13].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[13].B); - Assert.Equal("項目3", rows[14].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[14].B); - Assert.Equal("項目4", rows[15].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[15].B); - - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); - Assert.Equal("A1:C16", dimension); - } - - { - var tempaltePath = PathHelper.GetFile("xlsx/TestIssue207_Template_Merge_row_list_rendering_without_merge/template.xlsx"); - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - var value = new + id=1, + name="Jack", + indate=new DateTime(2022,5,13), + file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")) + }, + new { - project = new[] { - new {name = "項目1",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目2",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目3",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - new {name = "項目4",content="[]內容1,[]內容2,[]內容3,[]內容4,[]內容5"}, - } - }; - - _excelTemplater.FillTemplate(path, tempaltePath, value); - var rows = _excelImporter.Query(path).ToList(); - - Assert.Equal("項目1", rows[0].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].C); - Assert.Equal("項目2", rows[3].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[3].C); - Assert.Equal("項目3", rows[6].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[6].C); - Assert.Equal("項目4", rows[9].A); - Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[9].C); - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); - Assert.Equal("A1:E15", dimension); - } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/87 - /// - [Fact] - public void Issue87() - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateCenterEmpty.xlsx"); - using var path = AutoDeletingPath.Create(); - var value = new - { - Tests = Enumerable.Range(1, 5).Select((_, i) => new { test1 = i, test2 = i }) + id=2, + name="Henry", + indate=new DateTime(2022,4,10), + file = File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")) + }, }; + _excelExporter.Export(path.ToString(), value); - var rows = _excelImporter.Query(templatePath).ToList(); - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/208 - /// - [Fact] - public void Issue208() - { - var path = PathHelper.GetFile("xlsx/TestIssue208.xlsx"); - var columns = _excelImporter.GetColumnNames(path).ToList(); - Assert.Equal(16384, columns.Count); - Assert.Equal("XFD", columns[16383]); - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/206 - /// - [Fact] - public void Issue206() - { - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); - using var path = AutoDeletingPath.Create(); - - var dt = new DataTable(); - { - dt.Columns.Add("name"); - dt.Columns.Add("department"); - } - var value = new Dictionary - { - ["employees"] = dt - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:B2", dimension); - } - - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateBasicIEmumerableFill.xlsx"); - using var path = AutoDeletingPath.Create(); - - using var dt = new DataTable(); - dt.Columns.Add("name"); - dt.Columns.Add("department"); - dt.Rows.Add("Jack", "HR"); - - var value = new Dictionary { ["employees"] = dt }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - - var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:B2", dimension); - } - } - - - /// - /// https://github.com/mini-software/MiniExcel/issues/193 - /// - [Fact] - public void Issue193() - { - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplexWithNamespacePrefix.xlsx"); - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - // 1. By Class - var value = new - { - title = "FooCompany", - managers = new[] - { - new { name = "Jack", department = "HR" }, - new { name = "Loan", department = "IT" } - }, - employees = new[] - { - new { name = "Wade", department = "HR" }, - new { name = "Felix", department = "HR" }, - new { name = "Eric", department = "IT" }, - new { name = "Keaton", department = "IT" } - } - }; - _excelTemplater.FillTemplate(path, templatePath, value); - - foreach (var sheetName in _excelImporter.GetSheetNames(path)) - { - var rows = _excelImporter.Query(path, sheetName: sheetName).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 dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); - Assert.Equal("A1:C9", dimension); - - /*TODO:row can't contain xmlns*/ - // https://user-images.githubusercontent.com/12729184/114998840-ead44500-9ed3-11eb-8611-58afb98faed9.png - - } - } - - { - var templatePath = PathHelper.GetFile("xlsx/TestTemplateComplex.xlsx"); - using var path = AutoDeletingPath.Create(); - - // 2. By Dictionary - var value = new Dictionary - { - ["title"] = "FooCompany", - ["managers"] = new[] { - new {name="Jack",department="HR"}, - new {name="Loan",department="IT"} - }, - ["employees"] = new[] { - new {name="Wade",department="HR"}, - new {name="Felix",department="HR"}, - new {name="Eric",department="IT"}, - new {name="Keaton",department="IT"} - } - }; - _excelTemplater.FillTemplate(path.ToString(), templatePath, value); - var rows = _excelImporter.Query(path.ToString()).ToList(); - - 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 dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); - Assert.Equal("A1:C9", dimension); - } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/150 - /// - [Fact] - public void Issue150() - { - var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - - Assert.Throws(() => _excelExporter.Export(path, new[] { 1, 2 })); - File.Delete(path); - - Assert.Throws(() => _excelExporter.Export(path, new[] { "1", "2" })); - File.Delete(path); - - Assert.Throws(() => _excelExporter.Export(path, new[] { '1', '2' })); - File.Delete(path); - - Assert.Throws(() => _excelExporter.Export(path, new[] { DateTime.Now })); - File.Delete(path); - - Assert.Throws(() => _excelExporter.Export(path, new[] { Guid.NewGuid() })); - File.Delete(path); - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/157 - /// - [Fact] - public void Issue157() - { - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - List data = - [ - new() - { - ID = new Guid("78de23d2-dcb6-bd3d-ec67-c112bbc322a2"), - Name = "Wade", - BoD = new DateTime(2020, 9, 27), - Points = 5019.12m - }, - new() - { - ID = new Guid("20d3bfce-27c3-ad3e-4f70-35c81c7e8e45"), - Name = "Felix", - BoD = new DateTime(2020, 10, 25), - Points = 7028.46m - }, - new() - { - ID = new Guid("52013bf0-9aeb-48e6-e5f5-e9500afb034f"), - Name = "Phelan", - BoD = new DateTime(2020, 10, 25), - Points = 3835.7m, - VIP = true - }, - new() - { - ID = new Guid("3b97b87c-7afe-664f-1af5-6914d313ae25"), - Name = "Samuel", - BoD = new DateTime(2020, 6, 21), - Points = 9351.71m - }, - new() - { - ID = new Guid("9a989c43-d55f-5306-0d2f-0fbafae135bb"), - Name = "Raymond", - BoD = new DateTime(2021, 7, 12), - Points = 8209.76m, - VIP = true - } - ]; - - _excelExporter.Export(path, data); - - var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); - - using var p = new ExcelPackage(new FileInfo(path)); - var ws = p.Workbook.Worksheets.First(); - Assert.Equal("Sheet1", ws.Name); - Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); - } - { - var path = PathHelper.GetFile("xlsx/TestIssue157.xlsx"); + var rowIndx = 0; + using var reader = _excelImporter.GetDataReader(path.ToString(), true); - { - var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); - } - using (var p = new ExcelPackage(new FileInfo(path))) - { - var ws = p.Workbook.Worksheets.First(); - Assert.Equal("Sheet1", ws.Name); - Assert.Equal("Sheet1", p.Workbook.Worksheets["Sheet1"].Name); - } + Assert.Equal("id", reader.GetName(0)); + Assert.Equal("name", reader.GetName(1)); + Assert.Equal("indate", reader.GetName(2)); + Assert.Equal("file", reader.GetName(3)); + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) { - var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); - Assert.Equal(5, rows.Count); - - Assert.Equal(new Guid("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); - Assert.Equal("Wade", rows[0].Name); - Assert.Equal(new DateTime(2020,9,27), rows[0].BoD); - Assert.False(rows[0].VIP); - Assert.Equal(5019.12m, rows[0].Points); - Assert.Equal(1, rows[0].IgnoredProperty); + var v = reader.GetValue(i); + if (rowIndx == 0 && i == 0) Assert.Equal(1.0, v); + if (rowIndx == 0 && i == 1) Assert.Equal("Jack", v); + if (rowIndx == 0 && i == 2) Assert.Equal(new DateTime(2022, 5, 13), v); + if (rowIndx == 0 && i == 3) Assert.Equal(File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.png")), v); + if (rowIndx == 1 && i == 0) Assert.Equal(2.0, v); + if (rowIndx == 1 && i == 1) Assert.Equal("Henry", v); + if (rowIndx == 1 && i == 2) Assert.Equal(new DateTime(2022, 4, 10), v); + if (rowIndx == 1 && i == 3) Assert.Equal(File.ReadAllBytes(PathHelper.GetFile("xlsx/Issue327/TestIssue327.txt")), v); } + rowIndx++; } + + //TODO:How to resolve empty body sheet? } - /// - /// https://github.com/mini-software/MiniExcel/issues/149 - /// [Fact] - public void Issue149() + public void TestIssue331() { - char[] chars = - [ - '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', - '\u0009', // - '\u000A', // - '\u000B','\u000C', - '\u000D', // - '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', - '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' - ]; - var strings = chars.Select(s => s.ToString()).ToArray(); + var cln = CultureInfo.CurrentCulture.Name; + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("cs-CZ"); + var data = Enumerable.Range(1, 10).Select(x => new TestIssue331Dto { - var path = PathHelper.GetFile("xlsx/TestIssue149.xlsx"); - var rows = _excelImporter.Query(path).Select(s => (string)s.A).ToList(); - for (int i = 0; i < chars.Length; i++) - { - //output.WriteLine($"{i} , {chars[i]} , {rows[i]}"); - if (i == 13) - continue; + Number = x, + Text = $"Number {x}", + DecimalNumber = x / 2m, + DoubleNumber = x / 2d + }); - Assert.Equal(strings[i], rows[i]); - } - } + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), data); + + var rows = _excelImporter.Query(path.ToString(), startCell: "A2").ToArray(); + Assert.Equal(1.5, rows[2].B); + Assert.Equal(1.5, rows[2].C); + + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); + } + [Fact] + public void TestIssue331_2() + { + var cln = CultureInfo.CurrentCulture.Name; + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("cs-CZ"); + + var config = new OpenXmlConfiguration { - using var path = AutoDeletingPath.Create(); - var input = chars.Select(s => new { Test = s.ToString() }); - _excelExporter.Export(path.ToString(), input); + Culture = CultureInfo.GetCultureInfo("cs-CZ") + }; - var rows = _excelImporter.Query(path.ToString(), true).Select(s => (string)s.Test).ToList(); - for (int i = 0; i < chars.Length; i++) - { - _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); - if (i is 13 or 9 or 10) - continue; + var rnd = new Random(); + var data = Enumerable.Range(1, 100).Select(x => new TestIssue331Dto + { + Number = x, + Text = $"Number {x}", + DecimalNumber = (decimal)rnd.NextDouble(), + DoubleNumber = rnd.NextDouble() + }); - Assert.Equal(strings[i], rows[i]); - } + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), data, configuration: config); + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); + } + + // Excel was unable to open the file https://github.com/mini-software/MiniExcel/issues/343 + [Fact] + public void TestIssue343() + { + var date = DateTime.Parse("2022-03-17 09:32:06.111", CultureInfo.InvariantCulture); + using var path = AutoDeletingPath.Create(); + + CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("ff-Latn"); + var table = new DataTable(); + { + table.Columns.Add("time1", typeof(DateTime)); + table.Columns.Add("time2", typeof(DateTime)); + table.Rows.Add(date, date); + table.Rows.Add(date, date); } + var reader = table.CreateDataReader(); + _excelExporter.Export(path.ToString(), reader); + var rows = _excelImporter.Query(path.ToString(), true).ToArray(); + Assert.Equal(date, rows[0].time1); + Assert.Equal(date, rows[0].time2); + } + + [Fact] + public void TestIssue352() + { { + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + using var path = AutoDeletingPath.Create(); - var input = chars.Select(s => new { Test = s.ToString() }); - _excelExporter.Export(path.ToString(), input); + var reader = table.CreateDataReader(); + _excelExporter.Export(path.ToString(), reader); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Count(xml, ""); + } + { + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); - var rows = _excelImporter.Query(path.ToString()).Select(s => s.Test).ToList(); - for (int i = 0; i < chars.Length; i++) - { - _output.WriteLine($"{i}, {chars[i]}, {rows[i]}"); - if (i is 13 or 9 or 10) - continue; + using var path = AutoDeletingPath.Create(); + var reader = table.CreateDataReader(); + _excelExporter.Export(path.ToString(), reader, false); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Count(xml, ""); + } + { + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); - Assert.Equal(strings[i], rows[i]); - } + using var path = AutoDeletingPath.Create(); + var reader = table.CreateDataReader(); + _excelExporter.Export(path.ToString(), reader); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Count(xml, ""); } } - private class Issue149VO + [Fact] + public void TestIssue360() { - public string Test { get; set; } + var path = PathHelper.GetFile("xlsx/NotDuplicateSharedStrings_10x100.xlsx"); + var config = new OpenXmlConfiguration { SharedStringCacheSize = 1 }; + var sheets = _excelImporter.GetSheetNames(path); + foreach (var sheetName in sheets) + { + _ = _excelImporter.QueryAsDataTable(path, hasHeaderRow: true, sheetName: sheetName, configuration: config); + } } - /// - /// https://github.com/mini-software/MiniExcel/issues/153 - /// [Fact] - public void Issue153() + public void TestIssue369() { - var path = PathHelper.GetFile("xlsx/TestIssue153.xlsx"); - var rows = _excelImporter.Query(path, true).First() as IDictionary; - Assert.Equal( - [ - "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", - "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" - ], rows?.Keys); + var config = new OpenXmlConfiguration + { + DynamicColumns = + [ + new DynamicExcelColumn("id") { Ignore=true }, + new DynamicExcelColumn("name") { Index=1, Width=10 }, + new DynamicExcelColumn("createdate") { Index=0, Format="yyyy-MM-dd", Width=15 }, + new DynamicExcelColumn("point") { Index=2, Name="Account Point" } + ] + }; + using var path = AutoDeletingPath.Create(); + var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12), point = 123.456 } }; + _excelExporter.Export(path.ToString(), value, configuration: config); + + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("createdate", rows[0].A); + Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); + Assert.Equal("name", rows[0].B); + Assert.Equal("Jack", rows[1].B); + Assert.Equal("Account Point", rows[0].C); + Assert.Equal(123.456, rows[1].C); } - /// - /// https://github.com/mini-software/MiniExcel/issues/137 - /// [Fact] - public void Issue137() + public void TestIssue370() { - var path = PathHelper.GetFile("xlsx/TestIssue137.xlsx"); + var config = new OpenXmlConfiguration { - var rows = _excelImporter.Query(path).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); - { - var row = rows[0] as IDictionary; - Assert.Equal("比例", row!["A"]); - Assert.Equal("商品", row["B"]); - Assert.Equal("滿倉口數", row["C"]); - Assert.Equal(" ", row["D"]); - Assert.Equal(" ", row["E"]); - Assert.Equal(" ", row["F"]); - Assert.Equal(0.0, row["G"]); - Assert.Equal("1為港幣 0為台幣", row["H"]); - } - { - var row = rows[1] as IDictionary; - Assert.Equal(1.0, row!["A"]); - Assert.Equal("MTX", row["B"]); - Assert.Equal(10.0, row["C"]); - Assert.Null(row["D"]); - Assert.Null(row["E"]); - Assert.Null(row["F"]); - Assert.Null(row["G"]); - Assert.Null(row["H"]); - } + DynamicColumns = + [ + new DynamicExcelColumn("Id") { Ignore = true }, + new DynamicExcelColumn("Name") { Index = 1,Width = 10 }, + new DynamicExcelColumn("Date") { Index = 0, Format="yyyy-MM-dd", Width = 15 }, + new DynamicExcelColumn("Point") { Index = 2, Name = "Account Point" } + ] + }; + using var path = AutoDeletingPath.Create(); + List> value = + [ + new() { - var row = rows[2] as IDictionary; - Assert.Equal(0.95, row!["A"]); + ["Id"] = 1, + ["Name"] = "Jack", + ["Date"] = new DateTime(2022, 04, 12), + ["Point"] = 123.456 } - } + ]; + _excelExporter.Export(path.ToString(), value, configuration: config); - // dynamic query with head + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("Date", rows[0].A); + Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Jack", rows[1].B); + Assert.Equal("Account Point", rows[0].C); + Assert.Equal(123.456, rows[1].C); + } + + [Theory] + [InlineData(true, 1)] + [InlineData(false, 0)] + public void TestIssue401(bool autoFilter, int count) + { + // Test for DataTable { - var rows = _excelImporter.Query(path, true).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); + var table = new DataTable(); { - var row = rows[0] as IDictionary; - Assert.Equal(1.0, row!["比例"]); - Assert.Equal("MTX", row["商品"]); - Assert.Equal(10.0, row["滿倉口數"]); - Assert.Null(row["0"]); - Assert.Null(row["1為港幣 0為台幣"]); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); } - { - var row = rows[1] as IDictionary; - Assert.Equal(0.95, row!["比例"]); - } - } + var reader = table.CreateDataReader(); + using var path = AutoDeletingPath.Create(); + var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; + _excelExporter.Export(path.ToString(), reader, configuration: config); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Matches(xml, "").Count; + Assert.Equal(count, cnt); + } { - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(10, rows.Count); + var table = new DataTable(); { - var row = rows[0]; - Assert.Equal(1, row.比例); - Assert.Equal("MTX", row.商品); - Assert.Equal(10, row.滿倉口數); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); } + var reader = table.CreateDataReader(); + using var path = AutoDeletingPath.Create(); + var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; + _excelExporter.Export(path.ToString(), reader, false, configuration: config); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Matches(xml, "").Count; + Assert.Equal(count, cnt); + } + { + var table = new DataTable(); { - var row = rows[1]; - Assert.Equal(0.95, row.比例); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); } + var reader = table.CreateDataReader(); + using var path = AutoDeletingPath.Create(); + var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; + _excelExporter.Export(path.ToString(), reader, configuration: config); + + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Matches(xml, "").Count; + Assert.Equal(count, cnt); } - } - private class Issue137ExcelRow - { - public double? 比例 { get; set; } - public string 商品 { get; set; } - public int? 滿倉口數 { get; set; } - } + // Test for DataReader + { + using var path = AutoDeletingPath.Create(); + var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; + using (var connection = Db.GetConnection("Data Source=:memory:")) + { + connection.Open(); + using var command = connection.CreateCommand(); + command.CommandText = + """ + SELECT + 'MiniExcel' as Column1, + 1 as Column2 - /// - /// https://github.com/mini-software/MiniExcel/issues/138 - /// - [Fact] - public void Issue138() - { - var path = PathHelper.GetFile("xlsx/TestIssue138.xlsx"); - { - var rows = _excelImporter.Query(path, true).ToList(); - Assert.Equal(6, rows.Count); + UNION ALL + SELECT 'Github', 2 + """; - foreach (var index in new[] { 0, 2, 5 }) - { - Assert.Equal(1, rows[index].實單每日損益); - Assert.Equal(2, rows[index].程式每日損益); - Assert.Equal("測試商品1", rows[index].商品); - Assert.Equal(111.11, rows[index].滿倉口數); - Assert.Equal(111.11, rows[index].波段); - Assert.Equal(111.11, rows[index].當沖); + using var reader = command.ExecuteReader(); + _excelExporter.Export(path.ToString(), reader, configuration: config); } - foreach (var index in new[] { 1, 3, 4 }) - { - Assert.Null(rows[index].實單每日損益); - Assert.Null(rows[index].程式每日損益); - Assert.Null(rows[index].商品); - Assert.Null(rows[index].滿倉口數); - Assert.Null(rows[index].波段); - Assert.Null(rows[index].當沖); - } + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Matches(xml, "autoFilter").Count; + Assert.Equal(count, cnt); } + { + var xlsxPath = PathHelper.GetFile("xlsx/Test5x2.xlsx"); + using var tempSqlitePath = AutoDeletingPath.Create(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + var connectionString = $"Data Source={tempSqlitePath};Version=3;"; - var rows = _excelImporter.Query(path).ToList(); - Assert.Equal(6, rows.Count); - Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); + using (var connection = new SQLiteConnection(connectionString)) + { + connection.Execute("create table T (A varchar(20),B varchar(20));"); + } - foreach (var index in new[] { 0, 2, 5 }) + using (var connection = new SQLiteConnection(connectionString)) { - Assert.Equal(1, rows[index].實單每日損益); - Assert.Equal(2, rows[index].程式每日損益); - Assert.Equal("測試商品1", rows[index].商品); - Assert.Equal(111.11, rows[index].滿倉口數); - Assert.Equal(111.11, rows[index].波段); - Assert.Equal(111.11, rows[index].當沖); + connection.Open(); + using (var transaction = connection.BeginTransaction()) + using (var stream = File.OpenRead(xlsxPath)) + { + var rows = _excelImporter.Query(stream); + foreach (var row in rows) + connection.Execute( + "insert into T (A,B) values (@A,@B)", + new { row.A, row.B }, + transaction: transaction); + + transaction.Commit(); + } } - foreach (var index in new[] { 1, 3, 4 }) + using var path = AutoDeletingPath.Create(); + var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; + using (var connection = new SQLiteConnection(connectionString)) { - Assert.Null(rows[index].實單每日損益); - Assert.Null(rows[index].程式每日損益); - Assert.Null(rows[index].商品); - Assert.Null(rows[index].滿倉口數); - Assert.Null(rows[index].波段); - Assert.Null(rows[index].當沖); + using var command = new SQLiteCommand("select * from T", connection); + connection.Open(); + using var reader = command.ExecuteReader(); + _excelExporter.Export(path.ToString(), reader, configuration: config); } - } - } - private class Issue138ExcelRow - { - public DateTime? Date { get; set; } - public int? 實單每日損益 { get; set; } - public int? 程式每日損益 { get; set; } - public string 商品 { get; set; } - public double? 滿倉口數 { get; set; } - public double? 波段 { get; set; } - public double? 當沖 { get; set; } + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var cnt = Regex.Matches(xml, "autoFilter").Count; + Assert.Equal(count, cnt); + } } - /// - /// https://gitee.com/dotnetchina/MiniExcel/issues/I50VD5 - /// + // https://github.com/MiniExcel/MiniExcel/issues/405) [Fact] - public void IssueI50VD5() + public void TestIssue405() { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - - List list1 = - [ - new { Name = "github", Image = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")) }, - new { Name = "google", Image = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")) }, - new { Name = "microsoft", Image = File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png")) }, - new { Name = "reddit", Image = File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png")) }, - new { Name = "stackoverflow", Image = File.ReadAllBytes(PathHelper.GetFile("images/stackoverflow_logo.png")) } - ]; - - List list2 = - [ - new { Id = 1, Name = "github", Image = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")) }, - new { Id = 2, Name = "google", Image = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")) } - ]; - - var sheets = new Dictionary - { - ["A"] = list1, - ["B"] = list2, - }; - _excelExporter.Export(path, sheets); - - { - Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing1.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=\"drawing1\"", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); - Assert.Contains("../drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); + using var path = AutoDeletingPath.Create(); + var value = new[] { new { id = 1, name = "test" } }; + _excelExporter.Export(path.ToString(), value); - Assert.Contains("/xl/media/", SheetHelper.GetZipFileContent(path, "xl/drawings/_rels/drawing2.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", SheetHelper.GetZipFileContent(path, "xl/drawings/drawing2.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", SheetHelper.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=\"drawing2\"", SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet2.xml")); - Assert.Contains("../drawings/drawing2.xml", SheetHelper.GetZipFileContent(path, "xl/worksheets/_rels/sheet2.xml.rels")); - } + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/sharedStrings.xml"); + Assert.StartsWith("(path, configuration: config).ToList(); + var config = new OpenXmlConfiguration + { + Culture = new CultureInfo("ru") + { + 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); @@ -2794,23 +2023,29 @@ public void TestIssue409() 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 + // https://github.com/MiniExcel/MiniExcel/issues/413) + [Fact] + public void TestIssue413() { - private readonly IEnumerable _inner = inner; - public int GetEnumeratorCount { get; private set; } - - public IEnumerator GetEnumerator() + using var path = AutoDeletingPath.Create(); + var value = new { - GetEnumeratorCount++; - return _inner.GetEnumerator(); - } + list = new List> + { + new() { { "id","001"},{ "time",new DateTime(2022,12,25)} }, + new() { { "id","002"},{ "time",new DateTime(2022,9,23)} }, + } + }; + var templatePath = PathHelper.GetFile("xlsx/TestIssue413.xlsx"); + _excelTemplater.FillTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); + + Assert.Equal("2022-12-25 00:00:00", rows[1].B); + Assert.Equal("2022-09-23 00:00:00", rows[2].B); } - /// - /// https://github.com/mini-software/MiniExcel/issues/422 - /// [Fact] public void Issue422() { @@ -2827,6 +2062,21 @@ public void Issue422() Assert.Equal(1, enumerableWithCount.GetEnumeratorCount); } + // Exception : MiniExcelLibs.Core.Exceptions.ExcelInvalidCastException: 'ColumnName : Date, CellRow : 2, Value : 2021-01-31 10:03:00 +08:00, it can't cast to DateTimeOffset type.' + [Fact] + public void TestIssue430() + { + using var path = AutoDeletingPath.Create(); + TestIssue430Dto[] value = + [ + new() { Date = new DateTimeOffset(2021, 1, 31, 10, 3, 0, TimeSpan.FromHours(5)) } + ]; + _excelExporter.Export(path.ToString(), value); + + var testValue = _excelImporter.Query(path.ToString(), hasHeaderRow: true).First(); + Assert.Equal("2021-01-31 10:03:00", testValue.Date.ToString("yyyy-MM-dd HH:mm:ss")); + } + [Fact] public void Issue459() { @@ -2870,37 +2120,88 @@ public void Issue520() Assert.Equal(300.0, cells["C2"].Value); Assert.Equal(300.0.ToString("R$ #,##0.00"), cells["C2"].Text); } + + [Fact] + public void Issue527() + { + List row = + [ + new() { Name = "Bill", UserType = DescriptionEnum.V1 }, + new() { Name = "Bob", UserType = DescriptionEnum.V2 } + ]; + + var value = new { t = row }; + var template = PathHelper.GetFile("xlsx/Issue527Template.xlsx"); + + using var path = AutoDeletingPath.Create(); + _excelTemplater.FillTemplate(path.FilePath, template, value); + + var rows = _excelImporter.Query(path.FilePath).ToList(); + Assert.Equal("General User", rows[1].B); + Assert.Equal("General Administrator", rows[2].B); + } + + [Fact] + public void Issue542() + { + var path = PathHelper.GetFile("xlsx/TestIssue542.xlsx"); - class Issue520Dto(long l1, DateTime dt, long l2) - { - [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] - public long PaymentValue { get; set; } = l1; + var resultWithoutFirstRow = _excelImporter.Query(path).ToList(); + var resultWithFirstRow = _excelImporter.Query(path, treatHeaderAsData: true).ToList(); - [MiniExcelColumn(Format = "dd/MM/yyyy", Width = 15)] - public DateTime PaymentDate { get; set; } = dt; + Assert.Equal(15, resultWithoutFirstRow.Count); + Assert.Equal(16, resultWithFirstRow.Count); - [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] - public long ValueToSettle { get; set; } = l2; + Assert.Equal("Felix", resultWithoutFirstRow[0].Name); + Assert.Equal("Wade", resultWithFirstRow[0].Name); } - + [Fact] - public void Issue527() + public void TestIssue549() { - List row = - [ - new() { Name = "Bill", UserType = DescriptionEnum.V1 }, - new() { Name = "Bob", UserType = DescriptionEnum.V2 } - ]; + var data = new[] + { + new{ id = 1, name = "jack" }, + new{ id = 2, name = "mike" } + }; + + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); - var value = new { t = row }; - var template = PathHelper.GetFile("xlsx/Issue527Template.xlsx"); + _excelExporter.Export(path, data); + var rows = _excelImporter.Query(path, true).ToList(); + { + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var workbook = new XSSFWorkbook(stream); + + var sheet = workbook.GetSheetAt(0); + var a2 = sheet.GetRow(1).GetCell(0); + var b2 = sheet.GetRow(1).GetCell(1); + Assert.Equal((string)rows[0].id.ToString(), a2.NumericCellValue.ToString(CultureInfo.InvariantCulture)); + Assert.Equal((string)rows[0].name.ToString(), b2.StringCellValue); + } + } + [Fact] + public void TestIssue553() + { using var path = AutoDeletingPath.Create(); - _excelTemplater.FillTemplate(path.FilePath, template, value); + var templatePath = PathHelper.GetFile("xlsx/TestIssue553.xlsx"); + var data = new + { + B = new[] + { + new{ ITM = 1 }, + new{ ITM = 2 }, + new{ ITM = 3 } + } + }; + _excelTemplater.FillTemplate(path.ToString(), templatePath, data); - var rows = _excelImporter.Query(path.FilePath).ToList(); - Assert.Equal("General User", rows[1].B); - Assert.Equal("General Administrator", rows[2].B); + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal(rows[2].A, 1); + Assert.Equal(rows[3].A, 2); + Assert.Equal(rows[4].A, 3); } [Fact] @@ -2939,69 +2240,21 @@ WITH test('Id', 'Name') AS ( Assert.Equal("Name", rows[0].A); } - private class Issue585VO1 - { - public string Col1 { get; set; } - public string Col2 { get; set; } - public string Col3 { get; set; } - } - - private class Issue585VO2 - { - public string Col1 { get; set; } - - [MiniExcelColumnName("Col2")] - public string Prop2 { get; set; } - - public string Col3 { get; set; } - } - - private class Issue585VO3 - { - public string Col1 { get; set; } - - [MiniExcelColumnIndex("B")] - public string Prop2 { get; set; } - - public string Col3 { get; set; } - } - [Fact] public void Issue585() { var path = PathHelper.GetFile("xlsx/TestIssue585.xlsx"); - var items1 = _excelImporter.Query(path); + var items1 = _excelImporter.Query(path); Assert.Equal(2, items1.Count()); - var items2 = _excelImporter.Query(path); + var items2 = _excelImporter.Query(path); Assert.Equal(2, items2.Count()); - var items3 = _excelImporter.Query(path); + var items3 = _excelImporter.Query(path); Assert.Equal(2, items3.Count()); } - private class Issue542 - { - [MiniExcelColumnIndex(0)] public Guid ID { get; set; } - [MiniExcelColumnIndex(1)] public string Name { get; set; } - } - - [Fact] - public void Issue_542() - { - var path = PathHelper.GetFile("xlsx/TestIssue542.xlsx"); - - var resultWithoutFirstRow = _excelImporter.Query(path).ToList(); - var resultWithFirstRow = _excelImporter.Query(path, treatHeaderAsData: true).ToList(); - - Assert.Equal(15, resultWithoutFirstRow.Count); - Assert.Equal(16, resultWithFirstRow.Count); - - Assert.Equal("Felix", resultWithoutFirstRow[0].Name); - Assert.Equal("Wade", resultWithFirstRow[0].Name); - } - [Fact] public void Issue606_1() { @@ -3041,7 +2294,7 @@ public void Issue606_1() var path = Path.Combine ( Path.GetTempPath(), - string.Concat(nameof(MiniExcelIssueTests), "_", nameof(Issue606_1), ".xlsx") + string.Concat(nameof(MiniExcelGithubIssuesTests), "_", nameof(Issue606_1), ".xlsx") ); var templateFileName = PathHelper.GetFile("xlsx/TestIssue606_Template.xlsx"); @@ -3049,9 +2302,6 @@ public void Issue606_1() File.Delete(path); } - /// - /// https://github.com/mini-software/MiniExcel/issues/627 - /// [Fact] public void TestIssue627() { @@ -3099,26 +2349,17 @@ public void Issue632_1() var path = Path.Combine( Path.GetTempPath(), - string.Concat(nameof(MiniExcelIssueTests), "_", nameof(Issue632_1), ".xlsx") + string.Concat(nameof(MiniExcelGithubIssuesTests), "_", nameof(Issue632_1), ".xlsx") ); _excelExporter.Export(path, values, configuration: config, overwriteFile: true); File.Delete(path); } - private class Issue658TestData - { - public string FirstName { get; set; } - public string LastName { get; set; } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/658 - /// [Fact] public void Issue_658() { - static IEnumerable GetTestData() + static IEnumerable GetTestData() { yield return new() { FirstName = "Unit", LastName = "Test" }; yield return new() { FirstName = "Unit1", LastName = "Test1" }; @@ -3126,8 +2367,8 @@ static IEnumerable GetTestData() } using var memoryStream = new MemoryStream(); - var testData = GetTestData(); - var rowsWritten = _excelExporter.Export(memoryStream, testData, configuration: new OpenXmlConfiguration + var testData = GetTestData().ToList(); + var rowsWritten = _excelExporter.Export(memoryStream, testData, configuration: new OpenXmlConfiguration { FastMode = true }); @@ -3136,45 +2377,9 @@ static IEnumerable GetTestData() memoryStream.Position = 0; - var queryData = _excelImporter.Query(memoryStream).ToList(); - - Assert.Equal(testData.Count(), queryData.Count); - - var i = 0; - foreach (var data in testData) - { - Assert.Equal(data.FirstName, queryData[i].FirstName); - Assert.Equal(data.LastName, queryData[i].LastName); - i++; - } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/658 - /// - /// - [Fact] - public async Task Issue_658_async() - { - static IEnumerable GetTestData() - { - yield return new() { FirstName = "Unit", LastName = "Test" }; - yield return new() { FirstName = "Unit1", LastName = "Test1" }; - yield return new() { FirstName = "Unit2", LastName = "Test2" }; - } - - using var memoryStream = new MemoryStream(); - var testData = GetTestData(); - await _excelExporter.ExportAsync(memoryStream, testData, configuration: new OpenXmlConfiguration - { - FastMode = true, - }); - - memoryStream.Position = 0; - - var queryData = _excelImporter.QueryAsync(memoryStream).ToBlockingEnumerable().ToList(); + var queryData = _excelImporter.Query(memoryStream).ToList(); - Assert.Equal(testData.Count(), queryData.Count); + Assert.Equal(testData.Count, queryData.Count); var i = 0; foreach (var data in testData) @@ -3208,19 +2413,12 @@ public void Test_Issue_693_SaveSheetWithLongName() Assert.Throws(() => _excelExporter.InsertSheet(path2.ToString(), data, sheetName: "Some Other Very Looooooong Sheet Name")); } - private class Issue697 - { - public int First { get; set; } - public int Second { get; set; } - public int Third { get; set; } - public int Fourth { get; set; } - } [Fact] public void Test_Issue_697_EmptyRowsStronglyTypedQuery() { var path = PathHelper.GetFile("xlsx/TestIssue697.xlsx"); - var rowsIgnoreEmpty = _excelImporter.Query(path, configuration: new OpenXmlConfiguration { IgnoreEmptyRows = true }).ToList(); - var rowsCountEmpty = _excelImporter.Query(path).ToList(); + var rowsIgnoreEmpty = _excelImporter.Query(path, configuration: new OpenXmlConfiguration { IgnoreEmptyRows = true }).ToList(); + var rowsCountEmpty = _excelImporter.Query(path).ToList(); Assert.Equal(4, rowsIgnoreEmpty.Count); Assert.Equal(5, rowsCountEmpty.Count); } @@ -3291,11 +2489,6 @@ public void TestIssue750() _output.WriteLine($"memoryIncrease: {memoryIncrease}"); } - - /// - /// https://github.com/mini-software/MiniExcel/issues/751 - /// Optimize CleanXml method #751 - /// [Fact] public void TestIssue751() { @@ -3329,10 +2522,6 @@ public void TestIssue751() } } - /// - /// https://github.com/mini-software/MiniExcel/issues/763 - /// Optimize CleanXml method #751 - /// [Fact] public void TestIssue763() { @@ -3342,10 +2531,6 @@ public void TestIssue763() Assert.Null(rows[0].J); } - /// - /// https://github.com/mini-software/MiniExcel/issues/768 - /// Optimize CleanXml method #751 - /// [Fact] public void TestIssue768() { @@ -3373,41 +2558,6 @@ public void TestIssue768() Assert.Equal(list[1].value1.ToString(), rows[1].A.ToString()); } - /// - /// https://github.com/mini-software/MiniExcel/issues/186 - /// - [Fact] - public void TestIssue186() - { - var originPath = PathHelper.GetFile("xlsx/TestIssue186_Template.xlsx"); - using var path = AutoDeletingPath.Create(); - File.Copy(originPath, path.FilePath); - - MiniExcelPicture[] images = - [ - new() - { - ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")), - SheetName = null, // default null is first sheet - CellAddress = "C3", // required - }, - new() - { - ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), - PictureType = "image/png", // default PictureType = image/png - SheetName = "Demo", - CellAddress = "C9", // required - WidthPx = 100, - HeightPx = 100 - } - ]; - - _excelTemplater.AddPicture(path.FilePath, images); - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/771 - /// [Fact] public void TestIssue771() { @@ -3442,9 +2592,6 @@ public void TestIssue771() IEnumerable GetEnumerable() => Enumerable.Range(0, 3).Select(s => new { ID = Guid.NewGuid(), level = s }); } - /// - /// https://github.com/mini-software/MiniExcel/issues/772 - /// [Fact] public void TestIssue772() { @@ -3457,9 +2604,6 @@ public void TestIssue772() Assert.Equal("01108083-1Delta", testValue); } - /// - /// https://github.com/mini-software/MiniExcel/issues/773 - /// [Fact] public void TestIssue773() { @@ -3482,9 +2626,6 @@ public void TestIssue773() Assert.Equal("Ram", rows[6].B); } - /// - /// https://github.com/mini-software/MiniExcel/issues/789 - /// [Fact] public void TestIssue789() { @@ -3496,14 +2637,22 @@ public void TestIssue789() }; _excelExporter.Export(path, value); - var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); Assert.Contains("", xml); } - /// - /// https://github.com/mini-software/MiniExcel/issues/814 - /// + [Fact] + public void TestIssue809() + { + var path = PathHelper.GetFile("xlsx/TestIssue809.xlsx"); + var rows = _excelImporter.Query(path).ToList(); + + Assert.Equal(3, rows.Count); + Assert.Null(rows[0].A); + Assert.Equal(2, rows[2].B); + } + [Fact] public void TestIssue814() { @@ -3554,66 +2703,57 @@ public void TestIssue814() Assert.NotNull(pictureInC9); } - /// - /// https://github.com/mini-software/MiniExcel/issues/815 - /// [Fact] public void TestIssue815() { var originPath = PathHelper.GetFile("xlsx/TestIssue186_Template.xlsx"); using var path = AutoDeletingPath.Create(); File.Copy(originPath, path.FilePath); - { - MiniExcelPicture[] images = - [ - new() - { - ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")), - SheetName = null, // default null is first sheet - CellAddress = "C3", // required - }, - new() - { - ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), - PictureType = "image/png", // default PictureType = image/png - SheetName = "Demo", - CellAddress = "C9", // required - WidthPx = 500, - HeightPx = 500 - }, - new() - { - ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), - PictureType = "image/png", // default PictureType = image/png - SheetName = "Demo", - CellAddress = "E9", // required - WidthPx = 800, - HeightPx = 850 - } - ]; - - _excelTemplater.AddPicture(path.FilePath, images); - - using (var package = new ExcelPackage(new FileInfo(path.FilePath))) + + MiniExcelPicture[] images = + [ + new() + { + ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")), + SheetName = null, // default null is first sheet + CellAddress = "C3", // required + }, + new() + { + ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), + PictureType = "image/png", // default PictureType = image/png + SheetName = "Demo", + CellAddress = "C9", // required + WidthPx = 500, + HeightPx = 500 + }, + new() { - // Check picture in the first sheet (C3) - var firstSheet = package.Workbook.Worksheets[0]; - var pictureInC3 = firstSheet.Drawings.OfType().FirstOrDefault(p => p.From.Column == 2 && p.From.Row == 2); - Assert.NotNull(pictureInC3); - - // Check picture in the "Demo" sheet (C9) - var demoSheet = package.Workbook.Worksheets["Demo"]; - var pictureInC9 = demoSheet.Drawings.OfType().FirstOrDefault(p => p.From.Column == 2 && p.From.Row == 8); - Assert.NotNull(pictureInC9); + ImageBytes = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")), + PictureType = "image/png", // default PictureType = image/png + SheetName = "Demo", + CellAddress = "E9", // required + WidthPx = 800, + HeightPx = 850 } - } + ]; + + _excelTemplater.AddPicture(path.FilePath, images); + + using var package = new ExcelPackage(new FileInfo(path.FilePath)); + // Check picture in the first sheet (C3) + var firstSheet = package.Workbook.Worksheets[0]; + var pictureInC3 = firstSheet.Drawings.OfType().FirstOrDefault(p => p.From.Column == 2 && p.From.Row == 2); + Assert.NotNull(pictureInC3); + + // Check picture in the "Demo" sheet (C9) + var demoSheet = package.Workbook.Worksheets["Demo"]; + var pictureInC9 = demoSheet.Drawings.OfType().FirstOrDefault(p => p.From.Column == 2 && p.From.Row == 8); + Assert.NotNull(pictureInC9); // TODO:check C3 image WidthPx = 80px, HeightPx = 24px, C9 WidthPx=500,HeightPx=500 } - /// - /// https://github.com/mini-software/MiniExcel/issues/816 - /// [Fact] public void TestIssue816() { @@ -3747,25 +2887,6 @@ public void TestIssue816() } } - // https://github.com/mini-software/MiniExcel/issues/809 - [Fact] - public void TestIssue809() - { - var path = PathHelper.GetFile("xlsx/TestIssue809.xlsx"); - var rows = _excelImporter.Query(path).ToList(); - - Assert.Equal(3, rows.Count); - Assert.Null(rows[0].A); - Assert.Equal(2, rows[2].B); - } - - - private class Issue869 - { - public string? Name { get; set; } - public DateOnly? Date { get; set; } - } - [Theory] [InlineData("DateTimeMidnight", DateOnlyConversionMode.None, true)] [InlineData("DateTimeNotMidnight", DateOnlyConversionMode.None, true)] @@ -3778,16 +2899,16 @@ public void TestIssue869(string fileName, DateOnlyConversionMode mode, bool thro var path = PathHelper.GetFile($"xlsx/TestIssue869/{fileName}.xlsx"); var config = new OpenXmlConfiguration { DateOnlyConversionMode = mode }; - var testFn = () => _excelImporter.Query(path, configuration: config).ToList(); + List TestFn() => _excelImporter.Query(path, configuration: config).ToList(); if (throwsException) { - Assert.Throws(testFn); + Assert.Throws(TestFn); } else { try { - var result = testFn(); + var result = TestFn(); Assert.Equal(new DateOnly(2025, 1, 1), result[0].Date); } catch (Exception ex) @@ -3816,12 +2937,6 @@ public void TestIssue876() _excelExporter.Export(outputPath.ToString(), sheets); }); } - - private class Issue880 - { - public string Test { get; set; } - public string this[int i] => ""; - } [Fact] public void TestIssue880_ShouldThrowNotSerializableException() @@ -3840,33 +2955,22 @@ public void TestIssue881() { Assert.Throws(() => { - _ = _excelImporter.Query(PathHelper.GetFile("xlsx/TestIssue881.xlsx")).ToList(); + _ = _excelImporter.Query(PathHelper.GetFile("xlsx/TestIssue881.xlsx")).ToList(); }); } - private record ExcelDataRow(string Key, string Value) - { - public ExcelDataRow() : this("", "") { } - } - - /// - /// https://github.com/mini-software/MiniExcel/issues/888 - /// [Fact] public void TestIssue888_ShouldIgnoreFrame() { var xlsxPath = PathHelper.GetFile("xlsx/Issue888_DataWithFrame.xlsx"); - ExcelDataRow[] dataInSheet = - [ - new("Key1", "Value1"), - new("Key2", "Value2") - ]; - - // Act + using var stream = File.OpenRead(xlsxPath); - var dataRead = _excelImporter.Query(stream, startCell: "A2").ToArray(); + var dataRead = _excelImporter.Query(stream, startCell: "A2").ToArray(); - Assert.Equal(dataInSheet, dataRead); + Assert.Equal("Key1", dataRead[0].Key); + Assert.Equal("Value1", dataRead[0].Value); + Assert.Equal("Key2", dataRead[1].Key); + Assert.Equal("Value2", dataRead[1].Value); } [Fact] @@ -3899,7 +3003,7 @@ public void TestIssue951() var templatePath = PathHelper.GetFile("xlsx/TestTemplateEasyFill.xlsx"); using var path = AutoDeletingPath.Create(); - var value = new Issue951 + var value = new Issue951Dto { Name = "Jack", CreateDate = new DateTime(2021, 01, 01), @@ -3910,14 +3014,4 @@ public void TestIssue951() // must not throw _excelTemplater.FillTemplate(path.ToString(), templatePath, value); } - - class Issue951 - { - public string? Name { get; set; } - public DateTime CreateDate { get; set; } - public bool VIP { get; set; } - public double Points { get; set; } - - public object this[string test] => new(); - } } diff --git a/tests/MiniExcel.OpenXml.Tests/Issues/Models.cs b/tests/MiniExcel.OpenXml.Tests/Issues/Models.cs new file mode 100644 index 00000000..4dfe19a2 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Issues/Models.cs @@ -0,0 +1,313 @@ +using System.ComponentModel; + +namespace MiniExcelLib.OpenXml.Tests.Issues; + +internal enum DescriptionEnum +{ + [Description("General User")] V1, + [Description("General Administrator")] V2, + [Description("Super Administrator")] V3 +} + +internal class DescriptionEnumDto +{ + public string? Name { get; set; } + public DescriptionEnum? UserType { get; set; } +} + +public class UserAccount +{ + public Guid ID { get; set; } + public string? Name { get; set; } + public DateTime BoD { get; set; } + public int Age { get; set; } + public bool VIP { get; set; } + public decimal Points { get; set; } + + public int IgnoredProperty => 1; +} + +internal class TestIssues133Dto +{ + public string? Id { get; set; } + public string? Name { get; set; } +} + +internal class Issue137Dto +{ + public double? 比例 { get; set; } + public string? 商品 { get; set; } + public int? 滿倉口數 { get; set; } +} + +internal class Issue138Dto +{ + public DateTime? Date { get; set; } + public int? 實單每日損益 { get; set; } + public int? 程式每日損益 { get; set; } + public string? 商品 { get; set; } + public double? 滿倉口數 { get; set; } + public double? 波段 { get; set; } + public double? 當沖 { get; set; } +} + +internal class Issue142Dto +{ + [MiniExcelColumnName("CustomColumnName")] + public string? MyProperty1 { get; set; } //index = 1 + + [MiniExcelIgnore] public string? MyProperty7 { get; set; } //index = null + public string? MyProperty2 { get; set; } //index = 3 + [MiniExcelColumnIndex(6)] public string? MyProperty3 { get; set; } //index = 6 + + [MiniExcelColumnIndex("A")] // equal column index 0 + public string? MyProperty4 { get; set; } + + [MiniExcelColumnIndex(2)] public string? MyProperty5 { get; set; } //index = 2 + public string? MyProperty6 { get; set; } //index = 4 +} + +internal class Issue142DtoVariant1 +{ + [MiniExcelColumnIndex("Z")] + public int MyProperty1 { get; set; } +} + +internal class Issue142DtoVariant2 +{ + [MiniExcelColumnIndex("B")] + public int MyProperty1 { get; set; } +} + +internal class TestIssue190Dto +{ + public int ID { get; set; } + public string? Name { get; set; } + public int Age { get; set; } +} + +internal class TestIssue209Dto +{ + public int ID { get; set; } + public string? Name { get; set; } + public int SEQ { get; set; } +} + +internal class Issue241Dto +{ + public string? Name { get; set; } + + [MiniExcelFormat("MM dd, yyyy")] public DateTime InDate { get; set; } +} + +internal class Issue255DTO +{ + [MiniExcelFormat("yyyy")] public DateTime Time { get; set; } + + [MiniExcelColumn(Format = "yyyy")] public DateTime Time2 { get; set; } +} + +internal class TestIssue280Dto +{ + [MiniExcelColumnWidth(20)] public int ID { get; set; } + [MiniExcelColumnWidth(15.50)] public string? Name { get; set; } +} + +internal class TestIssue286Dto +{ + public TestIssue286Enum E { get; set; } +} + +internal enum TestIssue286Enum +{ + VIP1, + VIP2 +} + +internal class TestIssue310Dto +{ + public int? V1 { get; set; } +} + +internal class TestIssue312Dto +{ + [MiniExcelFormat("0,0.00")] public double? Value { get; set; } +} + +internal class TestIssue331Dto +{ + public int Number { get; set; } + public decimal DecimalNumber { get; set; } + public double DoubleNumber { get; set; } + public string? Text { get; set; } +} + +internal class Issue409Dto +{ + public string? Units { get; set; } + public double Quantity { get; set; } +} + +internal class Issue422Enumerable(IEnumerable inner) : IEnumerable +{ + public int GetEnumeratorCount { get; private set; } + + public IEnumerator GetEnumerator() + { + GetEnumeratorCount++; + return inner.GetEnumerator(); + } +} + +internal class TestIssue430Dto +{ + [MiniExcelFormat("yyyy-MM-dd HH:mm:ss")] + public DateTimeOffset Date { get; set; } +} + +internal class Issue520Dto(long l1, DateTime dt, long l2) +{ + [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] + public long PaymentValue { get; set; } = l1; + + [MiniExcelColumn(Format = "dd/MM/yyyy", Width = 15)] + public DateTime PaymentDate { get; set; } = dt; + + [MiniExcelColumn(Format = "R$ #,##0.00", Width = 15)] + public long ValueToSettle { get; set; } = l2; +} + +internal class Issue542 +{ + [MiniExcelColumnIndex(0)] public Guid ID { get; set; } + [MiniExcelColumnIndex(1)] public string? Name { get; set; } +} + +internal class Issue585Variant1 +{ + public string? Col1 { get; set; } + public string? Col2 { get; set; } + public string? Col3 { get; set; } +} + +internal class Issue585Variant2 +{ + public string? Col1 { get; set; } + + [MiniExcelColumnName("Col2")] public string? Prop2 { get; set; } + + public string? Col3 { get; set; } +} + +internal class Issue585Variant3 +{ + public string? Col1 { get; set; } + + [MiniExcelColumnIndex("B")] public string? Prop2 { get; set; } + + public string? Col3 { get; set; } +} + +internal class TestIssueI4ZYUUDto +{ + [MiniExcelColumn(Name = "ID", Index = 0)] + public string? MyProperty { get; set; } + + [MiniExcelColumn(Name = "CreateDate", Index = 1, Format = "yyyy-MM", Width = 100)] + public DateTime MyProperty2 { get; set; } +} + +internal class Issue658Dto +{ + public string? FirstName { get; set; } + public string? LastName { get; set; } +} + +internal class Issue697Dto +{ + public int First { get; set; } + public int Second { get; set; } + public int Third { get; set; } + public int Fourth { get; set; } +} + +internal class Issue869 +{ + public string? Name { get; set; } + public DateOnly? Date { get; set; } +} + +internal class Issue880 +{ + public string? Test { get; set; } + public string? this[int i] => ""; +} + +internal class Issue888Dto +{ + public string? Key { get; set; } + public string? Value { get; set; } +} + +internal class Issue951Dto +{ + public string? Name { get; set; } + public DateTime CreateDate { get; set; } + public bool VIP { get; set; } + public double Points { get; set; } + + public object this[string? test] => new(); +} + +internal class TestIssueI4YCLQ_2Dto +{ + [MiniExcelColumnIndex("A")] public string? 站点编码 { get; set; } + [MiniExcelColumnIndex("B")] public string? 站址名称 { get; set; } + [MiniExcelColumnIndex("C")] public string? 值1 { get; set; } + [MiniExcelColumnIndex("D")] public string? 值2 { get; set; } + [MiniExcelColumnIndex("E")] public string? 值3 { get; set; } + [MiniExcelColumnIndex("F")] public string? 资源ID { get; set; } + [MiniExcelColumnIndex("G")] public string? 值4 { get; set; } + [MiniExcelColumnIndex("H")] public string? 值5 { get; set; } + [MiniExcelColumnIndex("I")] public string? 值6 { get; set; } + public string? 值7 { get; set; } + [MiniExcelColumnName("NotExist")] public string? 值8 { get; set; } +} + +internal class TestIssueI4WM67Dto +{ + public int ID { get; set; } + public string? Name { get; set; } +} + +internal class TestIssueI4TXGTDto +{ + public int ID { get; set; } + public string? Name { get; set; } + [DisplayName("Specification")] public string? Spc { get; set; } + [DisplayName("Unit Price")] public decimal Up { get; set; } +} + +internal class TestIssueI49RZHDto +{ + [MiniExcelFormat("dd-MM-yyyy")] public DateTime? dd { get; set; } +} + +internal class TestIssueI40QA5Dto +{ + [MiniExcelColumnName(columnName: "EmployeeNo", aliases: new[] { "EmpNo", "No" })] + public string? Empno { get; set; } + + public string? Name { get; set; } +} + +internal class IssueI3X2ZLDTO +{ + public int Col1 { get; set; } + public DateTime Col2 { get; set; } +} + +internal class Issue149VO +{ + public string? Test { get; set; } +} diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs similarity index 88% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlAsyncTests.cs rename to tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs index 7e96ff28..7551dc33 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlAsyncTests.cs @@ -5,7 +5,7 @@ using MiniExcelLib.Tests.Common; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Main; public class MiniExcelOpenXmlAsyncTests { @@ -40,43 +40,6 @@ public async Task SaveAsControlChracter() var rows1 = await _excelImporter.QueryAsync(path).ToListAsync(); } - private class SaveAsControlChracterVO - { - public string Test { get; set; } - } - - private class ExcelAttributeDemo - { - [MiniExcelColumnName("Column1")] - public string Test1 { get; set; } - [MiniExcelColumnName("Column2")] - public string Test2 { get; set; } - [MiniExcelIgnore] - public string Test3 { get; set; } - [MiniExcelColumnIndex("I")] // system will convert "I" to 8 index - public string Test4 { get; set; } - public string Test5 { get; } //wihout set will ignore - public string Test6 { get; private set; } //un-public set will ignore - [MiniExcelColumnIndex(3)] // start with 0 - public string Test7 { get; set; } - } - - private class ExcelAttributeDemo2 - { - [MiniExcelColumn(Name = "Column1")] - public string Test1 { get; set; } - [MiniExcelColumn(Name = "Column2")] - public string Test2 { get; set; } - [MiniExcelColumn(Ignore = true)] - public string Test3 { get; set; } - [MiniExcelColumn(IndexName = "I")] // system will convert "I" to 8 index - public string Test4 { get; set; } - public string Test5 { get; } //wihout set will ignore - public string Test6 { get; private set; } //un-public set will ignore - [MiniExcelColumn(Index = 3)] // start with 0 - public string Test7 { get; set; } - } - [Fact] public async Task CustomAttributeWihoutVaildPropertiesTest() { @@ -166,14 +129,6 @@ public async Task SaveAsCustomAttributes2Test() Assert.Null(rows[0].Test6); } - private class CustomAttributesWihoutVaildPropertiesTestPoco - { - [MiniExcelIgnore] - public string Test3 { get; set; } - public string Test5 { get; } - public string Test6 { get; private set; } - } - [Fact] public async Task QueryCastToIDictionary() { @@ -288,22 +243,6 @@ public async Task TestDynamicQueryBasic_useHeaderRow() } } - private class DemoPocoHelloWorld - { - public string HelloWorld1 { get; set; } - } - - private class UserAccount - { - public Guid ID { get; set; } - public string Name { get; set; } - public DateTime BoD { get; set; } - public int Age { get; set; } - public bool VIP { get; set; } - public decimal Points { get; set; } - public int IgnoredProperty => 1; - } - [Fact] public async Task QueryStrongTypeMapping_Test() { @@ -336,15 +275,6 @@ public async Task QueryStrongTypeMapping_Test() } } - - private class AutoCheckType - { - public Guid? @guid { get; set; } - public bool? @bool { get; set; } - public DateTime? datetime { get; set; } - public string @string { get; set; } - } - [Fact] public async Task AutoCheckTypeTest() { @@ -447,11 +377,6 @@ public async Task FixDimensionJustOneColumnParsingError_Test() Assert.Equal(2, rows.Count); } - private class SaveAsFileWithDimensionByICollectionTestType - { - public string A { get; set; } - public string B { get; set; } - } [Fact] public async Task SaveAsFileWithDimensionByICollection() { @@ -937,12 +862,6 @@ public async Task SaveAsByDapperRows() } } - - private class Demo - { - public string Column1 { get; set; } - public decimal Column2 { get; set; } - } [Fact] public async Task QueryByStrongTypeParameterTest() { @@ -1675,73 +1594,6 @@ public async Task NumericFormattingWithMiniExcelFormatAttributeTest() Assert.Equal("0.000", cells["I3"].Style.Numberformat.Format); } - /// - /// Test class with multiple numeric properties using MiniExcelFormatAttribute - /// to verify that formatting is correctly applied during Excel export. - /// - private class NumericFormattingTestDto( - decimal currency, - decimal alignedCurrency, - decimal percentage, - double scientificNotation, - decimal fixedDecimal, - long phoneNumber, - long veryLongNumber, - double customFormat) - { - - /// - /// Regular currency format with 2 decimal places - /// - [MiniExcelFormat("\"$\"#,##0.00")] - public decimal Currency { get; set; } = currency; - - /// - /// Currency format with 2 decimal places, parentheses for negatives - /// - [MiniExcelFormat("$#,##0.00_);($#,##0.00)")] - public decimal AlignedCurrency { get; set; } = alignedCurrency; - - /// - /// Percentage format with 0 decimal places - /// - [MiniExcelFormat("0%")] - public decimal Percentage { get; set; } = percentage; - - /// - /// Scientific notation format with 2 decimal places - /// - [MiniExcelFormat("0.00E+00")] - public double ScientificNotation { get; set; } = scientificNotation; - - [MiniExcelFormat("0.00E+00"), MiniExcelHidden] - public double ScientificNotationDuplicate { get; set; } = scientificNotation; - - /// - /// Fixed decimal places (6 decimal places) - /// - [MiniExcelFormat("0.000000")] - public decimal FixedDecimal { get; set; } = fixedDecimal; - - /// - /// Phone number format - /// - [MiniExcelFormat("[<=9999999]###-####;(###) ###-####")] - public long PhoneNumber { get; set; } = phoneNumber; - - /// - /// Simple integer format that shows the number in its full length (no scientific notation) - /// - [MiniExcelFormat("#")] - public long VeryLongNumber { get; set; } = veryLongNumber; - - /// - /// Simple decimal format with 3 decimal places - /// - [MiniExcelFormat("0.000")] - public double CustomFormat { get; set; } = customFormat; - } - [Fact] public async Task DateTimeFormattingWithMiniExcelFormatAttributeTest() { @@ -1845,62 +1697,6 @@ public async Task DateTimeFormattingWithMiniExcelFormatAttributeTest() static DateTime GetDateTime(object value) => DateTime.FromOADate((double)value); } - /// - /// Test class with multiple date and time properties using MiniExcelFormatAttribute - /// to verify that date/time formatting is correctly applied during Excel export. - /// - private class DateTimeFormattingTestDto( - DateTime shortDate, - DateTime longDate, - DateTime dateWithTime, - TimeSpan timeOnly, - DateTime isoDateTime, - DateTime customDateTime, - DateTime monthYear) - { - /// - /// Short date format (mm/dd/yyyy) - /// - [MiniExcelFormat("mm/dd/yyyy")] - public DateTime ShortDate { get; set; } = shortDate; - - /// - /// Long date format (dddd, mmmm dd, yyyy) - /// - [MiniExcelFormat("dddd, mmmm dd, yyyy")] - public DateTime LongDate { get; set; } = longDate; - - /// - /// Date with time format (yyyy-mm-dd hh:mm:ss) - /// - [MiniExcelFormat("yyyy-mm-dd hh:mm:ss")] - public DateTime DateWithTime { get; set; } = dateWithTime; - - /// - /// Time only format ([h]:mm:ss) - /// - [MiniExcelFormat("[h]:mm:ss")] - public TimeSpan TimeOnly { get; set; } = timeOnly; - - /// - /// ISO 8601 datetime format (yyyy-mm-ddThh:mm:ss) - /// - [MiniExcelFormat("yyyy-mm-dd\"T\"hh:mm:ss")] - public DateTime IsoDateTime { get; set; } = isoDateTime; - - /// - /// Custom European format (dd.mm.yyyy hh:mm) - /// - [MiniExcelFormat("dd.mm.yyyy hh:mm")] - public DateTime CustomDateTime { get; set; } = customDateTime; - - /// - /// Month and year format (mmmm yyyy) - /// - [MiniExcelFormat("mmmm yyyy")] - public DateTime MonthYear { get; set; } = monthYear; - } - [Fact] public async Task InvalidSheetNameCharactersShouldThrow() { @@ -1919,21 +1715,6 @@ public async Task InvalidSheetNameCharactersShouldThrow() await Assert.ThrowsAsync(() => _excelExporter.AlterSheetAsync(ms3, "Sheet1", "Sheet*")); } - class LocalizationSupportDto(string firstName, string lastName, string address, int age) - { - [MiniExcelColumn(Name = nameof(FirstName), ResourceType = typeof(Localization), Width = 15)] - public string? FirstName { get; set; } = firstName; - - [MiniExcelColumn(Name = nameof(LastName), ResourceType = typeof(Localization), Width = 15)] - public string? LastName { get; set; } = lastName; - - [MiniExcelColumnName("Address", ResourceType = typeof(Localization))] - public string? Residency { get; set; } = address; - - [MiniExcelColumn(Name = nameof(Age), ResourceType = typeof(Localization), Width = 20)] - public int Age { get; set; } = age; - } - [Theory] [InlineData("")] [InlineData("it")] diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlTests.cs b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs similarity index 97% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlTests.cs rename to tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs index d619d02d..356c356e 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Main/MiniExcelOpenXmlTests.cs @@ -7,7 +7,7 @@ using FileHelper = MiniExcelLib.OpenXml.Tests.Utils.FileHelper; using Path = System.IO.Path; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.Main; public class MiniExcelOpenXmlTests(ITestOutputHelper output) { diff --git a/tests/MiniExcel.OpenXml.Tests/Main/Models.cs b/tests/MiniExcel.OpenXml.Tests/Main/Models.cs new file mode 100644 index 00000000..9d607630 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/Main/Models.cs @@ -0,0 +1,223 @@ +using MiniExcelLib.Tests.Common; + +namespace MiniExcelLib.OpenXml.Tests.Main; + +internal class AutoCheckType +{ + public Guid? @guid { get; set; } + public bool? @bool { get; set; } + public DateTime? datetime { get; set; } + public string? @string { get; set; } +} + +internal class CustomAttributesWihoutVaildPropertiesTestPoco +{ + [MiniExcelIgnore] + public string? Test3 { get; set; } + public string? Test5 { get; } + public string? Test6 { get; private set; } +} + +internal class Demo +{ + public string? Column1 { get; set; } + public decimal Column2 { get; set; } +} + +internal class DemoPocoHelloWorld +{ + public string? HelloWorld1 { get; set; } +} + +internal class SaveAsControlChracterVO +{ + public string? Test { get; set; } +} + +/// +/// Test class with multiple date and time properties using MiniExcelFormatAttribute +/// to verify that date/time formatting is correctly applied during Excel export. +/// +internal class DateTimeFormattingTestDto( + DateTime shortDate, + DateTime longDate, + DateTime dateWithTime, + TimeSpan timeOnly, + DateTime isoDateTime, + DateTime customDateTime, + DateTime monthYear) +{ + /// + /// Short date format (mm/dd/yyyy) + /// + [MiniExcelFormat("mm/dd/yyyy")] + public DateTime ShortDate { get; set; } = shortDate; + + /// + /// Long date format (dddd, mmmm dd, yyyy) + /// + [MiniExcelFormat("dddd, mmmm dd, yyyy")] + public DateTime LongDate { get; set; } = longDate; + + /// + /// Date with time format (yyyy-mm-dd hh:mm:ss) + /// + [MiniExcelFormat("yyyy-mm-dd hh:mm:ss")] + public DateTime DateWithTime { get; set; } = dateWithTime; + + /// + /// Time only format ([h]:mm:ss) + /// + [MiniExcelFormat("[h]:mm:ss")] + public TimeSpan TimeOnly { get; set; } = timeOnly; + + /// + /// ISO 8601 datetime format (yyyy-mm-ddThh:mm:ss) + /// + [MiniExcelFormat("yyyy-mm-dd\"T\"hh:mm:ss")] + public DateTime IsoDateTime { get; set; } = isoDateTime; + + /// + /// Custom European format (dd.mm.yyyy hh:mm) + /// + [MiniExcelFormat("dd.mm.yyyy hh:mm")] + public DateTime CustomDateTime { get; set; } = customDateTime; + + /// + /// Month and year format (mmmm yyyy) + /// + [MiniExcelFormat("mmmm yyyy")] + public DateTime MonthYear { get; set; } = monthYear; +} + + + /// + /// Test class with multiple numeric properties using MiniExcelFormatAttribute + /// to verify that formatting is correctly applied during Excel export. + /// +internal class NumericFormattingTestDto( + decimal currency, + decimal alignedCurrency, + decimal percentage, + double scientificNotation, + decimal fixedDecimal, + long phoneNumber, + long veryLongNumber, + double customFormat) +{ + + /// + /// Regular currency format with 2 decimal places + /// + [MiniExcelFormat("\"$\"#,##0.00")] + public decimal Currency { get; set; } = currency; + + /// + /// Currency format with 2 decimal places, parentheses for negatives + /// + [MiniExcelFormat("$#,##0.00_);($#,##0.00)")] + public decimal AlignedCurrency { get; set; } = alignedCurrency; + + /// + /// Percentage format with 0 decimal places + /// + [MiniExcelFormat("0%")] + public decimal Percentage { get; set; } = percentage; + + /// + /// Scientific notation format with 2 decimal places + /// + [MiniExcelFormat("0.00E+00")] + public double ScientificNotation { get; set; } = scientificNotation; + + [MiniExcelFormat("0.00E+00"), MiniExcelHidden] + public double ScientificNotationDuplicate { get; set; } = scientificNotation; + + /// + /// Fixed decimal places (6 decimal places) + /// + [MiniExcelFormat("0.000000")] + public decimal FixedDecimal { get; set; } = fixedDecimal; + + /// + /// Phone number format + /// + [MiniExcelFormat("[<=9999999]###-####;(###) ###-####")] + public long PhoneNumber { get; set; } = phoneNumber; + + /// + /// Simple integer format that shows the number in its full length (no scientific notation) + /// + [MiniExcelFormat("#")] + public long VeryLongNumber { get; set; } = veryLongNumber; + + /// + /// Simple decimal format with 3 decimal places + /// + [MiniExcelFormat("0.000")] + public double CustomFormat { get; set; } = customFormat; +} + +internal class UserAccount +{ + public Guid ID { get; set; } + public string? Name { get; set; } + public DateTime BoD { get; set; } + public int Age { get; set; } + public bool VIP { get; set; } + public decimal Points { get; set; } + public int IgnoredProperty => 1; +} + +internal class ExcelAttributeDemo +{ + [MiniExcelColumnName("Column1")] + public string? Test1 { get; set; } + [MiniExcelColumnName("Column2")] + public string? Test2 { get; set; } + [MiniExcelIgnore] + public string? Test3 { get; set; } + [MiniExcelColumnIndex("I")] // system will convert "I" to 8 index + public string? Test4 { get; set; } + public string? Test5 { get; } //wihout set will ignore + public string? Test6 { get; private set; } //un-public set will ignore + [MiniExcelColumnIndex(3)] // start with 0 + public string? Test7 { get; set; } +} + +internal class ExcelAttributeDemo2 +{ + [MiniExcelColumn(Name = "Column1")] + public string? Test1 { get; set; } + [MiniExcelColumn(Name = "Column2")] + public string? Test2 { get; set; } + [MiniExcelColumn(Ignore = true)] + public string? Test3 { get; set; } + [MiniExcelColumn(IndexName = "I")] // system will convert "I" to 8 index + public string? Test4 { get; set; } + public string? Test5 { get; } //wihout set will ignore + public string? Test6 { get; private set; } //un-public set will ignore + [MiniExcelColumn(Index = 3)] // start with 0 + public string? Test7 { get; set; } +} + +class LocalizationSupportDto(string firstName, string lastName, string address, int age) +{ + [MiniExcelColumn(Name = nameof(FirstName), ResourceType = typeof(Localization), Width = 15)] + public string? FirstName { get; set; } = firstName; + + [MiniExcelColumn(Name = nameof(LastName), ResourceType = typeof(Localization), Width = 15)] + public string? LastName { get; set; } = lastName; + + [MiniExcelColumnName("Address", ResourceType = typeof(Localization))] + public string? Residency { get; set; } = address; + + [MiniExcelColumn(Name = nameof(Age), ResourceType = typeof(Localization), Width = 20)] + public int Age { get; set; } = age; +} + +internal class SaveAsFileWithDimensionByICollectionTestType +{ + public string? A { get; set; } + public string? B { get; set; } +} diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs deleted file mode 100644 index f408a51a..00000000 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -using MiniExcelLib.Tests.Common.Utils; - -namespace MiniExcelLib.OpenXml.Tests; - -public class MiniExcelOpenXmlMultipleSheetAsyncTests -{ - private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); - - [Fact] - public async Task SpecifySheetNameQueryTest() - { - var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - { - var q = _excelImporter.QueryAsync(path, sheetName: "Sheet3").ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].A); - Assert.Equal(3, rows[0].B); - } - { - var q = _excelImporter.QueryAsync(path, sheetName: "Sheet2").ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(1, rows[0].A); - Assert.Equal(1, rows[0].B); - } - { - var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2, rows[0].A); - Assert.Equal(2, rows[0].B); - } - { - await Assert.ThrowsAsync(() => - { - _ = _excelImporter.QueryAsync(path, sheetName: "xxxx").ToBlockingEnumerable().ToList(); - return Task.CompletedTask; - }); - } - - await using var stream = File.OpenRead(path); - - { - var rows = _excelImporter.QueryAsync(stream, sheetName: "Sheet3").ToBlockingEnumerable().Cast>().ToList(); - Assert.Equal(5, rows.Count); - Assert.Equal(3d, rows[0]["A"]); - Assert.Equal(3d, rows[0]["B"]); - } - { - var rows = _excelImporter.QueryAsync(stream, sheetName: "Sheet2").ToBlockingEnumerable().Cast>().ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(1d, rows[0]["A"]); - Assert.Equal(1d, rows[0]["B"]); - } - { - var rows = _excelImporter.QueryAsync(stream, sheetName: "Sheet1").ToBlockingEnumerable().Cast>().ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2d, rows[0]["A"]); - Assert.Equal(2d, rows[0]["B"]); - } - { - var rows = _excelImporter.QueryAsync(stream, sheetName: "Sheet1").ToBlockingEnumerable().Cast>().ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2d, rows[0]["A"]); - Assert.Equal(2d, rows[0]["B"]); - } - } - - [Fact] - public async Task MultiSheetsQueryBasicTest() - { - var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - await using var stream = File.OpenRead(path); - _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet1").ToBlockingEnumerable(); - _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet2").ToBlockingEnumerable(); - _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet3").ToBlockingEnumerable(); - } - - [Fact] - public async Task MultiSheetsQueryTest() - { - var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - { - var sheetNames = await _excelImporter.GetSheetNamesAsync(path); - foreach (var sheetName in sheetNames) - { - _ = _excelImporter.QueryAsync(path, sheetName: sheetName).ToBlockingEnumerable(); - } - - Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); - } - - { - await using var stream = File.OpenRead(path); - var sheetNames = await _excelImporter.GetSheetNamesAsync(stream); - - Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); - foreach (var sheetName in sheetNames) - { - _ = _excelImporter.QueryAsync(stream, sheetName: sheetName).ToBlockingEnumerable().ToList(); - } - } - } -} \ No newline at end of file diff --git a/tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetAsyncTests.cs b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetAsyncTests.cs new file mode 100644 index 00000000..eaa469d3 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetAsyncTests.cs @@ -0,0 +1,86 @@ +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.OpenXml.Tests.MultipleSheets; + +public class MiniExcelOpenXmlMultipleSheetAsyncTests +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + + [Fact] + public async Task SpecifySheetNameQueryTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + var rows1 = await _excelImporter.QueryAsync(path, sheetName: "Sheet3").ToListAsync(); + Assert.Equal(5, rows1.Count); + Assert.Equal(3, rows1[0].A); + Assert.Equal(3, rows1[0].B); + + var rows2 = await _excelImporter.QueryAsync(path, sheetName: "Sheet2").ToListAsync(); + Assert.Equal(12, rows2.Count); + Assert.Equal(1, rows2[0].A); + Assert.Equal(1, rows2[0].B); + + var rows3 = await _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToListAsync(); + Assert.Equal(12, rows3.Count); + Assert.Equal(2, rows3[0].A); + Assert.Equal(2, rows3[0].B); + await Assert.ThrowsAsync(async () => await _excelImporter.QueryAsync(path, sheetName: "xxxx").ToListAsync()); + + await using var stream = File.OpenRead(path); + + var rows4 = await _excelImporter.QueryAsync(stream, sheetName: "Sheet3").Cast>().ToListAsync(); + Assert.Equal(5, rows4.Count); + Assert.Equal(3d, rows4[0]["A"]); + Assert.Equal(3d, rows4[0]["B"]); + + var rows5 = await _excelImporter.QueryAsync(stream, sheetName: "Sheet2").Cast>().ToListAsync(); + Assert.Equal(12, rows5.Count); + Assert.Equal(1d, rows5[0]["A"]); + Assert.Equal(1d, rows5[0]["B"]); + + var rows6 = await _excelImporter.QueryAsync(stream, sheetName: "Sheet1").Cast>().ToListAsync(); + Assert.Equal(12, rows6.Count); + Assert.Equal(2d, rows6[0]["A"]); + Assert.Equal(2d, rows6[0]["B"]); + + var rows7 = await _excelImporter.QueryAsync(stream, sheetName: "Sheet1").Cast>().ToListAsync(); + Assert.Equal(12, rows7.Count); + Assert.Equal(2d, rows7[0]["A"]); + Assert.Equal(2d, rows7[0]["B"]); + } + + [Fact] + public async Task MultiSheetsQueryBasicTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + await using var stream = File.OpenRead(path); + _ = await _excelImporter.QueryAsync(stream, sheetName: "Sheet1").ToListAsync(); + _ = await _excelImporter.QueryAsync(stream, sheetName: "Sheet2").ToListAsync(); + _ = await _excelImporter.QueryAsync(stream, sheetName: "Sheet3").ToListAsync(); + } + + [Fact] + public async Task MultiSheetsQueryTest() + { + var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + + var sheetNames1 = await _excelImporter.GetSheetNamesAsync(path); + foreach (var sheetName in sheetNames1) + { + _ = await _excelImporter.QueryAsync(path, sheetName: sheetName).ToListAsync(); + } + + Assert.Equal(["Sheet1", "Sheet2", "Sheet3"], sheetNames1); + + + await using var stream = File.OpenRead(path); + var sheetNames2 = await _excelImporter.GetSheetNamesAsync(stream); + + Assert.Equal(["Sheet1", "Sheet2", "Sheet3"], sheetNames2); + foreach (var sheetName in sheetNames2) + { + _ = await _excelImporter.QueryAsync(stream, sheetName: sheetName).ToListAsync(); + } + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetTests.cs b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetTests.cs similarity index 58% rename from tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetTests.cs rename to tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetTests.cs index d7de0b0f..300eea8c 100644 --- a/tests/MiniExcel.OpenXml.Tests/MiniExcelOpenXmlMultipleSheetTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/MiniExcelOpenXmlMultipleSheetTests.cs @@ -1,7 +1,7 @@ using MiniExcelLib.OpenXml.Models; using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLib.OpenXml.Tests; +namespace MiniExcelLib.OpenXml.Tests.MultipleSheets; public class MiniExcelOpenXmlMultipleSheetTests { @@ -11,52 +11,43 @@ public class MiniExcelOpenXmlMultipleSheetTests public void SpecifySheetNameQueryTest() { var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - { - var rows = _excelImporter.Query(path, sheetName: "Sheet3").ToList(); - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].A); - Assert.Equal(3, rows[0].B); - } - { - var rows = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(1, rows[0].A); - Assert.Equal(1, rows[0].B); - } - { - var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2, rows[0].A); - Assert.Equal(2, rows[0].B); - } - Assert.Throws(() => _excelImporter.Query(path, sheetName: "xxxx").ToList()); + var rows1 = _excelImporter.Query(path, sheetName: "Sheet3").ToList(); + Assert.Equal(5, rows1.Count); + Assert.Equal(3, rows1[0].A); + Assert.Equal(3, rows1[0].B); + + var rows2 = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); + Assert.Equal(12, rows2.Count); + Assert.Equal(1, rows2[0].A); + Assert.Equal(1, rows2[0].B); + + var rows3 = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); + Assert.Equal(12, rows3.Count); + Assert.Equal(2, rows3[0].A); + Assert.Equal(2, rows3[0].B); + Assert.Throws(() => _excelImporter.Query(path, sheetName: "xxxx").ToList()); using var stream = File.OpenRead(path); - { - var rows = _excelImporter.Query(stream, sheetName: "Sheet3").ToList(); - Assert.Equal(5, rows.Count); - Assert.Equal(3, rows[0].A); - Assert.Equal(3, rows[0].B); - } - { - var rows = _excelImporter.Query(stream, sheetName: "Sheet2").ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(1, rows[0].A); - Assert.Equal(1, rows[0].B); - } - { - var rows = _excelImporter.Query(stream, sheetName: "Sheet1").ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2, rows[0].A); - Assert.Equal(2, rows[0].B); - } - { - var rows = _excelImporter.Query(stream, sheetName: "Sheet1").ToList(); - Assert.Equal(12, rows.Count); - Assert.Equal(2, rows[0].A); - Assert.Equal(2, rows[0].B); - } + var rows4 = _excelImporter.Query(stream, sheetName: "Sheet3").ToList(); + Assert.Equal(5, rows4.Count); + Assert.Equal(3, rows4[0].A); + Assert.Equal(3, rows4[0].B); + + var rows5 = _excelImporter.Query(stream, sheetName: "Sheet2").ToList(); + Assert.Equal(12, rows5.Count); + Assert.Equal(1, rows5[0].A); + Assert.Equal(1, rows5[0].B); + + var rows6 = _excelImporter.Query(stream, sheetName: "Sheet1").ToList(); + Assert.Equal(12, rows6.Count); + Assert.Equal(2, rows6[0].A); + Assert.Equal(2, rows6[0].B); + + var rows7 = _excelImporter.Query(stream, sheetName: "Sheet1").ToList(); + Assert.Equal(12, rows7.Count); + Assert.Equal(2, rows7[0].A); + Assert.Equal(2, rows7[0].B); } [Fact] @@ -65,53 +56,35 @@ public void MultiSheetsQueryBasicTest() var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); using var stream = File.OpenRead(path); - _ = _excelImporter.Query(stream, sheetName: "Sheet1"); - _ = _excelImporter.Query(stream, sheetName: "Sheet2"); - _ = _excelImporter.Query(stream, sheetName: "Sheet3"); + _ = _excelImporter.Query(stream, sheetName: "Sheet1"); + _ = _excelImporter.Query(stream, sheetName: "Sheet2"); + _ = _excelImporter.Query(stream, sheetName: "Sheet3"); } [Fact] public void MultiSheetsQueryTest() { var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); + var sheetNames1 = _excelImporter.GetSheetNames(path).ToList(); + foreach (var sheetName in sheetNames1) { - var sheetNames = _excelImporter.GetSheetNames(path).ToList(); - foreach (var sheetName in sheetNames) - { - var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); - Assert.NotEmpty(rows); - } - - Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); + var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); + Assert.NotEmpty(rows); } - { - using var stream = File.OpenRead(path); - var sheetNames = _excelImporter.GetSheetNames(stream).ToList(); + Assert.Equal(["Sheet1", "Sheet2", "Sheet3"], sheetNames1); + + using var stream = File.OpenRead(path); + var sheetNames2 = _excelImporter.GetSheetNames(stream).ToList(); - Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); - foreach (var sheetName in sheetNames) - { - var rows = _excelImporter.Query(stream, sheetName: sheetName).ToList(); - Assert.NotEmpty(rows); - } + Assert.Equal(["Sheet1", "Sheet2", "Sheet3"], sheetNames2); + foreach (var sheetName in sheetNames2) + { + var rows = _excelImporter.Query(stream, sheetName: sheetName).ToList(); + Assert.NotEmpty(rows); } } - [MiniExcelSheet(Name = "Users")] - private class UserDto - { - public string? Name { get; set; } - public int Age { get; set; } - } - - [MiniExcelSheet(Name = "Departments", State = SheetState.Hidden)] - private class DepartmentDto - { - public string? ID { get; set; } - public string? Name { get; set; } - } - [Fact] public void ExcelSheetAttributeIsUsedWhenReadExcel() { @@ -191,38 +164,36 @@ public void DynamicSheetConfigurationIsUsedWhenReadExcel() public void ReadSheetVisibilityStateTest() { var path = PathHelper.GetFile("xlsx/TestMultiSheetWithHiddenSheet.xlsx"); - { - var sheetInfos = _excelImporter.GetSheetInformations(path).ToList(); - Assert.Collection(sheetInfos, - i => - { - Assert.Equal(0u, i.Index); - Assert.Equal(2u, i.Id); - Assert.Equal(SheetState.Visible, i.State); - Assert.Equal("Sheet2", i.Name); - }, - i => - { - Assert.Equal(1u, i.Index); - Assert.Equal(1u, i.Id); - Assert.Equal(SheetState.Visible, i.State); - Assert.Equal("Sheet1", i.Name); - }, - i => - { - Assert.Equal(2u, i.Index); - Assert.Equal(3u, i.Id); - Assert.Equal(SheetState.Visible, i.State); - Assert.Equal("Sheet3", i.Name); - }, - i => - { - Assert.Equal(3u, i.Index); - Assert.Equal(5u, i.Id); - Assert.Equal(SheetState.Hidden, i.State); - Assert.Equal("HiddenSheet4", i.Name); - }); - } + var sheetInfos = _excelImporter.GetSheetInformations(path).ToList(); + Assert.Collection(sheetInfos, + i => + { + Assert.Equal(0u, i.Index); + Assert.Equal(2u, i.Id); + Assert.Equal(SheetState.Visible, i.State); + Assert.Equal("Sheet2", i.Name); + }, + i => + { + Assert.Equal(1u, i.Index); + Assert.Equal(1u, i.Id); + Assert.Equal(SheetState.Visible, i.State); + Assert.Equal("Sheet1", i.Name); + }, + i => + { + Assert.Equal(2u, i.Index); + Assert.Equal(3u, i.Id); + Assert.Equal(SheetState.Visible, i.State); + Assert.Equal("Sheet3", i.Name); + }, + i => + { + Assert.Equal(3u, i.Index); + Assert.Equal(5u, i.Id); + Assert.Equal(SheetState.Hidden, i.State); + Assert.Equal("HiddenSheet4", i.Name); + }); } [Fact] @@ -284,4 +255,4 @@ public void WriteHiddenSheetTest() Assert.NotEmpty(rows); } } -} \ No newline at end of file +} diff --git a/tests/MiniExcel.OpenXml.Tests/MultipleSheets/Models.cs b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/Models.cs new file mode 100644 index 00000000..33d4f363 --- /dev/null +++ b/tests/MiniExcel.OpenXml.Tests/MultipleSheets/Models.cs @@ -0,0 +1,17 @@ +using MiniExcelLib.OpenXml.Models; + +namespace MiniExcelLib.OpenXml.Tests.MultipleSheets; + +[MiniExcelSheet(Name = "Users")] +internal class UserDto +{ + public string? Name { get; set; } + public int Age { get; set; } +} + +[MiniExcelSheet(Name = "Departments", State = SheetState.Hidden)] +internal class DepartmentDto +{ + public string? ID { get; set; } + public string? Name { get; set; } +} \ No newline at end of file diff --git a/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs b/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs index 860e2d4b..6477fabb 100644 --- a/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs +++ b/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs @@ -12,7 +12,7 @@ private AutoDeletingPath(string path) public static AutoDeletingPath Create(string path) => new(path); public static AutoDeletingPath Create(string path, string filename) => new(Path.Combine(path, filename)); public static AutoDeletingPath Create(ExcelType type = ExcelType.Xlsx) => Create( - Path.GetTempPath(), + Path.GetTempPath(), $"{Guid.NewGuid()}.{type.ToString().ToLowerInvariant()}"); public void Dispose()