diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d5b6c119..2a9cedad 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,6 +3,7 @@ name: MiniExcel Benchmarks on: workflow_dispatch: release: + branches: [master, v1.x-maintenance] types: [published] permissions: @@ -31,7 +32,7 @@ jobs: BenchmarkMode: Automatic BenchmarkSection: query - name: Renaming result file - run: mv MiniExcelLibs.Benchmarks.BenchmarkSections.QueryXlsxBenchmark-report-github.md query-benchmark.md + run: mv MiniExcelLib.Benchmarks.BenchmarkSections.QueryXlsxBenchmark-report-github.md query-benchmark.md working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results - name: Save benchmark results uses: actions/upload-artifact@v4 @@ -61,7 +62,7 @@ jobs: BenchmarkMode: Automatic BenchmarkSection: create - name: Renaming result file - run: mv MiniExcelLibs.Benchmarks.BenchmarkSections.CreateXlsxBenchmark-report-github.md create-benchmark.md + run: mv MiniExcelLib.Benchmarks.BenchmarkSections.CreateXlsxBenchmark-report-github.md create-benchmark.md working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results - name: Save benchmark results uses: actions/upload-artifact@v4 @@ -91,7 +92,7 @@ jobs: BenchmarkMode: Automatic BenchmarkSection: template - name: Renaming result file - run: mv MiniExcelLibs.Benchmarks.BenchmarkSections.TemplateXlsxBenchmark-report-github.md template-benchmark.md + run: mv MiniExcelLib.Benchmarks.BenchmarkSections.TemplateXlsxBenchmark-report-github.md template-benchmark.md working-directory: ./benchmarks/MiniExcel.Benchmarks/BenchmarkDotNet.Artifacts/results - name: Save benchmark results uses: actions/upload-artifact@v4 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a15f3d2a..25a1b9e9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -59,8 +59,9 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + +# - name: Autobuild +# uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -69,9 +70,8 @@ jobs: # and modify them (or add more) to build your code if your project # uses a compiled language - #- run: | - # make bootstrap - # make release + - name: Manual build + run: dotnet build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index a3b4eca6..bef3eae4 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -3,8 +3,7 @@ name: .NET on: pull_request: push: - branches: - - master + branches: [master, v1.x-maintenance] jobs: build: diff --git a/.gitignore b/.gitignore index c79821f5..d17a1326 100644 --- a/.gitignore +++ b/.gitignore @@ -401,5 +401,6 @@ FodyWeavers.xsd /BenchmarkDotNet.Artifacts /tests/MiniExcel.Tests.AspNetMvc/packages /TestTemplate -/tests/MiniExcelTests/TemplateOptimization -/samples/xlsx/Test_EnableWriteFilePath.xlsx \ No newline at end of file +/tests/MiniExcel.Tests/TemplateOptimization +/samples/xlsx/Test_EnableWriteFilePath.xlsx +/tests/MiniExcel.Core.Tests/TemplateOptimization/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e..d84996be 100644 --- a/LICENSE +++ b/LICENSE @@ -175,18 +175,7 @@ END OF TERMS AND CONDITIONS - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] + Copyright 2021 Wei Lin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MiniExcel.sln b/MiniExcel.sln deleted file mode 100644 index bebab6af..00000000 --- a/MiniExcel.sln +++ /dev/null @@ -1,71 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniExcelLibs", "src\MiniExcel\MiniExcelLibs.csproj", "{097903C9-1F81-4427-B4C8-530CB59687B8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs and setting", "Docs and setting", "{2AFABF2E-D6C3-4983-B43A-76ADA2BB2876}" - ProjectSection(SolutionItems) = preProject - .gitattributes = .gitattributes - .gitignore = .gitignore - appveyor.yml = appveyor.yml - .github\workflows\dotnet.yml = .github\workflows\dotnet.yml - LICENSE = LICENSE - README-NuGet.md = README-NuGet.md - README.md = README.md - README.zh-CN.md = README.zh-CN.md - README.zh-Hant.md = README.zh-Hant.md - .github\workflows\benchmark.yml = .github\workflows\benchmark.yml - .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml - docs\core_logic_diagram.drawio = docs\core_logic_diagram.drawio - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CC1E0601-AEC9-42D7-8F6A-3FB3939EED16}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{359A7094-3353-48F2-B3E1-FE9E59698318}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Releases", "Releases", "{3E370222-8E9E-45E8-8DCD-E5F41EE52A39}" - ProjectSection(SolutionItems) = preProject - docs\README.md = docs\README.md - docs\README.zh-CN.md = docs\README.zh-CN.md - docs\README.zh-Hant.md = docs\README.zh-Hant.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniExcelTests", "tests\MiniExcelTests\MiniExcelTests.csproj", "{77F2C86B-0F17-4370-AB38-A089F9DF4ED5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{77A8A169-168B-457F-AB5F-48F30D6BB33C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniExcel.Benchmarks", "benchmarks\MiniExcel.Benchmarks\MiniExcel.Benchmarks.csproj", "{F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {097903C9-1F81-4427-B4C8-530CB59687B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {097903C9-1F81-4427-B4C8-530CB59687B8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {097903C9-1F81-4427-B4C8-530CB59687B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {097903C9-1F81-4427-B4C8-530CB59687B8}.Release|Any CPU.Build.0 = Release|Any CPU - {77F2C86B-0F17-4370-AB38-A089F9DF4ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {77F2C86B-0F17-4370-AB38-A089F9DF4ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {77F2C86B-0F17-4370-AB38-A089F9DF4ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {77F2C86B-0F17-4370-AB38-A089F9DF4ED5}.Release|Any CPU.Build.0 = Release|Any CPU - {F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {097903C9-1F81-4427-B4C8-530CB59687B8} = {CC1E0601-AEC9-42D7-8F6A-3FB3939EED16} - {77F2C86B-0F17-4370-AB38-A089F9DF4ED5} = {359A7094-3353-48F2-B3E1-FE9E59698318} - {F1BDF4D7-F3C4-4114-82F6-EF81567DFBD8} = {77A8A169-168B-457F-AB5F-48F30D6BB33C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {51DF25DA-2DCB-4883-90FE-399DA950D4F2} - EndGlobalSection -EndGlobal diff --git a/MiniExcel.slnx b/MiniExcel.slnx new file mode 100644 index 00000000..92c62e02 --- /dev/null +++ b/MiniExcel.slnx @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs index 44809c0f..bbd60a07 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkBase.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.Benchmarks; +namespace MiniExcelLib.Benchmarks; public abstract class BenchmarkBase { diff --git a/benchmarks/MiniExcel.Benchmarks/Config.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkConfig.cs similarity index 85% rename from benchmarks/MiniExcel.Benchmarks/Config.cs rename to benchmarks/MiniExcel.Benchmarks/BenchmarkConfig.cs index 69334767..4128ccfd 100644 --- a/benchmarks/MiniExcel.Benchmarks/Config.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkConfig.cs @@ -7,16 +7,18 @@ using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; -namespace MiniExcelLibs.Benchmarks; +namespace MiniExcelLib.Benchmarks; -public class Config : ManualConfig +internal class BenchmarkConfig : ManualConfig { private const int Launches = 1; private const int Warmups = 3; private const int Unrolls = 3; private const int Iterations = 3; - public Config() + public static BenchmarkConfig Default => new(); + + private BenchmarkConfig() { AddLogger(ConsoleLogger.Default); diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateXlsxBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateExcelBenchmark.cs similarity index 87% rename from benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateXlsxBenchmark.cs rename to benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateExcelBenchmark.cs index 590da920..0ac47cd6 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateXlsxBenchmark.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/CreateExcelBenchmark.cs @@ -4,26 +4,31 @@ using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; -using MiniExcelLibs.Benchmarks.Utils; +using MiniExcelLib.Benchmarks.Utils; +using MiniExcelLib.Core; using NPOI.XSSF.UserModel; using OfficeOpenXml; -namespace MiniExcelLibs.Benchmarks.BenchmarkSections; +namespace MiniExcelLib.Benchmarks.BenchmarkSections; -public class CreateXlsxBenchmark : BenchmarkBase +public class CreateExcelBenchmark : BenchmarkBase { + private OpenXmlExporter _exporter; + [GlobalSetup] public void SetUp() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + _exporter = MiniExcel.Exporters.GetOpenXmlExporter(); } [Benchmark(Description = "MiniExcel Create Xlsx")] public void MiniExcelCreateTest() { using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.FilePath, GetValue()); + _exporter.Export(path.FilePath, GetValue()); } [Benchmark(Description = "ClosedXml Create Xlsx")] @@ -72,10 +77,9 @@ public void NPOICreateTest() row.CreateCell(9).SetCellValue(item.Column10); i++; } - using (var fs = File.Create(path.FilePath)) - { - wb.Write(fs); - } + + using var fs = File.Create(path.FilePath); + wb.Write(fs); } [Benchmark(Description = "OpenXmlSdk Create Xlsx by DOM mode")] diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs similarity index 90% rename from benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs rename to benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs index f2f9a1a5..e0e18a30 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryExcelBenchmark.cs @@ -4,30 +4,35 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using ExcelDataReader; +using MiniExcelLib.Core; using NPOI.XSSF.UserModel; using OfficeOpenXml; -namespace MiniExcelLibs.Benchmarks.BenchmarkSections; +namespace MiniExcelLib.Benchmarks.BenchmarkSections; -public class QueryXlsxBenchmark : BenchmarkBase +public class QueryExcelBenchmark : BenchmarkBase { + private OpenXmlImporter _importer; + [GlobalSetup] public void SetUp() { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + _importer = MiniExcel.Importers.GetOpenXmlImporter(); } [Benchmark(Description = "MiniExcel QueryFirst")] public void MiniExcel_QueryFirst_Test() { - _ = MiniExcel.Query(FilePath).First(); + _ = _importer.Query(FilePath).First(); } [Benchmark(Description = "MiniExcel Query")] public void MiniExcel_Query() { - foreach (var _ in MiniExcel.Query(FilePath)) { } + foreach (var _ in _importer.Query(FilePath)) { } } [Benchmark(Description = "ExcelDataReader QueryFirst")] diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateXlsxBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs similarity index 73% rename from benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateXlsxBenchmark.cs rename to benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs index 65eb5a53..c4fe16b7 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateXlsxBenchmark.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/TemplateExcelBenchmark.cs @@ -1,11 +1,20 @@ using BenchmarkDotNet.Attributes; using ClosedXML.Report; -using MiniExcelLibs.Benchmarks.Utils; +using MiniExcelLib.Benchmarks.Utils; +using MiniExcelLib.Core; -namespace MiniExcelLibs.Benchmarks.BenchmarkSections; +namespace MiniExcelLib.Benchmarks.BenchmarkSections; -public class TemplateXlsxBenchmark : BenchmarkBase +public class TemplateExcelBenchmark : BenchmarkBase { + private OpenXmlTemplater _templater; + + [GlobalSetup] + public void Setup() + { + _templater = MiniExcel.Templaters.GetOpenXmlTemplater(); + } + [Benchmark(Description = "MiniExcel Template Generate")] public void MiniExcel_Template_Generate_Test() { @@ -22,7 +31,7 @@ public void MiniExcel_Template_Generate_Test() }) }; - MiniExcel.SaveAsByTemplate(path.FilePath, templatePath, value); + _templater.ApplyTemplate(path.FilePath, templatePath, value); } [Benchmark(Description = "ClosedXml.Report Template Generate")] diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/XlsxAsyncBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/XlsxAsyncBenchmark.cs index 5c2044a9..ed327bfa 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/XlsxAsyncBenchmark.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/XlsxAsyncBenchmark.cs @@ -1,17 +1,28 @@ using BenchmarkDotNet.Attributes; -using MiniExcelLibs.Benchmarks.Utils; +using MiniExcelLib.Benchmarks.Utils; +using MiniExcelLib.Core; -namespace MiniExcelLibs.Benchmarks.BenchmarkSections; +namespace MiniExcelLib.Benchmarks.BenchmarkSections; public class XlsxAsyncBenchmark : BenchmarkBase { + private OpenXmlExporter _exporter; + private OpenXmlTemplater _templater; + + [GlobalSetup] + public void Setup() + { + _exporter = MiniExcel.Exporters.GetOpenXmlExporter(); + _templater = MiniExcel.Templaters.GetOpenXmlTemplater(); + } + [Benchmark(Description = "MiniExcel Create Xlsx Async")] public async Task MiniExcelCreateAsyncTest() { using var path = AutoDeletingPath.Create(); - using var stream = File.Create(path.FilePath); + await using var stream = File.Create(path.FilePath); - await stream.SaveAsAsync(GetValue()); + await _exporter.ExportAsync(stream, GetValue()); } [Benchmark(Description = "MiniExcel Generate Template Async")] @@ -30,6 +41,6 @@ public async Task MiniExcel_Template_Generate_Async_Test() }) }; - await MiniExcel.SaveAsByTemplateAsync(path.FilePath, templatePath, value); + await _templater.ApplyTemplateAsync(path.FilePath, templatePath, value); } } diff --git a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj index 0d2a9f57..f2dcfd17 100644 --- a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj +++ b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj @@ -6,7 +6,7 @@ enable enable latest - MiniExcelLibs.Benchmarks + MiniExcelLib.Benchmarks $(NoWarn);CA2000;CA2007 @@ -35,7 +35,7 @@ - + diff --git a/benchmarks/MiniExcel.Benchmarks/Program.cs b/benchmarks/MiniExcel.Benchmarks/Program.cs index 792bcab9..8dddb5e7 100644 --- a/benchmarks/MiniExcel.Benchmarks/Program.cs +++ b/benchmarks/MiniExcel.Benchmarks/Program.cs @@ -1,28 +1,28 @@ using BenchmarkDotNet.Running; -using MiniExcelLibs.Benchmarks; -using MiniExcelLibs.Benchmarks.BenchmarkSections; +using MiniExcelLib.Benchmarks; +using MiniExcelLib.Benchmarks.BenchmarkSections; if (Environment.GetEnvironmentVariable("BenchmarkMode") == "Automatic") { var section = Environment.GetEnvironmentVariable("BenchmarkSection"); var benchmark = section?.ToLowerInvariant().Trim() switch { - "query" => typeof(QueryXlsxBenchmark), - "create" => typeof(CreateXlsxBenchmark), - "template" => typeof(TemplateXlsxBenchmark), + "query" => typeof(QueryExcelBenchmark), + "create" => typeof(CreateExcelBenchmark), + "template" => typeof(TemplateExcelBenchmark), _ => throw new ArgumentException($"Benchmark section {section} does not exist") }; - BenchmarkRunner.Run(benchmark, new Config(), args); + BenchmarkRunner.Run(benchmark, BenchmarkConfig.Default, args); } else { BenchmarkSwitcher .FromTypes( [ - typeof(QueryXlsxBenchmark), - typeof(CreateXlsxBenchmark), - typeof(TemplateXlsxBenchmark) + typeof(QueryExcelBenchmark), + typeof(CreateExcelBenchmark), + typeof(TemplateExcelBenchmark) ]) - .Run(args, new Config()); + .Run(args, BenchmarkConfig.Default); } \ No newline at end of file diff --git a/benchmarks/MiniExcel.Benchmarks/Utils/AutoDeletingPath.cs b/benchmarks/MiniExcel.Benchmarks/Utils/AutoDeletingPath.cs index 0ef13df7..44004c2d 100644 --- a/benchmarks/MiniExcel.Benchmarks/Utils/AutoDeletingPath.cs +++ b/benchmarks/MiniExcel.Benchmarks/Utils/AutoDeletingPath.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.Benchmarks.Utils; +namespace MiniExcelLib.Benchmarks.Utils; internal class AutoDeletingPath : IDisposable { diff --git a/benchmarks/MiniExcel.Benchmarks/Utils/Extensions.cs b/benchmarks/MiniExcel.Benchmarks/Utils/Extensions.cs index fdaf3c32..5ff7dd46 100644 --- a/benchmarks/MiniExcel.Benchmarks/Utils/Extensions.cs +++ b/benchmarks/MiniExcel.Benchmarks/Utils/Extensions.cs @@ -1,6 +1,6 @@ using DocumentFormat.OpenXml.Spreadsheet; -namespace MiniExcelLibs.Benchmarks.Utils; +namespace MiniExcelLib.Benchmarks.Utils; internal static class Extensions { diff --git a/src/MiniExcel/miniexcel.publickey b/miniexcel.publickey similarity index 100% rename from src/MiniExcel/miniexcel.publickey rename to miniexcel.publickey diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..7c777d29 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,41 @@ + + + + netstandard2.0;net8.0;net10.0 + 2.0.0-preview.1 + enable + enable + 13 + + + + MiniExcel + Mini-Software + excel;xlsx;csv;micro-helper;mini;openxml;helper; + Fast, Low-Memory, Easy Excel .NET processing tool for importing, exporting and templating spreadsheets + Github : https://github.com/mini-software/MiniExcel + Gitee : https://gitee.com/dotnetchina/MiniExcel + Issues : https://github.com/mini-software/MiniExcel/issues + Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true + Wei Lin, Michele Bastione, PING-HSIU SHIH, Amos(izanhzh), eynarhaji, Mini-Software team + Wei Lin, 2021 onwards + en + https://raw.githubusercontent.com/mini-software/MiniExcel/master/LICENSE + Apache-2.0 + https://github.com/mini-software/MiniExcel + https://github.com/mini-software/MiniExcel + Github + icon.png + Please Check [Release Notes](https://github.com/mini-software/MiniExcel/tree/master/docs) + true + true + snupkg + README.md + + + + True + ..\miniexcel.snk + + + \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 00000000..bb48b5d7 --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/MiniExcel.Core/Abstractions/IInputValueExtractor.cs b/src/MiniExcel.Core/Abstractions/IInputValueExtractor.cs new file mode 100644 index 00000000..f0409064 --- /dev/null +++ b/src/MiniExcel.Core/Abstractions/IInputValueExtractor.cs @@ -0,0 +1,6 @@ +namespace MiniExcelLib.Core.Abstractions; + +public interface IInputValueExtractor +{ + IDictionary ToValueDictionary(object valueObject); +} \ No newline at end of file diff --git a/src/MiniExcel/IMiniExcelDataReader.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelDataReader.cs similarity index 62% rename from src/MiniExcel/IMiniExcelDataReader.cs rename to src/MiniExcel.Core/Abstractions/IMiniExcelDataReader.cs index 253374c7..a36aec05 100644 --- a/src/MiniExcel/IMiniExcelDataReader.cs +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelDataReader.cs @@ -1,9 +1,4 @@ -using System; -using System.Data; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.Abstractions; public interface IMiniExcelDataReader : IDataReader #if NET8_0_OR_GREATER @@ -12,7 +7,7 @@ public interface IMiniExcelDataReader : IDataReader { Task CloseAsync(); Task GetNameAsync(int i, CancellationToken cancellationToken = default); - Task GetValueAsync(int i, CancellationToken cancellationToken = default); + Task GetValueAsync(int i, CancellationToken cancellationToken = default); Task NextResultAsync(CancellationToken cancellationToken = default); Task ReadAsync(CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/MiniExcel/IExcelReader.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelReader.cs similarity index 65% rename from src/MiniExcel/IExcelReader.cs rename to src/MiniExcel.Core/Abstractions/IMiniExcelReader.cs index 7f106895..3760c139 100644 --- a/src/MiniExcel/IExcelReader.cs +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelReader.cs @@ -1,27 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using Zomp.SyncMethodGenerator; +namespace MiniExcelLib.Core.Abstractions; -namespace MiniExcelLibs; - -internal partial interface IExcelReader : IDisposable +public partial interface IMiniExcelReader : IDisposable { [CreateSyncVersion] IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, CancellationToken cancellationToken = default); [CreateSyncVersion] - IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool mapHeaderAsData, CancellationToken cancellationToken = default) where T : class, new(); [CreateSyncVersion] IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, string startCell, string endCell, CancellationToken cancellationToken = default); [CreateSyncVersion] - IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new(); [CreateSyncVersion] IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default); [CreateSyncVersion] - IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new(); } \ No newline at end of file diff --git a/src/MiniExcel/IExcelTemplate.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelTemplate.cs similarity index 67% rename from src/MiniExcel/IExcelTemplate.cs rename to src/MiniExcel.Core/Abstractions/IMiniExcelTemplate.cs index c5ec65a6..57a3eb8f 100644 --- a/src/MiniExcel/IExcelTemplate.cs +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelTemplate.cs @@ -1,10 +1,6 @@ -using System.Threading; -using System.Threading.Tasks; -using Zomp.SyncMethodGenerator; +namespace MiniExcelLib.Core.Abstractions; -namespace MiniExcelLibs; - -internal partial interface IExcelTemplate +public partial interface IMiniExcelTemplate { [CreateSyncVersion] Task SaveAsByTemplateAsync(string templatePath, object value, CancellationToken cancellationToken = default); @@ -12,6 +8,9 @@ internal partial interface IExcelTemplate [CreateSyncVersion] Task SaveAsByTemplateAsync(byte[] templateBytes, object value, CancellationToken cancellationToken = default); + [CreateSyncVersion] + Task SaveAsByTemplateAsync(Stream templateStream, object value, CancellationToken cancellationToken = default); + [CreateSyncVersion] Task MergeSameCellsAsync(string path, CancellationToken cancellationToken = default); diff --git a/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapter.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapter.cs new file mode 100644 index 00000000..1fb24a85 --- /dev/null +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapter.cs @@ -0,0 +1,17 @@ +using MiniExcelLib.Core.Reflection; + +namespace MiniExcelLib.Core.Abstractions; + +public interface IMiniExcelWriteAdapter +{ + bool TryGetKnownCount(out int count); + List? GetColumns(); + IEnumerable> GetRows(List props, CancellationToken cancellationToken = default); +} + +public readonly struct CellWriteInfo(object? value, int cellIndex, MiniExcelColumnInfo prop) +{ + public object? Value { get; } = value; + public int CellIndex { get; } = cellIndex; + public MiniExcelColumnInfo Prop { get; } = prop; +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapterAsync.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapterAsync.cs new file mode 100644 index 00000000..03865a63 --- /dev/null +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelWriteAdapterAsync.cs @@ -0,0 +1,9 @@ +using MiniExcelLib.Core.Reflection; + +namespace MiniExcelLib.Core.Abstractions; + +public interface IMiniExcelWriteAdapterAsync +{ + Task?> GetColumnsAsync(); + IAsyncEnumerable> GetRowsAsync(List props, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/MiniExcel/IExcelWriter.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs similarity index 58% rename from src/MiniExcel/IExcelWriter.cs rename to src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs index 2b7e59c1..b80f88dd 100644 --- a/src/MiniExcel/IExcelWriter.cs +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs @@ -1,10 +1,6 @@ -using System.Threading; -using System.Threading.Tasks; -using Zomp.SyncMethodGenerator; +namespace MiniExcelLib.Core.Abstractions; -namespace MiniExcelLibs; - -internal partial interface IExcelWriter +public partial interface IMiniExcelWriter { [CreateSyncVersion] Task SaveAsAsync(CancellationToken cancellationToken = default); diff --git a/src/MiniExcel.Core/Api/OpenXmlExporter.cs b/src/MiniExcel.Core/Api/OpenXmlExporter.cs new file mode 100644 index 00000000..49f47ee6 --- /dev/null +++ b/src/MiniExcel.Core/Api/OpenXmlExporter.cs @@ -0,0 +1,79 @@ +using MiniExcelLib.Core.OpenXml.Picture; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Core; + +public sealed partial class OpenXmlExporter +{ + internal OpenXmlExporter() { } + + + [CreateSyncVersion] + public async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) + { + using var stream = File.Open(path, FileMode.OpenOrCreate); + await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) + { + await MiniExcelPictureImplement.AddPictureAsync(excelStream, cancellationToken, images).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task InsertSheetAsync(string path, object value, string? sheetName = "Sheet1", bool printHeader = true, bool overwriteSheet = false, OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) + throw new NotSupportedException("MiniExcel's InsertExcelSheet does not support the .xlsm format"); + + if (!File.Exists(path)) + { + var rowsWritten = await ExportAsync(path, value, printHeader, sheetName, configuration: configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + return rowsWritten.FirstOrDefault(); + } + + using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan); + return await InsertSheetAsync(stream, value, sheetName, printHeader, overwriteSheet, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task InsertSheetAsync(Stream stream, object value, string? sheetName = "Sheet1", + bool printHeader = true, bool overwriteSheet = false, OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + stream.Seek(0, SeekOrigin.End); + configuration ??= new OpenXmlConfiguration { FastMode = true }; + + var writer = await OpenXmlWriter + .CreateAsync(stream, value, sheetName, printHeader, configuration, cancellationToken) + .ConfigureAwait(false); + + return await writer.InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ExportAsync(string path, object value, bool printHeader = true, + string? sheetName = "Sheet1", bool overwriteFile = false, OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) + throw new NotSupportedException("MiniExcel's ExportExcel does not support the .xlsm format"); + + var filePath = path.EndsWith(".xlsx", StringComparison.InvariantCultureIgnoreCase) ? path : $"{path}.xlsx" ; + + using var stream = overwriteFile ? File.Create(filePath) : new FileStream(filePath, FileMode.CreateNew); + return await ExportAsync(stream, value, printHeader, sheetName, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ExportAsync(Stream stream, object value, bool printHeader = true, string? sheetName = "Sheet1", + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var writer = await OpenXmlWriter + .CreateAsync(stream, value, sheetName, printHeader, configuration, cancellationToken) + .ConfigureAwait(false); + + return await writer.SaveAsAsync(cancellationToken).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Api/OpenXmlImporter.cs b/src/MiniExcel.Core/Api/OpenXmlImporter.cs new file mode 100644 index 00000000..8b30d3f7 --- /dev/null +++ b/src/MiniExcel.Core/Api/OpenXmlImporter.cs @@ -0,0 +1,323 @@ +using System.Dynamic; +using MiniExcelLib.Core.DataReader; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Core.OpenXml.Zip; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Core; + +public sealed partial class OpenXmlImporter +{ + internal OpenXmlImporter() { } + + #region Query + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(string path, string? sheetName = null, + string startCell = "A1", bool treatHeaderAsData = false, OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + using var stream = FileHelper.OpenSharedRead(path); + + var query = QueryAsync(stream, sheetName, startCell, treatHeaderAsData, configuration, cancellationToken); + + //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable + await foreach (var item in query.ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(Stream stream, string? sheetName = null, + string startCell = "A1", bool treatHeaderAsData = false, OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + using var reader = await OpenXmlReader.CreateAsync(stream, configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in reader.QueryAsync(sheetName, startCell, treatHeaderAsData, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await OpenXmlReader.CreateAsync(stream, configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryAsync(useHeaderRow, sheetName, startCell, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate(seed: GetNewExpandoObject(), func: AddPairToDict); + } + + #endregion + + #region Query Range + + /// + /// Extract the given range。 Only uppercase letters are effective。 + /// e.g. + /// MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3") + /// A2 represents the second row of column A, C3 represents the third row of column C + /// If you don't want to restrict rows, just don't include numbers + /// + /// + [CreateSyncVersion] + public async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", string endCell = "", OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, startCell, endCell, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryRangeAsync(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", string endCell = "", OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await OpenXmlReader.CreateAsync(stream, configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startCell, endCell, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate(seed: GetNewExpandoObject(), func: AddPairToDict); + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, + string? sheetName = null, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, + int? endColumnIndex = null, OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryRangeAsync(Stream stream, bool useHeaderRow = false, + string? sheetName = null, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, + int? endColumnIndex = null, OpenXmlConfiguration? configuration = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await OpenXmlReader.CreateAsync(stream, configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate(seed: GetNewExpandoObject(), func: AddPairToDict); + } + + #endregion + + #region Query As DataTable + + /// + /// QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + public async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await QueryAsDataTableAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false); + } + + /// + /// QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + public async Task QueryAsDataTableAsync(Stream stream, bool useHeaderRow = true, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + /*Issue #279*/ + sheetName ??= (await GetSheetNamesAsync(stream, configuration, cancellationToken).ConfigureAwait(false)).First(); + + var dt = new DataTable(sheetName); + var first = true; + using var reader = await OpenXmlReader.CreateAsync(stream, configuration, cancellationToken).ConfigureAwait(false); + var rows = reader.QueryAsync(false, sheetName, startCell, cancellationToken); + + var columnDict = new Dictionary(); + await foreach (var row in rows.ConfigureAwait(false)) + { + if (first) + { + foreach (var entry in row) + { + cancellationToken.ThrowIfCancellationRequested(); + + var columnName = useHeaderRow ? entry.Value?.ToString() : entry.Key; + if (!string.IsNullOrWhiteSpace(columnName)) // avoid #298 : Column '' does not belong to table + { + var column = new DataColumn(columnName, typeof(object)) { Caption = columnName }; + dt.Columns.Add(column); + columnDict.Add(entry.Key, columnName); //same column name throw exception??? + } + } + + dt.BeginLoadData(); + first = false; + if (useHeaderRow) + { + continue; + } + } + + var newRow = dt.NewRow(); + foreach (var entry in columnDict) + { + newRow[entry.Value] = row[entry.Key]; //TODO: optimize not using string key + } + + dt.Rows.Add(newRow); + } + + dt.EndLoadData(); + return dt; + } + + #endregion + + #region Sheet Info + + [CreateSyncVersion] + public async Task> GetSheetNamesAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetSheetNamesAsync(Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + config ??= OpenXmlConfiguration.Default; + + using var archive = new OpenXmlZip(stream, leaveOpen: true); + + using var reader = await OpenXmlReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); + var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); + + return rels?.Select(s => s.Name).ToList() ?? []; + } + + [CreateSyncVersion] + public async Task> GetSheetInformationsAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetSheetInformationsAsync(Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + config ??= OpenXmlConfiguration.Default; + + using var archive = new OpenXmlZip(stream); + using var reader = await OpenXmlReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); + var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); + + return rels?.Select((s, i) => s.ToSheetInfo((uint)i)).ToList() ?? []; + } + + [CreateSyncVersion] + public async Task> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetSheetDimensionsAsync(Stream stream, CancellationToken cancellationToken = default) + { + using var reader = await OpenXmlReader.CreateAsync(stream, null, cancellationToken: cancellationToken).ConfigureAwait(false); + return await reader.GetDimensionsAsync(cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetColumnNamesAsync(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetColumnNamesAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetColumnNamesAsync(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { +#pragma warning disable CA2007 // We need to assign the AsyncEnumerator before we can call ConfigureAwait on it + await using var enumerator = QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken).GetAsyncEnumerator(cancellationToken); +#pragma warning restore CA2007 + + _ = enumerator.ConfigureAwait(false); + if (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + return (enumerator.Current as IDictionary)?.Keys ?? []; + } + + return []; + } + + #endregion + + #region DataReader + + public MiniExcelDataReader GetDataReader(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null) + { + var stream = FileHelper.OpenSharedRead(path); + var values = Query(stream, useHeaderRow, sheetName, startCell, configuration).Cast>(); + + return MiniExcelDataReader.Create(stream, values); + } + + public MiniExcelDataReader GetDataReader(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null) + { + var values = Query(stream, useHeaderRow, sheetName, startCell, configuration).Cast>(); + return MiniExcelDataReader.Create(stream, values); + } + + public async Task GetAsyncDataReader(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + var stream = FileHelper.OpenSharedRead(path); + var values = QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken); + + return await MiniExcelAsyncDataReader.CreateAsync(stream, CastAsync(values, cancellationToken)).ConfigureAwait(false); + } + + public async Task GetAsyncDataReader(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", OpenXmlConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + var values = QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration, cancellationToken); + return await MiniExcelAsyncDataReader.CreateAsync(stream, CastAsync(values, cancellationToken)).ConfigureAwait(false); + } + + #endregion + + private static IDictionary GetNewExpandoObject() => new ExpandoObject(); + private static IDictionary AddPairToDict(IDictionary dict, KeyValuePair pair) + { + dict.Add(pair); + return dict; + } + + private static async IAsyncEnumerable> CastAsync(IAsyncEnumerable enumerable, CancellationToken cancellationToken = default) + { + await foreach (var item in enumerable.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (item is IDictionary dict) + yield return dict; + } + } +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Api/OpenXmlTemplater.cs b/src/MiniExcel.Core/Api/OpenXmlTemplater.cs new file mode 100644 index 00000000..30bb8bcb --- /dev/null +++ b/src/MiniExcel.Core/Api/OpenXmlTemplater.cs @@ -0,0 +1,94 @@ +using MiniExcelLib.Core.OpenXml.Templates; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Core; + +public sealed partial class OpenXmlTemplater +{ + internal OpenXmlTemplater() { } + + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(string path, string templatePath, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(path); + await ApplyTemplateAsync(stream, templatePath, value, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(string path, Stream templateStream, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(path); + var template = GetOpenXmlTemplate(stream, configuration); + await template.SaveAsByTemplateAsync(templateStream, value, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(Stream stream, string templatePath, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var template = GetOpenXmlTemplate(stream, configuration); + await template.SaveAsByTemplateAsync(templatePath, value, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(Stream stream, Stream templateStream, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var template = GetOpenXmlTemplate(stream, configuration); + await template.SaveAsByTemplateAsync(templateStream, value, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(string path, byte[] templateBytes, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(path); + await ApplyTemplateAsync(stream, templateBytes, value, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ApplyTemplateAsync(Stream stream, byte[] templateBytes, object value, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var template = GetOpenXmlTemplate(stream, configuration); + await template.SaveAsByTemplateAsync(templateBytes, value, cancellationToken).ConfigureAwait(false); + } + + #region Merge Cells + + [CreateSyncVersion] + public async Task MergeSameCellsAsync(string mergedFilePath, string path, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(mergedFilePath); + await MergeSameCellsAsync(stream, path, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task MergeSameCellsAsync(Stream stream, string path, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var template = GetOpenXmlTemplate(stream, configuration); + await template.MergeSameCellsAsync(path, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task MergeSameCellsAsync(Stream stream, byte[] file, + OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var template = GetOpenXmlTemplate(stream, configuration); + await template.MergeSameCellsAsync(file, cancellationToken).ConfigureAwait(false); + } + + + private static OpenXmlTemplate GetOpenXmlTemplate(Stream stream, OpenXmlConfiguration? configuration) + { + var valueExtractor = new OpenXmlValueExtractor(); + return new OpenXmlTemplate(stream, configuration, valueExtractor); + } + + #endregion +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs similarity index 83% rename from src/MiniExcel/Attributes/ExcelColumnAttribute.cs rename to src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs index 3ce2977e..2d6098a7 100644 --- a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs +++ b/src/MiniExcel.Core/Attributes/MiniExcelColumnAttribute.cs @@ -1,10 +1,7 @@ -using System; -using MiniExcelLibs.Utils; - -namespace MiniExcelLibs.Attributes; +namespace MiniExcelLib.Core.Attributes; [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelColumnAttribute : Attribute +public class MiniExcelColumnAttribute : Attribute { private int _index = -1; private string? _xName; @@ -42,7 +39,7 @@ private void Init(int index, string? columnName = null) public enum ColumnType { Value, Formula } -public class DynamicExcelColumn : ExcelColumnAttribute +public class DynamicExcelColumn : MiniExcelColumnAttribute { public string Key { get; set; } public Func CustomFormatter { get; set; } diff --git a/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelColumnIndexAttribute.cs similarity index 61% rename from src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs rename to src/MiniExcel.Core/Attributes/MiniExcelColumnIndexAttribute.cs index 0bdad2fd..b0033b63 100644 --- a/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs +++ b/src/MiniExcel.Core/Attributes/MiniExcelColumnIndexAttribute.cs @@ -1,15 +1,12 @@ -using System; -using MiniExcelLibs.Utils; - -namespace MiniExcelLibs.Attributes; +namespace MiniExcelLib.Core.Attributes; [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelColumnIndexAttribute : Attribute +public class MiniExcelColumnIndexAttribute : Attribute { public int ExcelColumnIndex { get; set; } internal string? ExcelXName { get; set; } - public ExcelColumnIndexAttribute(string columnName) => Init(ColumnHelper.GetColumnIndex(columnName), columnName); - public ExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex); + public MiniExcelColumnIndexAttribute(string columnName) => Init(ColumnHelper.GetColumnIndex(columnName), columnName); + public MiniExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex); private void Init(int columnIndex, string? columnName = null) { diff --git a/src/MiniExcel.Core/Attributes/MiniExcelColumnNameAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelColumnNameAttribute.cs new file mode 100644 index 00000000..c1a3b657 --- /dev/null +++ b/src/MiniExcel.Core/Attributes/MiniExcelColumnNameAttribute.cs @@ -0,0 +1,8 @@ +namespace MiniExcelLib.Core.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class MiniExcelColumnNameAttribute(string columnName, string[]? aliases = null) : Attribute +{ + public string ExcelColumnName { get; set; } = columnName; + public string[] Aliases { get; set; } = aliases ?? []; +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Attributes/MiniExcelColumnWidthAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelColumnWidthAttribute.cs new file mode 100644 index 00000000..38c1a9a2 --- /dev/null +++ b/src/MiniExcel.Core/Attributes/MiniExcelColumnWidthAttribute.cs @@ -0,0 +1,7 @@ +namespace MiniExcelLib.Core.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class MiniExcelColumnWidthAttribute(double columnWidth) : Attribute +{ + public double ExcelColumnWidth { get; set; } = columnWidth; +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelFormatAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelFormatAttribute.cs similarity index 50% rename from src/MiniExcel/Attributes/ExcelFormatAttribute.cs rename to src/MiniExcel.Core/Attributes/MiniExcelFormatAttribute.cs index a2aaea4b..6df4f1ae 100644 --- a/src/MiniExcel/Attributes/ExcelFormatAttribute.cs +++ b/src/MiniExcel.Core/Attributes/MiniExcelFormatAttribute.cs @@ -1,9 +1,7 @@ -using System; - -namespace MiniExcelLibs.Attributes; +namespace MiniExcelLib.Core.Attributes; [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelFormatAttribute(string format) : Attribute +public class MiniExcelFormatAttribute(string format) : Attribute { public string Format { get; set; } = format; } \ No newline at end of file diff --git a/src/MiniExcel.Core/Attributes/MiniExcelIgnoreAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelIgnoreAttribute.cs new file mode 100644 index 00000000..20aceb7e --- /dev/null +++ b/src/MiniExcel.Core/Attributes/MiniExcelIgnoreAttribute.cs @@ -0,0 +1,7 @@ +namespace MiniExcelLib.Core.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class MiniExcelIgnoreAttribute(bool ignore = true) : Attribute +{ + public bool Ignore { get; set; } = ignore; +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Attributes/MiniExcelSheetAttribute.cs b/src/MiniExcel.Core/Attributes/MiniExcelSheetAttribute.cs new file mode 100644 index 00000000..182c4ffa --- /dev/null +++ b/src/MiniExcel.Core/Attributes/MiniExcelSheetAttribute.cs @@ -0,0 +1,15 @@ +using MiniExcelLib.Core.OpenXml.Models; + +namespace MiniExcelLib.Core.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class MiniExcelSheetAttribute : Attribute +{ + public string? Name { get; set; } + public SheetState State { get; set; } = SheetState.Visible; +} + +public class DynamicExcelSheetAttribute(string key) : MiniExcelSheetAttribute +{ + public string Key { get; set; } = key; +} \ No newline at end of file diff --git a/src/MiniExcel.Core/DataReader/MiniExcelAsyncDataReader.cs b/src/MiniExcel.Core/DataReader/MiniExcelAsyncDataReader.cs new file mode 100644 index 00000000..aafdca80 --- /dev/null +++ b/src/MiniExcel.Core/DataReader/MiniExcelAsyncDataReader.cs @@ -0,0 +1,108 @@ +namespace MiniExcelLib.Core.DataReader; + +// todo: this is way improvable, ideally the sync and async implementations into a single datareader +public class MiniExcelAsyncDataReader : MiniExcelDataReaderBase, IAsyncDisposable +{ + private readonly IAsyncEnumerator> _source; + + private readonly Stream _stream; + private List _keys; + private int _fieldCount; + + private bool _isFirst = true; + private bool _disposed = false; + + /// + /// Initializes a new instance of the class. + /// + + internal MiniExcelAsyncDataReader(Stream? stream, IAsyncEnumerable> values) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _source = values.GetAsyncEnumerator(); + } + + public static async Task CreateAsync(Stream? stream, IAsyncEnumerable> values) + { + var reader = new MiniExcelAsyncDataReader(stream, values); + if (await reader._source.MoveNextAsync().ConfigureAwait(false)) + { + reader._keys = reader._source.Current?.Keys.ToList() ?? []; + reader._fieldCount = reader._keys.Count; + } + return reader; + } + + + /// + public override object? GetValue(int i) + { + if (_source.Current is null) + throw new InvalidOperationException("No current row available."); + + return _source.Current[_keys[i]]; + } + + /// + public override int FieldCount => _fieldCount; + + /// + /// This method will throw a NotSupportedException. Please use ReadAsync or the synchronous MiniExcelDataReader implementation. + /// + public override bool Read() => throw new NotSupportedException("Use the ReadAsync method instead."); + + public override async Task ReadAsync(CancellationToken cancellationToken = default) + { + if (_isFirst) + { + _isFirst = false; + return await Task.FromResult(true).ConfigureAwait(false); + } + + return await _source.MoveNextAsync().ConfigureAwait(false); + } + + /// + public override string GetName(int i) + { + return _keys[i]; + } + + /// + public override int GetOrdinal(string name) + { + return _keys.IndexOf(name); + } + + /// + protected override void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _stream?.Dispose(); + _source.DisposeAsync().GetAwaiter().GetResult(); + } + _disposed = true; + } + base.Dispose(disposing); + } + + /// + /// Disposes the object. + /// + public new void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public async ValueTask DisposeAsync() + { + _stream?.Dispose(); + await _source.DisposeAsync().ConfigureAwait(false); + + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelDataReader.cs b/src/MiniExcel.Core/DataReader/MiniExcelDataReader.cs similarity index 65% rename from src/MiniExcel/MiniExcelDataReader.cs rename to src/MiniExcel.Core/DataReader/MiniExcelDataReader.cs index 8dbb1756..df1d14e0 100644 --- a/src/MiniExcel/MiniExcelDataReader.cs +++ b/src/MiniExcel.Core/DataReader/MiniExcelDataReader.cs @@ -1,13 +1,9 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.DataReader; public class MiniExcelDataReader : MiniExcelDataReaderBase { private readonly IEnumerator> _source; + private readonly Stream _stream; private readonly List _keys; private readonly int _fieldCount; @@ -18,16 +14,11 @@ public class MiniExcelDataReader : MiniExcelDataReaderBase /// /// Initializes a new instance of the class. /// - /// The stream to read from. - /// Whether to use the header row. - /// The name of the sheet. - /// The type of the Excel file. - /// The start cell. - /// The configuration. - internal MiniExcelDataReader(Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) + internal MiniExcelDataReader(Stream? stream, IEnumerable> values) { _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _source = _stream.Query(useHeaderRow, sheetName, excelType, startCell, configuration).Cast>().GetEnumerator(); + _source = values.GetEnumerator(); + if (_source.MoveNext()) { _keys = _source.Current?.Keys.ToList() ?? []; @@ -35,8 +26,11 @@ internal MiniExcelDataReader(Stream stream, bool useHeaderRow = false, string? s } } + public static MiniExcelDataReader Create(Stream? stream, IEnumerable> values) => new(stream, values); + + /// - public override object GetValue(int i) + public override object? GetValue(int i) { if (_source.Current is null) throw new InvalidOperationException("No current row available."); @@ -58,6 +52,7 @@ public override bool Read() _isFirst = false; return true; } + return _source.MoveNext(); } diff --git a/src/MiniExcel/MiniExcelDataReaderBase.cs b/src/MiniExcel.Core/DataReader/MiniExcelDataReaderBase.cs similarity index 90% rename from src/MiniExcel/MiniExcelDataReaderBase.cs rename to src/MiniExcel.Core/DataReader/MiniExcelDataReaderBase.cs index e39df9fc..e10811ec 100644 --- a/src/MiniExcel/MiniExcelDataReaderBase.cs +++ b/src/MiniExcel.Core/DataReader/MiniExcelDataReaderBase.cs @@ -1,9 +1,6 @@ -using System; -using System.Data; -using System.Threading; -using System.Threading.Tasks; +using MiniExcelLib.Core.Abstractions; -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.DataReader; /// /// IMiniExcelDataReader Base Class @@ -42,7 +39,7 @@ public abstract class MiniExcelDataReaderBase : IMiniExcelDataReader /// /// /// - public virtual int FieldCount { get; } + public virtual int FieldCount { get; } = 0; /// /// @@ -67,7 +64,7 @@ public abstract class MiniExcelDataReaderBase : IMiniExcelDataReader /// /// /// - public virtual long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length) => 0; + public virtual long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferOffset, int length) => 0; /// /// @@ -127,7 +124,7 @@ public abstract class MiniExcelDataReaderBase : IMiniExcelDataReader /// /// /// - public virtual Type? GetFieldType(int i) => null; + public virtual Type GetFieldType(int i) => null!; /// /// @@ -257,7 +254,7 @@ public virtual Task GetNameAsync(int i, CancellationToken cancellationTo /// /// /// - public abstract object GetValue(int i); + public abstract object? GetValue(int i); /// /// @@ -265,10 +262,10 @@ public virtual Task GetNameAsync(int i, CancellationToken cancellationTo /// /// /// - public virtual Task GetValueAsync(int i, CancellationToken cancellationToken = default) + public virtual Task GetValueAsync(int i, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); + return Task.FromCanceled(cancellationToken); try { @@ -276,7 +273,7 @@ public virtual Task GetValueAsync(int i, CancellationToken cancellationT } catch (Exception e) { - return Task.FromException(e); + return Task.FromException(e); } } diff --git a/src/MiniExcel/Enums/XlsxImgType.cs b/src/MiniExcel.Core/Enums/XlsxImgType.cs similarity index 90% rename from src/MiniExcel/Enums/XlsxImgType.cs rename to src/MiniExcel.Core/Enums/XlsxImgType.cs index b10973a2..066ff335 100644 --- a/src/MiniExcel/Enums/XlsxImgType.cs +++ b/src/MiniExcel.Core/Enums/XlsxImgType.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.Enums; +namespace MiniExcelLib.Core.Enums; /// /// Excel image display mode (whether the image aligns/scales with cells). diff --git a/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs b/src/MiniExcel.Core/Exceptions/MiniExcelColumnNotFoundException.cs similarity index 80% rename from src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs rename to src/MiniExcel.Core/Exceptions/MiniExcelColumnNotFoundException.cs index 23d3cb86..621bfafb 100644 --- a/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs +++ b/src/MiniExcel.Core/Exceptions/MiniExcelColumnNotFoundException.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; +namespace MiniExcelLib.Core.Exceptions; -namespace MiniExcelLibs.Exceptions; - -public class ExcelColumnNotFoundException( +public class MiniExcelColumnNotFoundException( string? columnIndex, string? columnName, string[] columnAliases, diff --git a/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs b/src/MiniExcel.Core/Exceptions/MiniExcelInvalidCastException.cs similarity index 57% rename from src/MiniExcel/Exceptions/ExcelInvalidCastException.cs rename to src/MiniExcel.Core/Exceptions/MiniExcelInvalidCastException.cs index 2bc0a27d..2c883e0f 100644 --- a/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs +++ b/src/MiniExcel.Core/Exceptions/MiniExcelInvalidCastException.cs @@ -1,8 +1,6 @@ -using System; +namespace MiniExcelLib.Core.Exceptions; -namespace MiniExcelLibs.Exceptions; - -public class ExcelInvalidCastException(string columnName, int row, object value, Type invalidCastType, string message) +public class MiniExcelInvalidCastException(string columnName, int row, object value, Type invalidCastType, string message) : InvalidCastException(message) { public string ColumnName { get; set; } = columnName; diff --git a/src/MiniExcel.Core/GlobalUsings.cs b/src/MiniExcel.Core/GlobalUsings.cs new file mode 100644 index 00000000..05f12759 --- /dev/null +++ b/src/MiniExcel.Core/GlobalUsings.cs @@ -0,0 +1,17 @@ +// Global using directives + +global using System.Collections; +global using System.Data; +global using System.Globalization; +global using System.IO.Compression; +global using System.Reflection; +global using System.Runtime.CompilerServices; +global using System.Text; +global using System.Text.RegularExpressions; +global using System.Xml; +global using MiniExcelLib.Core.Abstractions; +global using MiniExcelLib.Core.Helpers; +global using MiniExcelLib.Core.OpenXml; +global using MiniExcelLib.Core.OpenXml.Utils; +global using MiniExcelLib.Core.Reflection; +global using Zomp.SyncMethodGenerator; \ No newline at end of file diff --git a/src/MiniExcel/Utils/AttributeExtension.cs b/src/MiniExcel.Core/Helpers/AttributeExtension.cs similarity index 90% rename from src/MiniExcel/Utils/AttributeExtension.cs rename to src/MiniExcel.Core/Helpers/AttributeExtension.cs index 470b1418..a8236785 100644 --- a/src/MiniExcel/Utils/AttributeExtension.cs +++ b/src/MiniExcel.Core/Helpers/AttributeExtension.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.Helpers; internal static class AttributeExtension { diff --git a/src/MiniExcel/Utils/ColumnHelper.cs b/src/MiniExcel.Core/Helpers/ColumnHelper.cs similarity index 89% rename from src/MiniExcel/Utils/ColumnHelper.cs rename to src/MiniExcel.Core/Helpers/ColumnHelper.cs index 9ea5c1da..b5d9ccba 100644 --- a/src/MiniExcel/Utils/ColumnHelper.cs +++ b/src/MiniExcel.Core/Helpers/ColumnHelper.cs @@ -1,11 +1,9 @@ using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.Helpers; // For Row/Column Index -internal static class ColumnHelper +public static class ColumnHelper { private const int GeneralColumnIndex = 255; private const int MaxColumnIndex = 16383; @@ -40,6 +38,7 @@ private static void CheckAndSetMaxColumnIndex(int columnIndex) { if (columnIndex < _intMappingAlphabetCount) return; + if (columnIndex > MaxColumnIndex) throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index."); @@ -51,7 +50,7 @@ private static void CheckAndSetMaxColumnIndex(int columnIndex) _intMappingAlphabetCount = IntMappingAlphabet.Count; } - internal static string IntToLetters(int value) + private static string IntToLetters(int value) { value++; var result = string.Empty; diff --git a/src/MiniExcel/Utils/FileHelper.cs b/src/MiniExcel.Core/Helpers/FileHelper.cs similarity index 59% rename from src/MiniExcel/Utils/FileHelper.cs rename to src/MiniExcel.Core/Helpers/FileHelper.cs index 3fa7655f..dbd1180c 100644 --- a/src/MiniExcel/Utils/FileHelper.cs +++ b/src/MiniExcel.Core/Helpers/FileHelper.cs @@ -1,8 +1,6 @@ -using System.IO; +namespace MiniExcelLib.Core.Helpers; -namespace MiniExcelLibs.Utils; - -internal static class FileHelper +public static class FileHelper { public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } \ No newline at end of file diff --git a/src/MiniExcel/Utils/ImageHelper.cs b/src/MiniExcel.Core/Helpers/ImageHelper.cs similarity index 93% rename from src/MiniExcel/Utils/ImageHelper.cs rename to src/MiniExcel.Core/Helpers/ImageHelper.cs index 1f376f12..97013e66 100644 --- a/src/MiniExcel/Utils/ImageHelper.cs +++ b/src/MiniExcel.Core/Helpers/ImageHelper.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.Helpers; internal static class ImageHelper { diff --git a/src/MiniExcel.Core/Helpers/ListHelper.cs b/src/MiniExcel.Core/Helpers/ListHelper.cs new file mode 100644 index 00000000..f2f2182d --- /dev/null +++ b/src/MiniExcel.Core/Helpers/ListHelper.cs @@ -0,0 +1,15 @@ +namespace MiniExcelLib.Core.Helpers; + +internal static class ListHelper +{ + internal static bool StartsWith(this IList span, IList value) where T : IEquatable + { + if (value is []) + return true; + + if (span.Count < value.Count) + return false; + + return span.Take(value.Count).SequenceEqual(value); + } +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs b/src/MiniExcel.Core/Helpers/SafeStreamWriter.cs similarity index 70% rename from src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs rename to src/MiniExcel.Core/Helpers/SafeStreamWriter.cs index 6f8509cb..e147263c 100644 --- a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs +++ b/src/MiniExcel.Core/Helpers/SafeStreamWriter.cs @@ -1,17 +1,11 @@ -using System; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +namespace MiniExcelLib.Core.Helpers; -namespace MiniExcelLibs.OpenXml; - -internal partial class MiniExcelStreamWriter(Stream stream, Encoding encoding, int bufferSize) : IDisposable +internal partial class SafeStreamWriter(Stream stream, Encoding encoding, int bufferSize) : IDisposable { private readonly StreamWriter _streamWriter = new(stream, encoding, bufferSize); - private bool _disposedValue; + private bool _disposed; - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task WriteAsync(string content, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -22,7 +16,7 @@ public async Task WriteAsync(string content, CancellationToken cancellationToken } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task WriteAndFlushAsync(string content, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -31,13 +25,13 @@ public async Task WriteAndFlushAsync(string content, CancellationToken can return await FlushAsync(cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task WriteWhitespaceAsync(int length) { await _streamWriter.WriteAsync(new string(' ', length)).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task FlushAsync(CancellationToken cancellationToken = default) { await _streamWriter.FlushAsync( @@ -55,10 +49,10 @@ public void SetPosition(long position) protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (!_disposed) { - _streamWriter?.Dispose(); - _disposedValue = true; + _streamWriter.Dispose(); + _disposed = true; } } diff --git a/src/MiniExcel.Core/Helpers/StringHelper.cs b/src/MiniExcel.Core/Helpers/StringHelper.cs new file mode 100644 index 00000000..94ff906b --- /dev/null +++ b/src/MiniExcel.Core/Helpers/StringHelper.cs @@ -0,0 +1,7 @@ +namespace MiniExcelLib.Core.Helpers; + +internal static class StringHelper +{ + public static string GetLetters(string content) => new([..content.Where(char.IsLetter)]); + public static int GetNumber(string content) => int.Parse(new string([..content.Where(char.IsNumber)])); +} \ No newline at end of file diff --git a/src/MiniExcel/Utils/TaskHelper.cs b/src/MiniExcel.Core/Helpers/TaskHelper.cs similarity index 77% rename from src/MiniExcel/Utils/TaskHelper.cs rename to src/MiniExcel.Core/Helpers/TaskHelper.cs index 660a9d8e..909e5691 100644 --- a/src/MiniExcel/Utils/TaskHelper.cs +++ b/src/MiniExcel.Core/Helpers/TaskHelper.cs @@ -1,8 +1,4 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.Helpers; internal static class TaskHelper { diff --git a/src/MiniExcel.Core/Helpers/ThrowHelper.cs b/src/MiniExcel.Core/Helpers/ThrowHelper.cs new file mode 100644 index 00000000..892e0645 --- /dev/null +++ b/src/MiniExcel.Core/Helpers/ThrowHelper.cs @@ -0,0 +1,19 @@ +namespace MiniExcelLib.Core.Helpers; + +internal static class ThrowHelper +{ + internal static void ThrowIfInvalidOpenXml(Stream stream) + { + var probe = new byte[8]; + stream.Seek(0, SeekOrigin.Begin); + var read = stream.Read(probe, 0, probe.Length); + if (read != probe.Length) + throw new InvalidDataException("The file/stream does not contain enough data to be processed."); + + stream.Seek(0, SeekOrigin.Begin); + + // OpenXml format can be any ZIP archive + if (probe is not [0x50, 0x4B, ..]) + throw new InvalidDataException("The file is not a valid OpenXml document."); + } +} diff --git a/src/MiniExcel.Core/Helpers/TypeHelper.cs b/src/MiniExcel.Core/Helpers/TypeHelper.cs new file mode 100644 index 00000000..1ba86cbf --- /dev/null +++ b/src/MiniExcel.Core/Helpers/TypeHelper.cs @@ -0,0 +1,44 @@ +namespace MiniExcelLib.Core.Helpers; + +internal static class TypeHelper +{ + public static IEnumerable> ConvertToEnumerableDictionary(IDataReader reader) + { + while (reader.Read()) + { + yield return Enumerable + .Range(0, reader.FieldCount) + .ToDictionary(reader.GetName, reader.GetValue); + } + } + + /// + /// From : https://stackoverflow.com/questions/906499/getting-type-t-from-ienumerablet + /// + public static IEnumerable GetGenericIEnumerables(object o) + { + return o.GetType() + .GetInterfaces() + .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + .Select(t => t.GetGenericArguments()[0]); + } + + public static bool IsNumericType(Type type, bool isNullableUnderlyingType = false) + { + if (isNullableUnderlyingType) + type = Nullable.GetUnderlyingType(type) ?? type; + + // True for all numeric types except bool, sbyte and byte + return Type.GetTypeCode(type) is >= TypeCode.Int16 and <= TypeCode.Decimal; + } + + public static bool IsAsyncEnumerable(this Type type, out Type? genericArgument) + { + var asyncEnumrableInterfaceType = type + .GetInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)); + + genericArgument = asyncEnumrableInterfaceType?.GetGenericArguments().FirstOrDefault(); + return genericArgument is not null; + } +} \ No newline at end of file diff --git a/src/MiniExcel/Utils/XmlEncoder.cs b/src/MiniExcel.Core/Helpers/XmlHelper.cs similarity index 66% rename from src/MiniExcel/Utils/XmlEncoder.cs rename to src/MiniExcel.Core/Helpers/XmlHelper.cs index 0ef0e5f2..ea12a6bf 100644 --- a/src/MiniExcel/Utils/XmlEncoder.cs +++ b/src/MiniExcel.Core/Helpers/XmlHelper.cs @@ -1,11 +1,7 @@ -using System.Text; -using System.Text.RegularExpressions; -using System.Xml; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.Helpers; /// XmlEncoder MIT Copyright ©2021 from https://github.com/ClosedXML -internal static partial class XmlEncoder +internal static partial class XmlHelper { #if NET7_0_OR_GREATER [GeneratedRegex("_(x[\\dA-Fa-f]{4})_", RegexOptions.Compiled)] private static partial Regex X4LRegexImpl(); @@ -45,4 +41,22 @@ internal static partial class XmlEncoder decodeStr = UppercaseX4LRegex.Replace(decodeStr, "_x005F_$1_"); return XmlConvert.DecodeName(decodeStr); } + + public static string MinifyXml(string xml) => xml + .Replace("\r", "") + .Replace("\n", "") + .Replace("\t", "") + .Trim(); + + /// + /// Encode to XML (special characteres: ' " > < &) + /// + public static string EncodeXml(string? value) => value is null ? "" + : XmlHelper.EncodeString(value) + ?.Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'") + .ToString() ?? ""; } \ No newline at end of file diff --git a/src/MiniExcel.Core/MiniExcel.Core.csproj b/src/MiniExcel.Core/MiniExcel.Core.csproj new file mode 100644 index 00000000..7db6767f --- /dev/null +++ b/src/MiniExcel.Core/MiniExcel.Core.csproj @@ -0,0 +1,16 @@ + + + + MiniExcelLib.Core + + + + MiniExcel.Core + MiniExcel.Core + + + + + + + diff --git a/src/MiniExcel.Core/MiniExcel.cs b/src/MiniExcel.Core/MiniExcel.cs new file mode 100644 index 00000000..29f2dfa6 --- /dev/null +++ b/src/MiniExcel.Core/MiniExcel.cs @@ -0,0 +1,8 @@ +namespace MiniExcelLib.Core; + +public static class MiniExcel +{ + public static readonly MiniExcelExporterProvider Exporters = new(); + public static readonly MiniExcelImporterProvider Importers = new(); + public static readonly MiniExcelTemplaterProvider Templaters = new(); +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelConfiguration.cs b/src/MiniExcel.Core/MiniExcelConfiguration.cs similarity index 59% rename from src/MiniExcel/MiniExcelConfiguration.cs rename to src/MiniExcel.Core/MiniExcelConfiguration.cs index 9518aef7..e5ec3ea0 100644 --- a/src/MiniExcel/MiniExcelConfiguration.cs +++ b/src/MiniExcel.Core/MiniExcelConfiguration.cs @@ -1,11 +1,10 @@ -using System.Globalization; -using MiniExcelLibs.Attributes; +using MiniExcelLib.Core.Attributes; -namespace MiniExcelLibs; +namespace MiniExcelLib.Core; public interface IMiniExcelConfiguration; -public abstract class MiniExcelConfiguration : IMiniExcelConfiguration +public abstract class MiniExcelBaseConfiguration : IMiniExcelConfiguration { public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; public DynamicExcelColumn[]? DynamicColumns { get; set; } = []; @@ -13,7 +12,7 @@ public abstract class MiniExcelConfiguration : IMiniExcelConfiguration public bool FastMode { get; set; } /// - /// When exporting using DataReader, the data not in DynamicColumn will be filtered. + /// When exporting using DataReader, the data not in DynamicColumn will be filtered. /// public bool DynamicColumnFirst { get; set; } = false; } \ No newline at end of file diff --git a/src/MiniExcel.Core/MiniExcelProviders.cs b/src/MiniExcel.Core/MiniExcelProviders.cs new file mode 100644 index 00000000..9a15527c --- /dev/null +++ b/src/MiniExcel.Core/MiniExcelProviders.cs @@ -0,0 +1,22 @@ +namespace MiniExcelLib.Core; + +public sealed class MiniExcelImporterProvider +{ + internal MiniExcelImporterProvider() { } + + public OpenXmlImporter GetOpenXmlImporter() => new(); +} + +public sealed class MiniExcelExporterProvider +{ + internal MiniExcelExporterProvider() { } + + public OpenXmlExporter GetOpenXmlExporter() => new(); +} + +public sealed class MiniExcelTemplaterProvider +{ + internal MiniExcelTemplaterProvider() { } + + public OpenXmlTemplater GetOpenXmlTemplater() => new(); +} diff --git a/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs b/src/MiniExcel.Core/OpenXml/Constants/ExcelContentTypes.cs similarity index 92% rename from src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs rename to src/MiniExcel.Core/OpenXml/Constants/ExcelContentTypes.cs index bcb5f9fa..6ef0fe98 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs +++ b/src/MiniExcel.Core/OpenXml/Constants/ExcelContentTypes.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.OpenXml.Constants; +namespace MiniExcelLib.Core.OpenXml.Constants; internal static class ExcelContentTypes { diff --git a/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs b/src/MiniExcel.Core/OpenXml/Constants/ExcelFileNames.cs similarity index 91% rename from src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs rename to src/MiniExcel.Core/OpenXml/Constants/ExcelFileNames.cs index 982c22eb..0f7a6f26 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs +++ b/src/MiniExcel.Core/OpenXml/Constants/ExcelFileNames.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.OpenXml.Constants; +namespace MiniExcelLib.Core.OpenXml.Constants; internal static class ExcelFileNames { diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel.Core/OpenXml/Constants/ExcelXml.cs similarity index 88% rename from src/MiniExcel/OpenXml/Constants/ExcelXml.cs rename to src/MiniExcel.Core/OpenXml/Constants/ExcelXml.cs index 6b489253..d5fd0639 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs +++ b/src/MiniExcel.Core/OpenXml/Constants/ExcelXml.cs @@ -1,16 +1,17 @@ -using MiniExcelLibs.OpenXml.Models; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Models; -namespace MiniExcelLibs.OpenXml.Constants; +namespace MiniExcelLib.Core.OpenXml.Constants; internal static class ExcelXml { static ExcelXml() { - DefaultRels = ExcelOpenXmlUtils.MinifyXml(DefaultRels); - DefaultWorkbookXml = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXml); - DefaultWorkbookXmlRels = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXmlRels); - DefaultSheetRelXml = ExcelOpenXmlUtils.MinifyXml(DefaultSheetRelXml); - DefaultDrawing = ExcelOpenXmlUtils.MinifyXml(DefaultDrawing); + DefaultRels = XmlHelper.MinifyXml(DefaultRels); + DefaultWorkbookXml = XmlHelper.MinifyXml(DefaultWorkbookXml); + DefaultWorkbookXmlRels = XmlHelper.MinifyXml(DefaultWorkbookXmlRels); + DefaultSheetRelXml = XmlHelper.MinifyXml(DefaultSheetRelXml); + DefaultDrawing = XmlHelper.MinifyXml(DefaultDrawing); } internal const string EmptySheetXml = """"""; @@ -123,6 +124,6 @@ internal static string DrawingXml(FileDto file, int fileIndex) """; internal static string Sheet(SheetDto sheetDto, int sheetId) - => $""""""; + => $""""""; } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/Schemas.cs b/src/MiniExcel.Core/OpenXml/Constants/Schemas.cs similarity index 91% rename from src/MiniExcel/OpenXml/Constants/Schemas.cs rename to src/MiniExcel.Core/OpenXml/Constants/Schemas.cs index 15a2cbe3..8af3ea62 100644 --- a/src/MiniExcel/OpenXml/Constants/Schemas.cs +++ b/src/MiniExcel.Core/OpenXml/Constants/Schemas.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.OpenXml.Constants; +namespace MiniExcelLib.Core.OpenXml.Constants; internal static class Schemas { diff --git a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs b/src/MiniExcel.Core/OpenXml/Constants/WorksheetXml.cs similarity index 95% rename from src/MiniExcel/OpenXml/Constants/WorksheetXml.cs rename to src/MiniExcel.Core/OpenXml/Constants/WorksheetXml.cs index 1ec9e1e8..a82e5f11 100644 --- a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs +++ b/src/MiniExcel.Core/OpenXml/Constants/WorksheetXml.cs @@ -1,7 +1,6 @@ -using System.Globalization; -using MiniExcelLibs.Attributes; +using MiniExcelLib.Core.Attributes; -namespace MiniExcelLibs.OpenXml.Constants; +namespace MiniExcelLib.Core.OpenXml.Constants; internal static class WorksheetXml { diff --git a/src/MiniExcel/OpenXml/Models/DrawingDto.cs b/src/MiniExcel.Core/OpenXml/Models/DrawingDto.cs similarity index 58% rename from src/MiniExcel/OpenXml/Models/DrawingDto.cs rename to src/MiniExcel.Core/OpenXml/Models/DrawingDto.cs index 672ae8b3..3a8ba087 100644 --- a/src/MiniExcel/OpenXml/Models/DrawingDto.cs +++ b/src/MiniExcel.Core/OpenXml/Models/DrawingDto.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.OpenXml.Models; +namespace MiniExcelLib.Core.OpenXml.Models; internal class DrawingDto { diff --git a/src/MiniExcel/OpenXml/Models/ExcelRange.cs b/src/MiniExcel.Core/OpenXml/Models/ExcelRange.cs similarity index 89% rename from src/MiniExcel/OpenXml/Models/ExcelRange.cs rename to src/MiniExcel.Core/OpenXml/Models/ExcelRange.cs index 0ecb16cb..b541c7c8 100644 --- a/src/MiniExcel/OpenXml/Models/ExcelRange.cs +++ b/src/MiniExcel.Core/OpenXml/Models/ExcelRange.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.OpenXml.Models; +namespace MiniExcelLib.Core.OpenXml.Models; public class ExcelRangeElement { diff --git a/src/MiniExcel/OpenXml/ExcelWidthCollection.cs b/src/MiniExcel.Core/OpenXml/Models/ExcelWidthCollection.cs similarity index 87% rename from src/MiniExcel/OpenXml/ExcelWidthCollection.cs rename to src/MiniExcel.Core/OpenXml/Models/ExcelWidthCollection.cs index a8db103d..b122fc07 100644 --- a/src/MiniExcel/OpenXml/ExcelWidthCollection.cs +++ b/src/MiniExcel.Core/OpenXml/Models/ExcelWidthCollection.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Models; public sealed class ExcelColumnWidth { public int Index { get; set; } public double Width { get; set; } - internal static IEnumerable FromProps(ICollection props, double? minWidth = null) + internal static IEnumerable FromProps(ICollection props, double? minWidth = null) { var i = 1; foreach (var p in props) @@ -37,7 +34,7 @@ public sealed class ExcelWidthCollection public IEnumerable Columns => _columnWidths.Values; - internal ExcelWidthCollection(double minWidth, double maxWidth, ICollection props) + internal ExcelWidthCollection(double minWidth, double maxWidth, ICollection props) { _maxWidth = maxWidth; _columnWidths = ExcelColumnWidth.FromProps(props, minWidth).ToDictionary(x => x.Index); diff --git a/src/MiniExcel.Core/OpenXml/Models/ExcellSheetInfo.cs b/src/MiniExcel.Core/OpenXml/Models/ExcellSheetInfo.cs new file mode 100644 index 00000000..79cd8869 --- /dev/null +++ b/src/MiniExcel.Core/OpenXml/Models/ExcellSheetInfo.cs @@ -0,0 +1,15 @@ +namespace MiniExcelLib.Core.OpenXml.Models; + +internal class ExcellSheetInfo +{ + public object Key { get; set; } + public string? ExcelSheetName { get; set; } + public SheetState ExcelSheetState { get; set; } + + private string ExcelSheetStateAsString => ExcelSheetState.ToString().ToLower(); + + public SheetDto ToDto(int sheetIndex) + { + return new SheetDto { Name = ExcelSheetName, SheetIdx = sheetIndex, State = ExcelSheetStateAsString }; + } +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Models/FileDto.cs b/src/MiniExcel.Core/OpenXml/Models/FileDto.cs similarity index 86% rename from src/MiniExcel/OpenXml/Models/FileDto.cs rename to src/MiniExcel.Core/OpenXml/Models/FileDto.cs index b9fd5667..6a6ddc4a 100644 --- a/src/MiniExcel/OpenXml/Models/FileDto.cs +++ b/src/MiniExcel.Core/OpenXml/Models/FileDto.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.OpenXml.Models; +namespace MiniExcelLib.Core.OpenXml.Models; internal class FileDto { diff --git a/src/MiniExcel/OpenXml/MergeCells.cs b/src/MiniExcel.Core/OpenXml/Models/MergeCells.cs similarity index 68% rename from src/MiniExcel/OpenXml/MergeCells.cs rename to src/MiniExcel.Core/OpenXml/Models/MergeCells.cs index a1a62d78..a8c41919 100644 --- a/src/MiniExcel/OpenXml/MergeCells.cs +++ b/src/MiniExcel.Core/OpenXml/Models/MergeCells.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Models; internal class MergeCells { diff --git a/src/MiniExcel/OpenXml/Models/SheetDto.cs b/src/MiniExcel.Core/OpenXml/Models/SheetDto.cs similarity index 79% rename from src/MiniExcel/OpenXml/Models/SheetDto.cs rename to src/MiniExcel.Core/OpenXml/Models/SheetDto.cs index 2d6da7cc..c18cb0ea 100644 --- a/src/MiniExcel/OpenXml/Models/SheetDto.cs +++ b/src/MiniExcel.Core/OpenXml/Models/SheetDto.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.OpenXml.Models; +namespace MiniExcelLib.Core.OpenXml.Models; internal class SheetDto { diff --git a/src/MiniExcel/OpenXml/SheetInfo.cs b/src/MiniExcel.Core/OpenXml/Models/SheetInfo.cs similarity index 91% rename from src/MiniExcel/OpenXml/SheetInfo.cs rename to src/MiniExcel.Core/OpenXml/Models/SheetInfo.cs index a75d05f3..5bea92b2 100644 --- a/src/MiniExcel/OpenXml/SheetInfo.cs +++ b/src/MiniExcel.Core/OpenXml/Models/SheetInfo.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Models; public class SheetInfo(uint id, uint index, string name, SheetState sheetState, bool active) { diff --git a/src/MiniExcel/OpenXml/SheetRecord.cs b/src/MiniExcel.Core/OpenXml/Models/SheetRecord.cs similarity index 91% rename from src/MiniExcel/OpenXml/SheetRecord.cs rename to src/MiniExcel.Core/OpenXml/Models/SheetRecord.cs index ae85edc3..2bc57d71 100644 --- a/src/MiniExcel/OpenXml/SheetRecord.cs +++ b/src/MiniExcel.Core/OpenXml/Models/SheetRecord.cs @@ -1,6 +1,4 @@ -using System; - -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Models; internal sealed class SheetRecord(string name, string state, uint id, string rid, bool active) { diff --git a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs similarity index 74% rename from src/MiniExcel/OpenXml/OpenXmlConfiguration.cs rename to src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs index 0b956425..b3de0395 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlConfiguration.cs @@ -1,10 +1,11 @@ -using MiniExcelLibs.Attributes; +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.OpenXml.Styles; -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml; -public class OpenXmlConfiguration : MiniExcelConfiguration +public class OpenXmlConfiguration : MiniExcelBaseConfiguration { - internal static readonly OpenXmlConfiguration DefaultConfig = new(); + internal static OpenXmlConfiguration Default => new(); public bool FillMergedCells { get; set; } public TableStyles TableStyles { get; set; } = TableStyles.Default; @@ -20,7 +21,7 @@ public class OpenXmlConfiguration : MiniExcelConfiguration public bool EnableSharedStringCache { get; set; } = true; public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; public OpenXmlStyleOptions StyleOptions { get; set; } = new(); - public DynamicExcelSheet[]? DynamicSheets { get; set; } + public DynamicExcelSheetAttribute[]? DynamicSheets { get; set; } public bool EnableWriteFilePath{ get; set; } = true; /// @@ -29,4 +30,10 @@ public class OpenXmlConfiguration : MiniExcelConfiguration public bool EnableAutoWidth { get; set; } public double MinWidth { get; set; } = 9.28515625; public double MaxWidth { get; set; } = 200; +} + +public enum TableStyles +{ + None, + Default } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs similarity index 86% rename from src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs rename to src/MiniExcel.Core/OpenXml/OpenXmlReader.cs index 6162554d..1cbfd8bd 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlReader.cs @@ -1,49 +1,45 @@ -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.OpenXml; - -internal partial class ExcelOpenXmlSheetReader : IExcelReader +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Core.OpenXml.Styles; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Core.OpenXml.Zip; +using MiniExcelLib.Core.Reflection; +using MiniExcelMapper = MiniExcelLib.Core.Reflection.MiniExcelMapper; +using XmlReaderHelper = MiniExcelLib.Core.OpenXml.Utils.XmlReaderHelper; + +namespace MiniExcelLib.Core.OpenXml; + +internal partial class OpenXmlReader : Abstractions.IMiniExcelReader { private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; private static readonly string[] RelationshiopNs = [Schemas.SpreadsheetmlXmlRelationshipns, Schemas.SpreadsheetmlXmlStrictRelationshipns]; private readonly OpenXmlConfiguration _config; private List? _sheetRecords; - private ExcelOpenXmlStyles? _style; + private OpenXmlStyles? _style; private bool _disposed; - internal readonly ExcelOpenXmlZip Archive; + internal readonly OpenXmlZip Archive; internal IDictionary? SharedStrings; - private ExcelOpenXmlSheetReader(Stream stream, IMiniExcelConfiguration? configuration) + private OpenXmlReader(Stream stream, IMiniExcelConfiguration? configuration) { - Archive = new ExcelOpenXmlZip(stream); - _config = (OpenXmlConfiguration?)configuration ?? OpenXmlConfiguration.DefaultConfig; + Archive = new OpenXmlZip(stream); + _config = (OpenXmlConfiguration?)configuration ?? OpenXmlConfiguration.Default; } [CreateSyncVersion] - public static async Task CreateAsync(Stream stream, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) + internal static async Task CreateAsync(Stream stream, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) { - var reader = new ExcelOpenXmlSheetReader(stream, configuration); + ThrowHelper.ThrowIfInvalidOpenXml(stream); + + var reader = new OpenXmlReader(stream, configuration); await reader.SetSharedStringsAsync(cancellationToken).ConfigureAwait(false); return reader; } - + [CreateSyncVersion] public IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, CancellationToken cancellationToken = default) { @@ -51,13 +47,14 @@ public static async Task CreateAsync(Stream stream, IMi } [CreateSyncVersion] - public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool mapHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { - sheetName ??= CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; + sheetName ??= ExcelPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; //Todo: Find a way if possible to remove the 'hasHeader' parameter to check whether or not to include // the first row in the result set in favor of modifying the already present 'useHeaderRow' to do the same job - return QueryImplAsync(QueryAsync(false, sheetName, startCell, cancellationToken), startCell, hasHeader, _config, cancellationToken); } + return MiniExcelMapper.MapQueryAsync(QueryAsync(false, sheetName, startCell, cancellationToken), startCell, mapHeaderAsData, _config.TrimColumnNames, _config, cancellationToken); + } [CreateSyncVersion] public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) @@ -88,9 +85,9 @@ public static async Task CreateAsync(Stream stream, IMi } [CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { - return QueryImplAsync(QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken), startCell, hasHeader, _config, cancellationToken); + return MiniExcelMapper.MapQueryAsync(QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken), startCell, treatHeaderAsData, _config.TrimColumnNames, _config, cancellationToken); } [CreateSyncVersion] @@ -132,9 +129,9 @@ public static async Task CreateAsync(Stream stream, IMi } [CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { - return QueryImplAsync(QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken), ReferenceHelper.ConvertXyToCell(startColumnIndex, startRowIndex), hasHeader, _config, cancellationToken); + return MiniExcelMapper.MapQueryAsync(QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken), ReferenceHelper.ConvertCoordinatesToCell(startColumnIndex, startRowIndex), treatHeaderAsData, _config.TrimColumnNames, _config, cancellationToken); } [CreateSyncVersion] @@ -322,7 +319,7 @@ await XmlReaderHelper.SkipToNextSameLevelDomAsync(reader, cancellationToken) xfIndex = styleIndex; // only when have s attribute then load styles xml data - _style ??= new ExcelOpenXmlStyles(Archive); + _style ??= new OpenXmlStyles(Archive); cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); } @@ -335,80 +332,7 @@ await XmlReaderHelper.SkipToNextSameLevelDomAsync(reader, cancellationToken) } yield return cell; } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryImplAsync(IAsyncEnumerable> values, string startCell, bool hasHeader, MiniExcelConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() - { - cancellationToken.ThrowIfCancellationRequested(); - - var type = typeof(T); - - //TODO:need to optimize - List props = []; - Dictionary headersDic = []; - string[] keys = []; - var first = true; - var rowIndex = 0; - - await foreach (var item in values.WithCancellation(cancellationToken).ConfigureAwait(false)) - { - if (first) - { - keys = item.Keys.ToArray(); - var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; - headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); - - //TODO: alert don't duplicate column name - props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); - first = false; - - if (hasHeader) - continue; - } - - var v = new T(); - foreach (var pInfo in props) - { - if (pInfo.ExcelColumnAliases is not null) - { - foreach (var alias in pInfo.ExcelColumnAliases) - { - if (headersDic?.TryGetValue(alias, out var columnId) ?? false) - { - var columnName = keys[columnId]; - item.TryGetValue(columnName, out var aliasItemValue); - - if (aliasItemValue is not null) - { - var newAliasValue = TypeHelper.TypeMapping(v, pInfo, aliasItemValue, rowIndex, startCell, configuration); - } - } - } - } - - //Q: Why need to check every time? A: it needs to check everytime, because it's dictionary - object? itemValue = null; - if (pInfo.ExcelIndexName is not null && (keys?.Contains(pInfo.ExcelIndexName) ?? false)) - { - item.TryGetValue(pInfo.ExcelIndexName, out itemValue); - } - else if (pInfo.ExcelColumnName is not null && (headersDic?.TryGetValue(pInfo.ExcelColumnName, out var columnId) ?? false)) - { - var columnName = keys[columnId]; - item.TryGetValue(columnName, out itemValue); - } - - if (itemValue is not null) - { - var newValue = TypeHelper.TypeMapping(v, pInfo, itemValue, rowIndex, startCell, configuration); - } - } - - rowIndex++; - yield return v; - } - } - + private ZipArchiveEntry GetSheetEntry(string? sheetName) { // if sheets count > 1 need to read xl/_rels/workbook.xml.rels @@ -731,7 +655,7 @@ private async Task ReadCellAndSetColumnIndexAsync(XmlReader reade } else if (XmlReaderHelper.IsStartElement(reader, "is", Ns)) { - var rawValue = await StringHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); + var rawValue = await XmlReaderHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(rawValue)) ConvertCellValue(rawValue, aT, xfIndex, out value); } @@ -757,7 +681,7 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec if (sstIndex >= 0 && sstIndex < SharedStrings?.Count) { //value = Helpers.ConvertEscapeChars(_SharedStrings[sstIndex]); - value = XmlEncoder.DecodeString(SharedStrings[sstIndex]); + value = XmlHelper.DecodeString(SharedStrings[sstIndex]); return; } } @@ -767,7 +691,7 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec case "inlineStr": case "str": //TODO: it will unbox,box - var v = XmlEncoder.DecodeString(rawValue); + var v = XmlHelper.DecodeString(rawValue); if (_config.EnableConvertByteArray) { //if str start with "data:image/png;base64," then convert to byte[] https://github.com/mini-software/MiniExcel/issues/318 @@ -1175,7 +1099,7 @@ internal static async Task TryGetMergeCellsAsync(ZipArchiveEntry sheetEntr for (int y = y1; y <= y2; y++) { if (!isFirst) - mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); + mergeCells.MergesMap.Add(ReferenceHelper.ConvertCoordinatesToCell(x, y), refs[0]); isFirst = false; } } @@ -1193,7 +1117,7 @@ internal static async Task TryGetMergeCellsAsync(ZipArchiveEntry sheetEntr return true; } - ~ExcelOpenXmlSheetReader() + ~OpenXmlReader() { Dispose(false); } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.DefaultOpenXml.cs similarity index 86% rename from src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs rename to src/MiniExcel.Core/OpenXml/OpenXmlWriter.DefaultOpenXml.cs index 0d625705..47178af3 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.DefaultOpenXml.cs @@ -1,22 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.Linq; -using System.Text; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using static MiniExcelLibs.Utils.ImageHelper; - -namespace MiniExcelLibs.OpenXml; - -internal partial class ExcelOpenXmlSheetWriter : IExcelWriter +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Core.OpenXml.Zip; +using MiniExcelLib.Core.Reflection; +using static MiniExcelLib.Core.Helpers.ImageHelper; + +namespace MiniExcelLib.Core.OpenXml; + +internal partial class OpenXmlWriter : Abstractions.IMiniExcelWriter { private readonly Dictionary _zipDictionary = []; private Dictionary _cellXfIdMap; - + private IEnumerable> GetSheets() { var sheetId = 0; @@ -38,7 +34,7 @@ internal partial class ExcelOpenXmlSheetWriter : IExcelWriter { sheetId++; var sheetInfos = GetSheetInfos(dt.TableName); - yield return Tuple.Create(sheetInfos.ToDto(sheetId), dt); + yield return Tuple.Create(sheetInfos.ToDto(sheetId), (object?)dt); } yield break; @@ -109,7 +105,7 @@ private string GetPanes() WorksheetXml.StartPane( xSplit: _configuration.FreezeColumnCount > 0 ? _configuration.FreezeColumnCount : null, ySplit: _configuration.FreezeRowCount > 0 ? _configuration.FreezeRowCount : null, - topLeftCell: ExcelOpenXmlUtils.ConvertXYToCell( + topLeftCell: ReferenceHelper.ConvertCoordinatesToCell( _configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1 ), @@ -127,13 +123,13 @@ private string GetPanes() */ - var cellTr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount + 1, 1); + var cellTr = ReferenceHelper.ConvertCoordinatesToCell(_configuration.FreezeColumnCount + 1, 1); sb.Append(WorksheetXml.PaneSelection("topRight", cellTr, cellTr)); - var cellBl = ExcelOpenXmlUtils.ConvertXYToCell(1, _configuration.FreezeRowCount + 1); + var cellBl = ReferenceHelper.ConvertCoordinatesToCell(1, _configuration.FreezeRowCount + 1); sb.Append(WorksheetXml.PaneSelection("bottomLeft", cellBl, cellBl)); - var cellBr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1); + var cellBr = ReferenceHelper.ConvertCoordinatesToCell(_configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1); sb.Append(WorksheetXml.PaneSelection("bottomRight", cellBr, cellBr)); } else if (_configuration.FreezeColumnCount > 0) @@ -142,7 +138,7 @@ private string GetPanes() /* */ - var cellTr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount, 1); + var cellTr = ReferenceHelper.ConvertCoordinatesToCell(_configuration.FreezeColumnCount, 1); sb.Append(WorksheetXml.PaneSelection("topRight", cellTr, cellTr)); } @@ -159,20 +155,20 @@ private string GetPanes() return sb.ToString(); } - private Tuple GetCellValue(int rowIndex, int cellIndex, object value, ExcelColumnInfo? columnInfo, bool valueIsNull) + private Tuple GetCellValue(int rowIndex, int cellIndex, object value, MiniExcelColumnInfo? columnInfo, bool valueIsNull) { if (valueIsNull) return Tuple.Create("2", "str", string.Empty); if (value is string str) - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(str)); + return Tuple.Create("2", "str", XmlHelper.EncodeXml(str)); var type = GetValueType(value, columnInfo); if (columnInfo is { ExcelFormat: not null, ExcelFormatId: -1 } && value is IFormattable formattableValue) { var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(formattedStr)); + return Tuple.Create("2", "str", XmlHelper.EncodeXml(formattedStr)); } if (type == typeof(DateTime)) @@ -209,15 +205,15 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, var base64 = GetFileValue(rowIndex, cellIndex, value); if (_configuration.EnableWriteFilePath) { - return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXml(base64)); + return Tuple.Create("4", "str", XmlHelper.EncodeXml(base64)); } return Tuple.Create("4", "str", ""); } - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(value.ToString())); + return Tuple.Create("2", "str", XmlHelper.EncodeXml(value.ToString())); } - private static Type? GetValueType(object value, ExcelColumnInfo? columnInfo) + private static Type? GetValueType(object value, MiniExcelColumnInfo? columnInfo) { Type type; if (columnInfo is not { Key: null }) @@ -284,7 +280,7 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) SheetId = _currentSheetIndex }; - if (format != ImageFormat.Unknown) + if (format != ImageHelper.ImageFormat.Unknown) { file.Extension = format.ToString(); file.IsImage = true; @@ -301,7 +297,7 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) return base64; } - private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) + private Tuple GetDateTimeValue(DateTime value, MiniExcelColumnInfo columnInfo) { string? cellValue; if (!ReferenceEquals(_configuration.Culture, CultureInfo.InvariantCulture)) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs similarity index 85% rename from src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs rename to src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs index a59daa33..4d0b2acc 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs @@ -1,23 +1,21 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using System.Xml.Linq; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.OpenXml.Styles; -using MiniExcelLibs.Utils; -using MiniExcelLibs.WriteAdapter; -using MiniExcelLibs.Zip; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.OpenXml; - -internal partial class ExcelOpenXmlSheetWriter : IExcelWriter +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Core.OpenXml.Styles.Builder; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Core.OpenXml.Zip; +using MiniExcelLib.Core.Reflection; +using MiniExcelLib.Core.WriteAdapters; +using DefaultSheetStyleBuilder = MiniExcelLib.Core.OpenXml.Styles.Builder.DefaultSheetStyleBuilder; +using ISheetStyleBuilder = MiniExcelLib.Core.OpenXml.Styles.Builder.ISheetStyleBuilder; +using MinimalSheetStyleBuilder = MiniExcelLib.Core.OpenXml.Styles.Builder.MinimalSheetStyleBuilder; +using SafeStreamWriter = MiniExcelLib.Core.Helpers.SafeStreamWriter; + +namespace MiniExcelLib.Core.OpenXml; + +internal partial class OpenXmlWriter : Abstractions.IMiniExcelWriter { private static readonly UTF8Encoding Utf8WithBom = new(true); @@ -33,13 +31,13 @@ internal partial class ExcelOpenXmlSheetWriter : IExcelWriter private int _currentSheetIndex = 0; - public ExcelOpenXmlSheetWriter(Stream stream, object? value, string? sheetName, IMiniExcelConfiguration? configuration, bool printHeader) + internal OpenXmlWriter(Stream stream, object? value, string? sheetName, IMiniExcelConfiguration? configuration, bool printHeader) { _stream = stream; // Why ZipArchiveMode.Update not ZipArchiveMode.Create? // R : Mode create - ZipArchiveEntry does not support seeking.' - _configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.DefaultConfig; + _configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.Default; if (_configuration is { EnableAutoWidth: true, FastMode: false }) throw new InvalidOperationException("Auto width requires fast mode to be enabled"); @@ -50,7 +48,19 @@ public ExcelOpenXmlSheetWriter(Stream stream, object? value, string? sheetName, _printHeader = printHeader; _defaultSheetName = sheetName; } - + + [CreateSyncVersion] + internal static Task CreateAsync(Stream stream, object? value, string? sheetName, bool printHeader, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(sheetName)) + throw new ArgumentException("Sheet names cannot be empty or null", nameof(sheetName)); + if (sheetName?.Length > 31) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); + + var writer = new OpenXmlWriter(stream, value, sheetName, configuration, printHeader); + return Task.FromResult(writer); + } + [CreateSyncVersion] public async Task SaveAsAsync(CancellationToken cancellationToken = default) { @@ -98,7 +108,7 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToke cancellationToken.ThrowIfCancellationRequested(); - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + using var reader = await OpenXmlReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); var sheetRecords = (await reader.GetWorkbookRelsAsync(_archive.Entries, cancellationToken).ConfigureAwait(false)).ToArray(); foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id)) { @@ -189,7 +199,7 @@ private async Task CreateSheetXmlAsync(object? values, string sheetPath, Ca #else using var zipStream = entry.Open(); #endif - using var writer = new MiniExcelStreamWriter(zipStream, Utf8WithBom, _configuration.BufferSize); + using var writer = new SafeStreamWriter(zipStream, Utf8WithBom, _configuration.BufferSize); if (values is null) { @@ -205,13 +215,13 @@ private async Task CreateSheetXmlAsync(object? values, string sheetPath, Ca } [CreateSyncVersion] - private static async Task WriteEmptySheetAsync(MiniExcelStreamWriter writer) + private static async Task WriteEmptySheetAsync(SafeStreamWriter writer) { await writer.WriteAsync(ExcelXml.EmptySheetXml).ConfigureAwait(false); } [CreateSyncVersion] - private static async Task WriteDimensionPlaceholderAsync(MiniExcelStreamWriter writer) + private static async Task WriteDimensionPlaceholderAsync(SafeStreamWriter writer) { var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension).ConfigureAwait(false); await writer.WriteAsync(WorksheetXml.DimensionPlaceholder).ConfigureAwait(false); // end of code will be replaced @@ -220,7 +230,7 @@ private static async Task WriteDimensionPlaceholderAsync(MiniExcelStreamWr } [CreateSyncVersion] - private static async Task WriteDimensionAsync(MiniExcelStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + private static async Task WriteDimensionAsync(SafeStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) { // Flush and save position so that we can get back again. var position = await writer.FlushAsync().ConfigureAwait(false); @@ -232,7 +242,7 @@ private static async Task WriteDimensionAsync(MiniExcelStreamWriter writer, int } [CreateSyncVersion] - private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object values, CancellationToken cancellationToken) + private async Task WriteValuesAsync(SafeStreamWriter writer, object values, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -245,11 +255,13 @@ private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object va var count = 0; var isKnownCount = writeAdapter is not null && writeAdapter.TryGetKnownCount(out count); - List props; + List props; #if SYNC_ONLY props = writeAdapter?.GetColumns(); #else - props = writeAdapter is not null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); + props = writeAdapter is not null + ? writeAdapter?.GetColumns() + : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); #endif if (props is null) @@ -319,6 +331,8 @@ private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object va else { #if !SYNC_ONLY +#pragma warning disable CA2007 + //todo: why does this throw compiler error even if ConfigureAwait(false) is present? await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); @@ -330,6 +344,7 @@ private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object va } await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); } +#pragma warning restore CA2007 #endif } maxRowIndex = currentRowIndex; @@ -360,7 +375,7 @@ private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object va } [CreateSyncVersion] - private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, int count, CancellationToken cancellationToken = default) + private static async Task WriteColumnWidthPlaceholdersAsync(SafeStreamWriter writer, int count, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -370,7 +385,7 @@ private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStrea } [CreateSyncVersion] - private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable? columnWidths, CancellationToken cancellationToken = default) + private static async Task OverwriteColumnWidthPlaceholdersAsync(SafeStreamWriter writer, long placeholderPosition, IEnumerable? columnWidths, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -384,7 +399,7 @@ private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamW } [CreateSyncVersion] - private static async Task WriteColumnsWidthsAsync(MiniExcelStreamWriter writer, IEnumerable? columnWidths, CancellationToken cancellationToken = default) + private static async Task WriteColumnsWidthsAsync(SafeStreamWriter writer, IEnumerable? columnWidths, CancellationToken cancellationToken = default) { var hasWrittenStart = false; @@ -408,7 +423,7 @@ private static async Task WriteColumnsWidthsAsync(MiniExcelStreamWriter writer, } [CreateSyncVersion] - private async Task PrintHeaderAsync(MiniExcelStreamWriter writer, List props, CancellationToken cancellationToken = default) + private async Task PrintHeaderAsync(SafeStreamWriter writer, List props, CancellationToken cancellationToken = default) { const int yIndex = 1; await writer.WriteAsync(WorksheetXml.StartRow(yIndex), cancellationToken).ConfigureAwait(false); @@ -422,7 +437,7 @@ private async Task PrintHeaderAsync(MiniExcelStreamWriter writer, List /// Only takes effect when the image is in AbsoluteAnchor floating mode /// public Point Location { get; set; } public XlsxImgType ImgType { get; set; } - internal int ColumnNumber => ReferenceHelper.ConvertCellToXY(CellAddress).Item1 -1; - internal int RowNumber => ReferenceHelper.ConvertCellToXY(CellAddress).Item2 - 1; + + internal int ColumnNumber => ReferenceHelper.ConvertCellToCoordinates(CellAddress).Item1 -1; + internal int RowNumber => ReferenceHelper.ConvertCellToCoordinates(CellAddress).Item2 - 1; public int WidthPx { get; set; } = 80; public int HeightPx { get; set; } = 24; diff --git a/src/MiniExcel/Picture/MiniExcelPictureImplement.cs b/src/MiniExcel.Core/OpenXml/Picture/OpenXmlPictureImplement.cs similarity index 94% rename from src/MiniExcel/Picture/MiniExcelPictureImplement.cs rename to src/MiniExcel.Core/OpenXml/Picture/OpenXmlPictureImplement.cs index 4274495e..602658aa 100644 --- a/src/MiniExcel/Picture/MiniExcelPictureImplement.cs +++ b/src/MiniExcel.Core/OpenXml/Picture/OpenXmlPictureImplement.cs @@ -1,17 +1,8 @@ -using MiniExcelLibs.Enums; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Zip; -using System; -using System.Drawing; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.Picture; +using System.Drawing; +using MiniExcelLib.Core.Enums; +using MiniExcelLib.Core.OpenXml.Zip; + +namespace MiniExcelLib.Core.OpenXml.Picture; internal static partial class MiniExcelPictureImplement { @@ -39,8 +30,8 @@ private static bool CheckRelationshipExists(XmlDocument doc, string id, string t public static async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) { // get sheets - using var excelArchive = new ExcelOpenXmlZip(excelStream); - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(excelStream, null, cancellationToken).ConfigureAwait(false); + using var excelArchive = new OpenXmlZip(excelStream); + using var reader = await OpenXmlReader.CreateAsync(excelStream, null, cancellationToken).ConfigureAwait(false); using var archive = new ZipArchive(excelStream, ZipArchiveMode.Update, true); var sheetEntries = (await reader.GetWorkbookRelsAsync(excelArchive.EntryCollection, cancellationToken).ConfigureAwait(false))?.ToList() ?? []; @@ -144,8 +135,9 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c var heightPx = image.HeightPx; var imgtype = image.ImgType; var location = image.Location; - // Step 1: Add image to /xl/media/ - var imageName = $"image{Guid.NewGuid():N}.png"; + + // Step 1: Add image to /xl/media/ + var imageName = $"image{Guid.NewGuid():N}.png"; var imagePath = $"xl/media/{imageName}"; var imageEntry = archive.CreateEntry(imagePath); @@ -179,7 +171,7 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c // Step 3: Add anchor to drawing XML var relId = $"rId{Guid.NewGuid():N}"; - drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype,location); + drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype, location); // Step 4: Add image relationship to drawing rels var relNode = drawingRelsDoc.CreateElement("Relationship", drawingRelsDoc.DocumentElement.NamespaceURI); @@ -197,8 +189,8 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c public static void AddPicture(Stream excelStream, params MiniExcelPicture[] images) { // get sheets - using var excelArchive = new ExcelOpenXmlZip(excelStream); - using var reader = ExcelOpenXmlSheetReader.Create(excelStream, null); + using var excelArchive = new OpenXmlZip(excelStream); + using var reader = OpenXmlReader.Create(excelStream, null); using var archive = new ZipArchive(excelStream, ZipArchiveMode.Update, true); var sheetEntries = reader.GetWorkbookRels(excelArchive.EntryCollection)?.ToList() ?? []; @@ -208,7 +200,7 @@ public static void AddPicture(Stream excelStream, params MiniExcelPicture[] imag { var sheetName = sheetGroup.Key; var sheetEnt = sheetEntries.Find(x => x.Name == sheetName) ?? sheetEntries[0]; - var sheetXmlName = sheetEnt.Path.Split('/').Last().Split('.')[0]; + var sheetXmlName = Enumerable.Last(sheetEnt.Path.Split('/')).Split('.')[0]; var sheetPath = $"xl/worksheets/{sheetXmlName}.xml"; var sheetEntry = archive.GetEntry(sheetPath); @@ -302,6 +294,7 @@ public static void AddPicture(Stream excelStream, params MiniExcelPicture[] imag var heightPx = image.HeightPx; var imgtype = image.ImgType; var location = image.Location; + // Step 1: Add image to /xl/media/ var imageName = $"image{Guid.NewGuid():N}.png"; var imagePath = $"xl/media/{imageName}"; @@ -326,7 +319,7 @@ public static void AddPicture(Stream excelStream, params MiniExcelPicture[] imag // Step 3: Add anchor to drawing XML var relId = $"rId{Guid.NewGuid():N}"; - drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype,location); + drawingDoc = CreateDrawingXml(drawingDoc, col, row, widthPx, heightPx, relId, imgtype, location); // Step 4: Add image relationship to drawing rels var relNode = drawingRelsDoc.CreateElement("Relationship", drawingRelsDoc.DocumentElement.NamespaceURI); @@ -382,7 +375,7 @@ private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, in { return DrawingXmlHelper.CreateOrUpdateDrawingXml(existingDoc, col, row, widthPx, heightPx, relId); } - private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, int row, int widthPx, int heightPx, string relId,XlsxImgType imgtype,Point location) + private static XmlDocument CreateDrawingXml(XmlDocument existingDoc, int col, int row, int widthPx, int heightPx, string relId,XlsxImgType imgtype, Point location) { return DrawingXmlHelper.CreateOrUpdateDrawingXml(existingDoc, col, row, widthPx, heightPx, relId,imgtype, location); } @@ -408,13 +401,13 @@ private static string GetColumnName(int colIndex) return columnName; } public static XmlDocument CreateOrUpdateDrawingXml( - XmlDocument? existingDoc, - int col, int row, - int widthPx, int heightPx, - string relId, - XlsxImgType imgType, - Point Location -) + XmlDocument? existingDoc, + int col, int row, + int widthPx, int heightPx, + string relId, + XlsxImgType imgType, + Point Location + ) { var doc = existingDoc ?? new XmlDocument(); var ns = new XmlNamespaceManager(doc.NameTable); @@ -597,8 +590,8 @@ Point Location return doc; } - - public static XmlDocument CreateOrUpdateDrawingXml( + + public static XmlDocument CreateOrUpdateDrawingXml( XmlDocument? existingDoc, int col, int row, int widthPx, int heightPx, diff --git a/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/DefaultSheetStyleBuilder.cs similarity index 97% rename from src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs rename to src/MiniExcel.Core/OpenXml/Styles/Builder/DefaultSheetStyleBuilder.cs index 399c2624..3f4d387e 100644 --- a/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/DefaultSheetStyleBuilder.cs @@ -1,10 +1,9 @@ -using System.Threading.Tasks; +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; -namespace MiniExcelLibs.OpenXml.Styles; - -internal partial class DefaultSheetStyleBuilder : SheetStyleBuilderBase +internal partial class DefaultSheetStyleBuilder(SheetStyleBuildContext context, OpenXmlStyleOptions styleOptions) + : SheetStyleBuilderBase(context) { - private static readonly SheetStyleElementInfos GenerateElementInfos = new SheetStyleElementInfos + private static readonly SheetStyleElementInfos GenerateElementInfos = new() { NumFmtCount = 0,//The default NumFmt number is 0, but there will be NumFmt dynamically generated based on ColumnsToApply FontCount = 2, @@ -14,21 +13,15 @@ internal partial class DefaultSheetStyleBuilder : SheetStyleBuilderBase CellXfCount = 5 }; - private readonly SheetStyleBuildContext _context; - private OpenXmlStyleOptions _styleOptions; - - public DefaultSheetStyleBuilder(SheetStyleBuildContext context, OpenXmlStyleOptions styleOptions) : base(context) - { - _context = context; - _styleOptions = styleOptions; - } + private readonly SheetStyleBuildContext _context = context; + private readonly OpenXmlStyleOptions _styleOptions = styleOptions; protected override SheetStyleElementInfos GetGenerateElementInfos() { return GenerateElementInfos; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateNumFmtAsync() { const int numFmtIndex = 166; @@ -47,7 +40,7 @@ protected override async Task GenerateNumFmtAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateFontAsync() { /* @@ -105,7 +98,7 @@ protected override async Task GenerateFontAsync() await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateFillAsync() { /* @@ -147,7 +140,7 @@ protected override async Task GenerateFillAsync() await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateBorderAsync() { /* @@ -259,7 +252,7 @@ protected override async Task GenerateBorderAsync() await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateCellStyleXfAsync() { /* @@ -326,7 +319,7 @@ protected override async Task GenerateCellStyleXfAsync() await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateCellXfAsync() { /* diff --git a/src/MiniExcel.Core/OpenXml/Styles/Builder/ISheetStyleBuilder.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/ISheetStyleBuilder.cs new file mode 100644 index 00000000..7e8685a6 --- /dev/null +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/ISheetStyleBuilder.cs @@ -0,0 +1,7 @@ +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; + +internal partial interface ISheetStyleBuilder +{ + [CreateSyncVersion] + Task BuildAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/MinimalSheetStyleBuilder.cs similarity index 87% rename from src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs rename to src/MiniExcel.Core/OpenXml/Styles/Builder/MinimalSheetStyleBuilder.cs index b80a2415..89f6e766 100644 --- a/src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/MinimalSheetStyleBuilder.cs @@ -1,10 +1,8 @@ -using System.Threading.Tasks; +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; -namespace MiniExcelLibs.OpenXml.Styles; - -internal partial class MinimalSheetStyleBuilder : SheetStyleBuilderBase +internal partial class MinimalSheetStyleBuilder(SheetStyleBuildContext context) : SheetStyleBuilderBase(context) { - internal static SheetStyleElementInfos GenerateElementInfos = new SheetStyleElementInfos + internal static SheetStyleElementInfos GenerateElementInfos = new() { NumFmtCount = 0,//默认的NumFmt数量是0,但是会有根据ColumnsToApply动态生成的NumFmt FontCount = 1, @@ -14,19 +12,14 @@ internal partial class MinimalSheetStyleBuilder : SheetStyleBuilderBase CellXfCount = 5 }; - private readonly SheetStyleBuildContext _context; - - public MinimalSheetStyleBuilder(SheetStyleBuildContext context) : base(context) - { - _context = context; - } + private readonly SheetStyleBuildContext _context = context; protected override SheetStyleElementInfos GetGenerateElementInfos() { return GenerateElementInfos; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateNumFmtAsync() { const int numFmtIndex = 166; @@ -45,7 +38,7 @@ protected override async Task GenerateNumFmtAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateFontAsync() { /* @@ -55,7 +48,7 @@ protected override async Task GenerateFontAsync() await _context.NewXmlWriter.WriteFullEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateFillAsync() { /* @@ -65,7 +58,7 @@ protected override async Task GenerateFillAsync() await _context.NewXmlWriter.WriteFullEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateBorderAsync() { /* @@ -75,7 +68,7 @@ protected override async Task GenerateBorderAsync() await _context.NewXmlWriter.WriteFullEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateCellStyleXfAsync() { /* @@ -85,7 +78,7 @@ protected override async Task GenerateCellStyleXfAsync() await _context.NewXmlWriter.WriteFullEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected override async Task GenerateCellXfAsync() { /* diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildContext.cs similarity index 85% rename from src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs rename to src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildContext.cs index 287faba5..e24453f6 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildContext.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildContext.cs @@ -1,43 +1,35 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.Zip; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; - -namespace MiniExcelLibs.OpenXml.Styles; +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Zip; + +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; internal class SheetStyleBuildContext : IDisposable { - private static readonly string _emptyStylesXml = ExcelOpenXmlUtils.MinifyXml - (@" - - - " - ); + private static readonly string EmptyStylesXml = XmlHelper.MinifyXml( + """ + + + + """); private readonly Dictionary _zipDictionary; private readonly MiniExcelZipArchive _archive; private readonly Encoding _encoding; - private readonly ICollection _columns; + private readonly ICollection _columns; - private StringReader _emptyStylesXmlStringReader; + private StringReader? _emptyStylesXmlStringReader; private ZipArchiveEntry? _oldStyleXmlZipEntry; private ZipArchiveEntry? _newStyleXmlZipEntry; - private Stream _oldXmlReaderStream; - private Stream _newXmlWriterStream; + private Stream? _oldXmlReaderStream; + private Stream? _newXmlWriterStream; private bool _initialized; private bool _finalized; private bool _disposed; - public SheetStyleBuildContext(Dictionary zipDictionary, MiniExcelZipArchive archive, Encoding encoding, ICollection columns) + public SheetStyleBuildContext(Dictionary zipDictionary, MiniExcelZipArchive archive, Encoding encoding, ICollection columns) { _zipDictionary = zipDictionary; _archive = archive; @@ -45,11 +37,11 @@ public SheetStyleBuildContext(Dictionary zipDictionary, _columns = columns; } - public XmlReader OldXmlReader { get; private set; } - public XmlWriter NewXmlWriter { get; private set; } + public XmlReader? OldXmlReader { get; private set; } + public XmlWriter? NewXmlWriter { get; private set; } public SheetStyleElementInfos OldElementInfos { get; private set; } public SheetStyleElementInfos GenerateElementInfos { get; private set; } - public IEnumerable ColumnsToApply { get; private set; } + public IEnumerable ColumnsToApply { get; private set; } public int CustomFormatCount { get; private set; } public void Initialize(SheetStyleElementInfos generateElementInfos) @@ -62,7 +54,7 @@ public void Initialize(SheetStyleElementInfos generateElementInfos) { using (var oldStyleXmlStream = _oldStyleXmlZipEntry.Open()) { - using XmlReader reader = XmlReader.Create(oldStyleXmlStream, new XmlReaderSettings { IgnoreWhitespace = true }); + using var reader = XmlReader.Create(oldStyleXmlStream, new XmlReaderSettings { IgnoreWhitespace = true }); OldElementInfos = ReadSheetStyleElementInfos(reader); } @@ -75,7 +67,7 @@ public void Initialize(SheetStyleElementInfos generateElementInfos) { OldElementInfos = new SheetStyleElementInfos(); - _emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); + _emptyStylesXmlStringReader = new StringReader(EmptyStylesXml); OldXmlReader = XmlReader.Create(_emptyStylesXmlStringReader, new XmlReaderSettings { IgnoreWhitespace = true }); _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); @@ -122,7 +114,7 @@ public async Task InitializeAsync(SheetStyleElementInfos generateElementInfos, C else { OldElementInfos = new SheetStyleElementInfos(); - _emptyStylesXmlStringReader = new StringReader(_emptyStylesXml); + _emptyStylesXmlStringReader = new StringReader(EmptyStylesXml); OldXmlReader = XmlReader.Create(_emptyStylesXmlStringReader, new XmlReaderSettings { IgnoreWhitespace = true, Async = true }); _newStyleXmlZipEntry = _archive.CreateEntry(ExcelFileNames.Styles, CompressionLevel.Fastest); @@ -153,7 +145,7 @@ public void FinalizeAndUpdateZipDictionary() try { - OldXmlReader.Dispose(); + OldXmlReader?.Dispose(); OldXmlReader = null; _oldXmlReaderStream?.Dispose(); _oldXmlReaderStream = null; @@ -161,12 +153,12 @@ public void FinalizeAndUpdateZipDictionary() _emptyStylesXmlStringReader?.Dispose(); _emptyStylesXmlStringReader = null; - NewXmlWriter.Flush(); - NewXmlWriter.Close(); - NewXmlWriter.Dispose(); + NewXmlWriter?.Flush(); + NewXmlWriter?.Close(); + NewXmlWriter?.Dispose(); NewXmlWriter = null; - _newXmlWriterStream.Dispose(); + _newXmlWriterStream?.Dispose(); _newXmlWriterStream = null; if (_oldStyleXmlZipEntry is null) @@ -211,7 +203,7 @@ public async Task FinalizeAndUpdateZipDictionaryAsync(CancellationToken cancella { cancellationToken.ThrowIfCancellationRequested(); - OldXmlReader.Dispose(); + OldXmlReader?.Dispose(); OldXmlReader = null; #if NET5_0_OR_GREATER if (_oldXmlReaderStream is not null) @@ -219,7 +211,7 @@ public async Task FinalizeAndUpdateZipDictionaryAsync(CancellationToken cancella await _oldXmlReaderStream.DisposeAsync().ConfigureAwait(false); } #else - _oldXmlReaderStream?.Dispose(); + _oldXmlReaderStream?.Dispose(); #endif _oldXmlReaderStream = null; @@ -233,7 +225,7 @@ public async Task FinalizeAndUpdateZipDictionaryAsync(CancellationToken cancella #if NET5_0_OR_GREATER await NewXmlWriter.DisposeAsync().ConfigureAwait(false); #else - NewXmlWriter.Dispose(); + NewXmlWriter.Dispose(); #endif NewXmlWriter = null; @@ -241,7 +233,7 @@ public async Task FinalizeAndUpdateZipDictionaryAsync(CancellationToken cancella #if NET5_0_OR_GREATER await _newXmlWriterStream.DisposeAsync().ConfigureAwait(false); #else - _newXmlWriterStream.Dispose(); + _newXmlWriterStream?.Dispose(); #endif _newXmlWriterStream = null; diff --git a/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildResult.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildResult.cs new file mode 100644 index 00000000..47b63da7 --- /dev/null +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuildResult.cs @@ -0,0 +1,6 @@ +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; + +internal class SheetStyleBuildResult(Dictionary cellXfIdMap) +{ + public Dictionary CellXfIdMap { get; set; } = cellXfIdMap; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderBase.cs similarity index 86% rename from src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs rename to src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderBase.cs index 9673c7df..10129602 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderBase.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderBase.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; -namespace MiniExcelLibs.OpenXml.Styles; - -internal abstract partial class SheetStyleBuilderBase : ISheetStyleBuilder +internal abstract partial class SheetStyleBuilderBase(SheetStyleBuildContext context) : ISheetStyleBuilder { - internal static readonly Dictionary _allElements = new Dictionary + private readonly SheetStyleBuildContext _context = context; + + internal static readonly Dictionary AllElements = new() { ["numFmts"] = 0, ["fonts"] = 1, @@ -21,15 +18,9 @@ internal abstract partial class SheetStyleBuilderBase : ISheetStyleBuilder ["extLst"] = 9 }; - private readonly SheetStyleBuildContext _context; - - public SheetStyleBuilderBase(SheetStyleBuildContext context) - { - _context = context; - } // Todo: add CancellationToken to all methods called inside of BuildAsync - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public virtual async Task BuildAsync(CancellationToken cancellationToken = default) { await _context.InitializeAsync(GetGenerateElementInfos(), cancellationToken).ConfigureAwait(false); @@ -87,7 +78,7 @@ public virtual async Task BuildAsync(CancellationToken ca protected abstract SheetStyleElementInfos GetGenerateElementInfos(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task WriteAttributesAsync(string element, CancellationToken cancellationToken = default) { if (_context.OldXmlReader.NodeType is XmlNodeType.Element || _context.OldXmlReader.NodeType is XmlNodeType.XmlDeclaration) @@ -150,49 +141,49 @@ protected virtual async Task WriteAttributesAsync(string element, CancellationTo } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateElementBeforStartElementAsync() { - if (!_allElements.TryGetValue(_context.OldXmlReader.LocalName, out var elementIndex)) + if (!AllElements.TryGetValue(_context.OldXmlReader.LocalName, out var elementIndex)) { return; } - if (!_context.OldElementInfos.ExistsNumFmts && !_context.GenerateElementInfos.ExistsNumFmts && _allElements["numFmts"] < elementIndex) + if (!_context.OldElementInfos.ExistsNumFmts && !_context.GenerateElementInfos.ExistsNumFmts && AllElements["numFmts"] < elementIndex) { await GenerateNumFmtsAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsNumFmts = true; } - else if (!_context.OldElementInfos.ExistsFonts && !_context.GenerateElementInfos.ExistsFonts && _allElements["fonts"] < elementIndex) + else if (!_context.OldElementInfos.ExistsFonts && !_context.GenerateElementInfos.ExistsFonts && AllElements["fonts"] < elementIndex) { await GenerateFontsAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsFonts = true; } - else if (!_context.OldElementInfos.ExistsFills && !_context.GenerateElementInfos.ExistsFills && _allElements["fills"] < elementIndex) + else if (!_context.OldElementInfos.ExistsFills && !_context.GenerateElementInfos.ExistsFills && AllElements["fills"] < elementIndex) { await GenerateFillsAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsFills = true; } - else if (!_context.OldElementInfos.ExistsBorders && !_context.GenerateElementInfos.ExistsBorders && _allElements["borders"] < elementIndex) + else if (!_context.OldElementInfos.ExistsBorders && !_context.GenerateElementInfos.ExistsBorders && AllElements["borders"] < elementIndex) { await GenerateBordersAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsBorders = true; } - else if (!_context.OldElementInfos.ExistsCellStyleXfs && !_context.GenerateElementInfos.ExistsCellStyleXfs && _allElements["cellStyleXfs"] < elementIndex) + else if (!_context.OldElementInfos.ExistsCellStyleXfs && !_context.GenerateElementInfos.ExistsCellStyleXfs && AllElements["cellStyleXfs"] < elementIndex) { await GenerateCellStyleXfsAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsCellStyleXfs = true; } - else if (!_context.OldElementInfos.ExistsCellXfs && !_context.GenerateElementInfos.ExistsCellXfs && _allElements["cellXfs"] < elementIndex) + else if (!_context.OldElementInfos.ExistsCellXfs && !_context.GenerateElementInfos.ExistsCellXfs && AllElements["cellXfs"] < elementIndex) { await GenerateCellXfsAsync().ConfigureAwait(false); _context.GenerateElementInfos.ExistsCellXfs = true; } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateElementBeforEndElementAsync() { - switch (_context.OldXmlReader.LocalName) + switch (_context.OldXmlReader?.LocalName) { case "styleSheet" when !_context.OldElementInfos.ExistsNumFmts && !_context.GenerateElementInfos.ExistsNumFmts: await GenerateNumFmtsAsync().ConfigureAwait(false); @@ -218,7 +209,7 @@ protected virtual async Task GenerateElementBeforEndElementAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateNumFmtsAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "numFmts", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -232,10 +223,10 @@ protected virtual async Task GenerateNumFmtsAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateNumFmtAsync(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateFontsAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fonts", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -249,10 +240,10 @@ protected virtual async Task GenerateFontsAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateFontAsync(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateFillsAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fills", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -266,10 +257,10 @@ protected virtual async Task GenerateFillsAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateFillAsync(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateBordersAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "borders", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -283,10 +274,10 @@ protected virtual async Task GenerateBordersAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateBorderAsync(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateCellStyleXfsAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "cellStyleXfs", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -300,10 +291,10 @@ protected virtual async Task GenerateCellStyleXfsAsync() } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateCellStyleXfAsync(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected virtual async Task GenerateCellXfsAsync() { await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "cellXfs", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); @@ -312,7 +303,7 @@ protected virtual async Task GenerateCellXfsAsync() await _context.NewXmlWriter.WriteFullEndElementAsync().ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] protected abstract Task GenerateCellXfAsync(); private Dictionary GetCellXfIdMap() diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderHelper.cs similarity index 52% rename from src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs rename to src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderHelper.cs index d18df3cb..1e4a60c9 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/Builder/SheetStyleBuilderHelper.cs @@ -1,26 +1,26 @@ -using System.Collections.Generic; -using System.Linq; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.OpenXml.Utils; -namespace MiniExcelLibs.OpenXml.Styles; +namespace MiniExcelLib.Core.OpenXml.Styles.Builder; public static class SheetStyleBuilderHelper { - public static IEnumerable GenerateStyleIds(int startUpCellXfs, ICollection? dynamicColumns) + public static IEnumerable GenerateStyleIds(int startUpCellXfs, ICollection? dynamicColumns) { if (dynamicColumns is null) yield break; int index = 0; var cols = dynamicColumns - .Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid) + .Where(x => !string.IsNullOrWhiteSpace(x.Format) && new OpenXmlNumberFormatHelper(x.Format).IsValid) .GroupBy(x => x.Format); foreach (var g in cols) { - foreach ( var col in g ) + foreach (var col in g) + { col.FormatId = startUpCellXfs + index; + } yield return g.First(); index++; diff --git a/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs b/src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyleOptions.cs similarity index 63% rename from src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs rename to src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyleOptions.cs index 48e3e490..68261879 100644 --- a/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyleOptions.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Styles; public class OpenXmlStyleOptions { diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs b/src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyles.cs similarity index 94% rename from src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs rename to src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyles.cs index 24ccb242..be53a3ed 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/OpenXmlStyles.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Core.OpenXml.Zip; +using XmlReaderHelper = MiniExcelLib.Core.OpenXml.Utils.XmlReaderHelper; -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Styles; -internal class ExcelOpenXmlStyles +internal class OpenXmlStyles { private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; @@ -14,7 +13,7 @@ internal class ExcelOpenXmlStyles private readonly Dictionary _cellStyleXfs = new(); private readonly Dictionary _customFormats = new(); - public ExcelOpenXmlStyles(ExcelOpenXmlZip zip) + public OpenXmlStyles(OpenXmlZip zip) { using var reader = zip.GetXmlReader("xl/styles.xml"); diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleElementInfos.cs b/src/MiniExcel.Core/OpenXml/Styles/SheetStyleElementInfos.cs similarity index 87% rename from src/MiniExcel/OpenXml/Styles/SheetStyleElementInfos.cs rename to src/MiniExcel.Core/OpenXml/Styles/SheetStyleElementInfos.cs index e7f76421..3ab807e8 100644 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleElementInfos.cs +++ b/src/MiniExcel.Core/OpenXml/Styles/SheetStyleElementInfos.cs @@ -1,28 +1,17 @@ -namespace MiniExcelLibs.OpenXml.Styles; +namespace MiniExcelLib.Core.OpenXml.Styles; public class SheetStyleElementInfos { public bool ExistsNumFmts { get; set; } - public int NumFmtCount { get; set; } - public bool ExistsFonts { get; set; } - public int FontCount { get; set; } - public bool ExistsFills { get; set; } - public int FillCount { get; set; } - public bool ExistsBorders { get; set; } - public int BorderCount { get; set; } - public bool ExistsCellStyleXfs { get; set; } - public int CellStyleXfCount { get; set; } - public bool ExistsCellXfs { get; set; } - public int CellXfCount { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs similarity index 93% rename from src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs rename to src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs index 9bc90610..6bb25e03 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.Impl.cs @@ -1,23 +1,10 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.Utils; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.OpenXml.Constants; - -namespace MiniExcelLibs.SaveByTemplate; +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Core.Reflection; + +namespace MiniExcelLib.Core.OpenXml.Templates; #region Utils internal class XRowInfo @@ -110,27 +97,37 @@ public string ToXmlString(string prefix) => $"<{prefix}mergeCell ref=\"{ColumnHelper.GetAlphabetColumnName(X1)}{Y1}:{ColumnHelper.GetAlphabetColumnName(X2)}{Y2}\"/>"; } -internal class MergeCellIndex +internal class MergeCellIndex(int rowStart, int rowEnd) { - public int RowStart { get; set; } - public int RowEnd { get; set; } - - public MergeCellIndex(int rowStart, int rowEnd) - { - RowStart = rowStart; - RowEnd = rowEnd; - } + public int RowStart { get; } = rowStart; + public int RowEnd { get; } = rowEnd; } internal class XChildNode { - public string InnerText { get; set; } + public string? InnerText { get; set; } public string ColIndex { get; set; } public int RowIndex { get; set; } } + +internal struct Range +{ + public int StartColumn { get; set; } + public int StartRow { get; set; } + public int EndColumn { get; set; } + public int EndRow { get; set; } + + public bool ContainsRow(int row) => StartRow <= row && row <= EndRow; +} + +internal class ConditionalFormatRange +{ + public XmlNode? Node { get; set; } + public List Ranges { get; set; } = []; +} #endregion -internal partial class ExcelOpenXmlTemplate +internal partial class OpenXmlTemplate { private List _xRowInfos; private readonly List _calcChainCellRefs = []; @@ -138,19 +135,19 @@ internal partial class ExcelOpenXmlTemplate private List _newXMergeCellInfos; #if NET7_0_OR_GREATER - [GeneratedRegex("([A-Z]+)([0-9]+)")] private static partial Regex CellRegexImpl(); - private static readonly Regex CellRegex = CellRegexImpl(); - [GeneratedRegex(@"\{\{(.*?)\}\}")] private static partial Regex TemplateRegexImpl(); - private static readonly Regex TemplateRegex = TemplateRegexImpl(); - [GeneratedRegex(@".*?\{\{.*?\}\}.*?")] private static partial Regex NonTemplateRegexImpl(); - private static readonly Regex NonTemplateRegex = NonTemplateRegexImpl(); + [GeneratedRegex("([A-Z]+)([0-9]+)")] private static partial Regex CellRegexImpl(); + private static readonly Regex CellRegex = CellRegexImpl(); + [GeneratedRegex(@"\{\{(.*?)\}\}")] private static partial Regex TemplateRegexImpl(); + private static readonly Regex TemplateRegex = TemplateRegexImpl(); + [GeneratedRegex(@".*?\{\{.*?\}\}.*?")] private static partial Regex NonTemplateRegexImpl(); + private static readonly Regex NonTemplateRegex = NonTemplateRegexImpl(); #else private static readonly Regex CellRegex = new("([A-Z]+)([0-9]+)", RegexOptions.Compiled); private static readonly Regex TemplateRegex = new(@"\{\{(.*?)\}\}", RegexOptions.Compiled); private static readonly Regex NonTemplateRegex = new(@".*?\{\{.*?\}\}.*?", RegexOptions.Compiled); #endif - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] private async Task GenerateSheetXmlImplByUpdateModeAsync(ZipArchiveEntry sheetZipEntry, Stream stream, Stream sheetStream, IDictionary inputMaps, IDictionary sharedStrings, bool mergeCells = false, CancellationToken cancellationToken = default) { var doc = new XmlDocument(); @@ -161,7 +158,6 @@ private async Task GenerateSheetXmlImplByUpdateModeAsync(ZipArchiveEntry sheetZi sheetStream.Dispose(); #endif - sheetZipEntry.Delete(); // ZipArchiveEntry can't update directly, so need to delete then create logic var worksheet = doc.SelectSingleNode("/x:worksheet", Ns); @@ -175,8 +171,7 @@ private async Task GenerateSheetXmlImplByUpdateModeAsync(ZipArchiveEntry sheetZi await WriteSheetXmlAsync(stream, doc, sheetData, mergeCells, cancellationToken).ConfigureAwait(false); } - - + private void GenerateSheetXmlImplByCreateMode(ZipArchiveEntry templateSheetZipEntry, Stream outputZipSheetEntryStream, Stream outputSheetStream, IDictionary inputMaps, IDictionary sharedStrings, bool mergeCells = false) { var doc = new XmlDocument(); @@ -276,35 +271,7 @@ private static IEnumerable ParseConditionalFormatRanges( } } - private class MergeCellIndex - { - public int RowStart { get; set; } - public int RowEnd { get; set; } - - public MergeCellIndex(int rowStart, int rowEnd) - { - RowStart = rowStart; - RowEnd = rowEnd; - } - } - - private struct Range - { - public int StartColumn { get; set; } - public int StartRow { get; set; } - public int EndColumn { get; set; } - public int EndRow { get; set; } - - public bool ContainsRow(int row) => StartRow <= row && row <= EndRow; - } - - private class ConditionalFormatRange - { - public XmlNode Node { get; set; } - public List Ranges { get; set; } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] private async Task WriteSheetXmlAsync(Stream outputFileStream, XmlDocument doc, XmlNode sheetData, bool mergeCells = false, CancellationToken cancellationToken = default) { //Q.Why so complex? @@ -619,7 +586,7 @@ class GenerateCellValuesContext } //todo: refactor in a way that needs less parameters - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] private async Task GenerateCellValuesAsync(GenerateCellValuesContext generateCellValuesContext, string endPrefix, StreamWriter writer, StringBuilder rowXml, int mergeRowCount, bool isHeaderRow, XRowInfo rowInfo, XmlElement row, int groupingRowDiff, @@ -780,11 +747,11 @@ private async Task GenerateCellValuesAsync(GenerateCe else if (type?.IsEnum ?? false) { var description = CustomPropertyHelper.DescriptionAttr(type, cellValue); - cellValueStr = ExcelOpenXmlUtils.EncodeXml(description); + cellValueStr = XmlHelper.EncodeXml(description); } else { - cellValueStr = ExcelOpenXmlUtils.EncodeXml(cellValue?.ToString()); + cellValueStr = XmlHelper.EncodeXml(cellValue?.ToString()); if (!isDictOrTable && TypeHelper.IsNumericType(type)) { if (decimal.TryParse(cellValueStr, out var decimalValue)) @@ -1054,11 +1021,11 @@ private void ProcessFormulas(StringBuilder rowXml, int rowIndex) continue; var fNode = c.OwnerDocument.CreateElement("f", Schemas.SpreadsheetmlXmlns); - fNode.InnerText = v.InnerText.Substring(2); + fNode.InnerText = v.InnerText[2..]; c.InsertBefore(fNode, v); c.RemoveChild(v); - var celRef = ExcelOpenXmlUtils.ConvertXYToCell(ci + 1, rowIndex); + var celRef = ReferenceHelper.ConvertCoordinatesToCell(ci + 1, rowIndex); _calcChainCellRefs.Add(celRef); } } @@ -1070,8 +1037,8 @@ private void ProcessFormulas(StringBuilder rowXml, int rowIndex) private static string? ConvertToDateTimeString(PropertyInfo? propInfo, object cellValue) { //TODO:c.SetAttribute("t", "d"); and custom format - var format = propInfo?.GetAttributeValue((ExcelFormatAttribute x) => x.Format) - ?? propInfo?.GetAttributeValue((ExcelColumnAttribute x) => x.Format) + var format = propInfo?.GetAttributeValue((MiniExcelFormatAttribute x) => x.Format) + ?? propInfo?.GetAttributeValue((MiniExcelColumnAttribute x) => x.Format) ?? "yyyy-MM-dd HH:mm:ss"; return (cellValue as DateTime?)?.ToString(format); diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.MergeCells.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.MergeCells.cs similarity index 72% rename from src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.MergeCells.cs rename to src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.MergeCells.cs index 82e4cc8e..ab990744 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.MergeCells.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.MergeCells.cs @@ -1,34 +1,24 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; +using MiniExcelLib.Core.OpenXml.Zip; -namespace MiniExcelLibs.SaveByTemplate; +namespace MiniExcelLib.Core.OpenXml.Templates; -internal partial class ExcelOpenXmlTemplate +internal partial class OpenXmlTemplate { - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task MergeSameCellsAsync(string path, CancellationToken cancellationToken = default) { - using var stream = FileHelper.OpenSharedRead(path); + using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); await MergeSameCellsImplAsync(stream, cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task MergeSameCellsAsync(byte[] fileInBytes, CancellationToken cancellationToken = default) { using Stream stream = new MemoryStream(fileInBytes); await MergeSameCellsImplAsync(stream, cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] private async Task MergeSameCellsImplAsync(Stream stream, CancellationToken cancellationToken = default) { await stream.CopyToAsync(_outputFileStream @@ -37,8 +27,8 @@ await stream.CopyToAsync(_outputFileStream #endif ).ConfigureAwait(false); - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_outputFileStream, null, cancellationToken: cancellationToken).ConfigureAwait(false); - using var archive = new ExcelOpenXmlZip(_outputFileStream, mode: ZipArchiveMode.Update, true, Encoding.UTF8); + using var reader = await OpenXmlReader.CreateAsync(_outputFileStream, null, cancellationToken: cancellationToken).ConfigureAwait(false); + using var archive = new OpenXmlZip(_outputFileStream, mode: ZipArchiveMode.Update, true, Encoding.UTF8); //read sharedString var sharedStrings = reader.SharedStrings; diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.cs similarity index 79% rename from src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs rename to src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.cs index 5fab5039..d6e9e083 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlTemplate.cs @@ -1,21 +1,12 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.SaveByTemplate; - -internal partial class ExcelOpenXmlTemplate : IExcelTemplate +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.OpenXml.Constants; +using MiniExcelLib.Core.OpenXml.Zip; +using CalcChainHelper = MiniExcelLib.Core.OpenXml.Utils.CalcChainHelper; +using IMiniExcelTemplate = MiniExcelLib.Core.Abstractions.IMiniExcelTemplate; + +namespace MiniExcelLib.Core.OpenXml.Templates; + +internal partial class OpenXmlTemplate : IMiniExcelTemplate { #if NET7_0_OR_GREATER [GeneratedRegex("(?<={{).*?(?=}})")] private static partial Regex ExpressionRegex(); @@ -30,50 +21,54 @@ internal partial class ExcelOpenXmlTemplate : IExcelTemplate private readonly IInputValueExtractor _inputValueExtractor; private readonly StringBuilder _calcChainContent = new(); - static ExcelOpenXmlTemplate() + static OpenXmlTemplate() { Ns = new XmlNamespaceManager(new NameTable()); Ns.AddNamespace("x", Schemas.SpreadsheetmlXmlns); Ns.AddNamespace("x14ac", Schemas.SpreadsheetmlXmlX14Ac); } - public ExcelOpenXmlTemplate(Stream stream, IMiniExcelConfiguration? configuration, InputValueExtractor inputValueExtractor) + internal OpenXmlTemplate(Stream stream, IMiniExcelConfiguration? configuration, OpenXmlValueExtractor inputValueExtractor) { _outputFileStream = stream; - _configuration = (OpenXmlConfiguration?)configuration ?? OpenXmlConfiguration.DefaultConfig; + _configuration = (OpenXmlConfiguration?)configuration ?? OpenXmlConfiguration.Default; _inputValueExtractor = inputValueExtractor; } [CreateSyncVersion] public async Task SaveAsByTemplateAsync(string templatePath, object value, CancellationToken cancellationToken = default) { - using var stream = FileHelper.OpenSharedRead(templatePath); - await SaveAsByTemplateImplAsync(stream, value, cancellationToken).ConfigureAwait(false); + using var stream = File.Open(templatePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + await SaveAsByTemplateAsync(stream, value, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] public async Task SaveAsByTemplateAsync(byte[] templateBytes, object value, CancellationToken cancellationToken = default) { using Stream stream = new MemoryStream(templateBytes); - await SaveAsByTemplateImplAsync(stream, value, cancellationToken).ConfigureAwait(false); + await SaveAsByTemplateAsync(stream, value, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] - internal async Task SaveAsByTemplateImplAsync(Stream templateStream, object value, CancellationToken cancellationToken = default) + public async Task SaveAsByTemplateAsync(Stream templateStream, object value, CancellationToken cancellationToken = default) { - // foreach all templateStream and create file for _outputFileStream and not create sheet file - templateStream.Position = 0; - using var templateReader = await ExcelOpenXmlSheetReader.CreateAsync(templateStream, null, cancellationToken: cancellationToken).ConfigureAwait(false); - using var outputFileArchive = new ExcelOpenXmlZip(_outputFileStream, mode: ZipArchiveMode.Create, true, Encoding.UTF8, isUpdateMode: false); + if(!templateStream.CanSeek) + throw new ArgumentException("The template stream must be seekable"); + + templateStream.Seek(0, SeekOrigin.Begin); + using var templateReader = await OpenXmlReader.CreateAsync(templateStream, null, cancellationToken: cancellationToken).ConfigureAwait(false); + using var outputFileArchive = new OpenXmlZip(_outputFileStream, mode: ZipArchiveMode.Create, true, Encoding.UTF8, isUpdateMode: false); + try { outputFileArchive.EntryCollection = templateReader.Archive.ZipFile.Entries; //TODO:need to remove } catch (InvalidDataException e) { - throw new InvalidDataException($"An invalid valid OpenXml zip archive was detected, please check or open an issue for this error: {e.Message}"); + throw new InvalidDataException($"An invalid OpenXml zip archive was detected, please check or open an issue for this error: {e.Message}"); } + // foreach all templateStream and create file for _outputFileStream and not create sheet file foreach (var entry in templateReader.Archive.ZipFile.Entries) { outputFileArchive.Entries.Add(entry.FullName.Replace('\\', '/'), entry); @@ -110,7 +105,7 @@ internal async Task SaveAsByTemplateImplAsync(Stream templateStream, object valu await originalEntryStream.CopyToAsync(newEntryStream #if NETCOREAPP2_1_OR_GREATER - , cancellationToken + , cancellationToken #endif ).ConfigureAwait(false); } diff --git a/src/MiniExcel/SaveByTemplate/InputValueExtractor.cs b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlValueExtractor.cs similarity index 69% rename from src/MiniExcel/SaveByTemplate/InputValueExtractor.cs rename to src/MiniExcel.Core/OpenXml/Templates/OpenXmlValueExtractor.cs index 28c1856a..50a49056 100644 --- a/src/MiniExcel/SaveByTemplate/InputValueExtractor.cs +++ b/src/MiniExcel.Core/OpenXml/Templates/OpenXmlValueExtractor.cs @@ -1,41 +1,38 @@ -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Reflection; -using MiniExcelLibs.Utils; - -namespace MiniExcelLibs.SaveByTemplate; - -public class InputValueExtractor : IInputValueExtractor -{ - public IDictionary ToValueDictionary(object valueObject) - => valueObject is Dictionary valueDictionary - ? GetValuesFromDictionary(valueDictionary) - : GetValuesFromObject(valueObject); - - private static IDictionary GetValuesFromDictionary(Dictionary valueDictionary) - { - return valueDictionary.ToDictionary( - x => x.Key, - x => x.Value is IDataReader dataReader - ? TypeHelper.ConvertToEnumerableDictionary(dataReader).ToList() - : x.Value); - } - - private static IDictionary GetValuesFromObject(object valueObject) - { - var type = valueObject.GetType(); - - var propertyValues = type - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(property => new { property.Name, Value = property.GetValue(valueObject) }); - - var fieldValues = type - .GetFields(BindingFlags.Public | BindingFlags.Instance) - .Select(field => new { field.Name, Value = field.GetValue(valueObject) }); - - return propertyValues - .Concat(fieldValues) - .ToDictionary(x => x.Name, x => x.Value); - } +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Helpers; + +namespace MiniExcelLib.Core.OpenXml.Templates; + +public class OpenXmlValueExtractor : IInputValueExtractor +{ + public IDictionary ToValueDictionary(object valueObject) + => valueObject is Dictionary valueDictionary + ? GetValuesFromDictionary(valueDictionary) + : GetValuesFromObject(valueObject); + + private static Dictionary GetValuesFromDictionary(Dictionary valueDictionary) + { + return valueDictionary.ToDictionary( + x => x.Key, + x => x.Value is IDataReader dataReader + ? TypeHelper.ConvertToEnumerableDictionary(dataReader).ToList() + : x.Value)!; + } + + private static Dictionary GetValuesFromObject(object valueObject) + { + var type = valueObject.GetType(); + + var propertyValues = type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(property => new { property.Name, Value = property.GetValue(valueObject) }); + + var fieldValues = type + .GetFields(BindingFlags.Public | BindingFlags.Instance) + .Select(field => new { field.Name, Value = field.GetValue(valueObject) }); + + return propertyValues + .Concat(fieldValues) + .ToDictionary(x => x.Name, x => x.Value); + } } \ No newline at end of file diff --git a/src/MiniExcel/Utils/CalcChainHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/CalcChainHelper.cs similarity index 86% rename from src/MiniExcel/Utils/CalcChainHelper.cs rename to src/MiniExcel.Core/OpenXml/Utils/CalcChainHelper.cs index 6066bb4b..c2bae0ce 100644 --- a/src/MiniExcel/Utils/CalcChainHelper.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/CalcChainHelper.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.OpenXml.Utils; internal static partial class CalcChainHelper { @@ -27,7 +20,7 @@ public static string GetCalcChainContent( List cellRefs, int sheetIndex return calcChainContent.ToString(); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public static async Task GenerateCalcChainSheetAsync(Stream calcChainStream, string calcChainContent, CancellationToken cancellationToken = default) { using var writer = new StreamWriter(calcChainStream, Encoding.UTF8); diff --git a/src/MiniExcel/Utils/DateTimeHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/DateTimeHelper.cs similarity index 82% rename from src/MiniExcel/Utils/DateTimeHelper.cs rename to src/MiniExcel.Core/OpenXml/Utils/DateTimeHelper.cs index 7741cefc..dac80e70 100644 --- a/src/MiniExcel/Utils/DateTimeHelper.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/DateTimeHelper.cs @@ -1,11 +1,11 @@ -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.OpenXml.Utils; public static class DateTimeHelper { /// /// NumberFormat from NuGet ExcelNumberFormat MIT@License /// - public static bool IsDateTimeFormat(string formatCode) => new ExcelNumberFormat(formatCode).IsDateTimeFormat; + public static bool IsDateTimeFormat(string formatCode) => new OpenXmlNumberFormatHelper(formatCode).IsDateTimeFormat; /**Below Code from ExcelDataReader @MIT License**/ // All OA dates must be strictly in between OADateMinAsDouble and OADateMaxAsDouble diff --git a/src/MiniExcel.Core/OpenXml/Utils/GeneralHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/GeneralHelper.cs new file mode 100644 index 00000000..1f19995a --- /dev/null +++ b/src/MiniExcel.Core/OpenXml/Utils/GeneralHelper.cs @@ -0,0 +1,57 @@ +namespace MiniExcelLib.Core.OpenXml.Utils; + +internal static class GeneralHelper +{ + public static int GetCellColumnIndex(string cell) + { + const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const int mode = 26; + + var x = 0; + var cellLetter = GetCellColumnLetter(cell); + //AA=27,ZZ=702 + foreach (var t in cellLetter) + x = x * mode + keys.IndexOf(t); + + return x; + } + + public static int GetCellRowNumber(string cell) + { + if (string.IsNullOrEmpty(cell)) + throw new Exception("cell is null or empty"); + + var cellNumber = string.Empty; + foreach (var t in cell) + { + if (char.IsDigit(t)) + cellNumber += t; + } + return int.Parse(cellNumber); + } + + public static string GetCellColumnLetter(string cell) + { + string GetCellLetter = string.Empty; + foreach (var t in cell) + { + if (char.IsLetter(t)) + GetCellLetter += t; + } + return GetCellLetter; + } + + public static string ConvertColumnName(int x) + { + int dividend = x; + string columnName = string.Empty; + + while (dividend > 0) + { + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; + } + return columnName; + } +} \ No newline at end of file diff --git a/src/MiniExcel.Core/OpenXml/Utils/MiniExcelPropertyHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/MiniExcelPropertyHelper.cs new file mode 100644 index 00000000..0e153d97 --- /dev/null +++ b/src/MiniExcel.Core/OpenXml/Utils/MiniExcelPropertyHelper.cs @@ -0,0 +1,39 @@ +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.OpenXml.Models; + +namespace MiniExcelLib.Core.OpenXml.Utils; + +internal static class ExcelPropertyHelper +{ + internal static ExcellSheetInfo GetExcellSheetInfo(Type type, MiniExcelBaseConfiguration configuration) + { + // default options + var sheetInfo = new ExcellSheetInfo + { + Key = type.Name, + ExcelSheetName = null, // will be generated automatically as Sheet + ExcelSheetState = SheetState.Visible + }; + + // options from ExcelSheetAttribute + if (type.GetCustomAttribute(typeof(MiniExcelSheetAttribute)) is MiniExcelSheetAttribute excelSheetAttr) + { + sheetInfo.ExcelSheetName = excelSheetAttr.Name ?? type.Name; + sheetInfo.ExcelSheetState = excelSheetAttr.State; + } + + // options from DynamicSheets configuration + var openXmlCOnfiguration = configuration as OpenXmlConfiguration; + if (openXmlCOnfiguration?.DynamicSheets?.Length > 0) + { + var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(x => x.Key == type.Name); + if (dynamicSheet is not null) + { + sheetInfo.ExcelSheetName = dynamicSheet.Name; + sheetInfo.ExcelSheetState = dynamicSheet.State; + } + } + + return sheetInfo; + } +} \ No newline at end of file diff --git a/src/MiniExcel/Utils/ExcelNumberFormat.cs b/src/MiniExcel.Core/OpenXml/Utils/OpenXmlNumberFormatHelper.cs similarity index 95% rename from src/MiniExcel/Utils/ExcelNumberFormat.cs rename to src/MiniExcel.Core/OpenXml/Utils/OpenXmlNumberFormatHelper.cs index fd9fae76..813067bc 100644 --- a/src/MiniExcel/Utils/ExcelNumberFormat.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/OpenXmlNumberFormatHelper.cs @@ -1,21 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.OpenXml.Utils; /// /// This code edit from https://github.com/andersnm/ExcelNumberFormat /// -internal class ExcelNumberFormat +internal class OpenXmlNumberFormatHelper { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The number format string. - public ExcelNumberFormat(string formatString) + public OpenXmlNumberFormatHelper(string formatString) { var sections = Parser.ParseSections(formatString, out bool syntaxError); @@ -30,7 +24,7 @@ public ExcelNumberFormat(string formatString) } else { - Sections = new List
(); + Sections = []; } } @@ -100,26 +94,18 @@ internal enum SectionType internal class Section { public int SectionIndex { get; set; } - public SectionType Type { get; set; } - public List? GeneralTextDateDurationParts { get; set; } } internal class FractionSection { public List IntegerPart { get; set; } - public List Numerator { get; set; } - public List DenominatorPrefix { get; set; } - public List Denominator { get; set; } - public int DenominatorConstant { get; set; } - public List DenominatorSuffix { get; set; } - public List FractionSuffix { get; set; } public static bool TryParse(List tokens, out FractionSection? format) @@ -167,7 +153,7 @@ public static bool TryParse(List tokens, out FractionSection? format) return true; } - static void GetNumerator(List tokens, out List? integerPart, out List? numeratorPart) + private static void GetNumerator(List tokens, out List? integerPart, out List? numeratorPart) { var hasPlaceholder = false; var hasSpace = false; @@ -256,7 +242,7 @@ private static bool TryGetDenominator(List? tokens, out List? de var token = tokens[index]; if (hasPlaceholder && Token.IsPlaceholder(token)) { - ; // OK + // OK } else if (hasConstant && (Token.IsDigit09(token))) diff --git a/src/MiniExcel/Utils/ReferenceHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/ReferenceHelper.cs similarity index 81% rename from src/MiniExcel/Utils/ReferenceHelper.cs rename to src/MiniExcel.Core/OpenXml/Utils/ReferenceHelper.cs index eb467eac..4cd4135f 100644 --- a/src/MiniExcel/Utils/ReferenceHelper.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/ReferenceHelper.cs @@ -1,8 +1,4 @@ -using System; -using System.Globalization; -using System.Linq; - -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.OpenXml.Utils; internal static class ReferenceHelper { @@ -21,7 +17,7 @@ public static string GetCellLetter(string cell) } /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static (int, int) ConvertCellToXY(string? cell) + public static (int, int) ConvertCellToCoordinates(string? cell) { const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int mode = 26; @@ -35,7 +31,7 @@ public static (int, int) ConvertCellToXY(string? cell) } /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static string ConvertXyToCell(int x, int y) + public static string ConvertCoordinatesToCell(int x, int y) { int dividend = x; string columnName = string.Empty; @@ -78,17 +74,18 @@ public static bool ParseReference(string value, out int column, out int row) continue; } - if (!char.IsLetter(c)) + if (char.IsLetter(c)) { - if (char.IsDigit(c)) - break; - return false; + row = 0; + column = 0; + position = 0; + break; } - row = 0; - column = 0; - position = 0; - break; + if (char.IsDigit(c)) + break; + + return false; } if (position == 0) diff --git a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs similarity index 93% rename from src/MiniExcel/OpenXml/SharedStringsDiskCache.cs rename to src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs index a02cd3f2..bae9245c 100644 --- a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/SharedStringsDiskCache.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace MiniExcelLibs.OpenXml; +namespace MiniExcelLib.Core.OpenXml.Utils; internal class SharedStringsDiskCache : IDictionary, IDisposable { diff --git a/src/MiniExcel/Utils/XmlReaderHelper.cs b/src/MiniExcel.Core/OpenXml/Utils/XmlReaderHelper.cs similarity index 59% rename from src/MiniExcel/Utils/XmlReaderHelper.cs rename to src/MiniExcel.Core/OpenXml/Utils/XmlReaderHelper.cs index 61b0e1e6..e65e60ed 100644 --- a/src/MiniExcel/Utils/XmlReaderHelper.cs +++ b/src/MiniExcel.Core/OpenXml/Utils/XmlReaderHelper.cs @@ -1,16 +1,11 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Zomp.SyncMethodGenerator; +using MiniExcelLib.Core.OpenXml.Constants; -namespace MiniExcelLibs.Utils; +namespace MiniExcelLib.Core.OpenXml.Utils; internal static partial class XmlReaderHelper { + private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; + /// /// Pass <?xml> and <worksheet> /// @@ -125,7 +120,7 @@ public static async IAsyncEnumerable GetSharedStringsAsync(Stream stream { if (IsStartElement(reader, "si", nss)) { - var value = await StringHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); + var value = await ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); yield return value; } else if (!await SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) @@ -135,6 +130,70 @@ public static async IAsyncEnumerable GetSharedStringsAsync(Stream stream } } + + /// + /// Copied and modified from ExcelDataReader - @MIT License + /// + [CreateSyncVersion] + public static async Task ReadStringItemAsync(XmlReader reader, CancellationToken cancellationToken = default) + { + var result = new StringBuilder(); + if (!await ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return string.Empty; + + while (!reader.EOF) + { + if (IsStartElement(reader, "t", Ns)) + { + // There are multiple in a . Concatenate within an . + result.Append(await reader.ReadElementContentAsStringAsync() +#if NET6_0_OR_GREATER + .WaitAsync(cancellationToken) +#endif + .ConfigureAwait(false)); + } + else if (IsStartElement(reader, "r", Ns)) + { + result.Append(await ReadRichTextRunAsync(reader, cancellationToken).ConfigureAwait(false)); + } + else if (!await SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + return result.ToString(); + } + + /// + /// Copied and modified from ExcelDataReader - @MIT License + /// + [CreateSyncVersion] + private static async Task ReadRichTextRunAsync(XmlReader reader, CancellationToken cancellationToken = default) + { + var result = new StringBuilder(); + if (!await ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return string.Empty; + + while (!reader.EOF) + { + if (IsStartElement(reader, "t", Ns)) + { + result.Append(await reader.ReadElementContentAsStringAsync() +#if NET6_0_OR_GREATER + .WaitAsync(cancellationToken) +#endif + .ConfigureAwait(false)); + } + else if (!await SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + return result.ToString(); + } + internal static XmlReaderSettings GetXmlReaderSettings(bool async) => new() { IgnoreComments = true, diff --git a/src/MiniExcel/Zip/MiniExcelZipArchive.cs b/src/MiniExcel.Core/OpenXml/Zip/MiniExcelZipArchive.cs similarity index 61% rename from src/MiniExcel/Zip/MiniExcelZipArchive.cs rename to src/MiniExcel.Core/OpenXml/Zip/MiniExcelZipArchive.cs index 0d729adc..9df07bdc 100644 --- a/src/MiniExcel/Zip/MiniExcelZipArchive.cs +++ b/src/MiniExcel.Core/OpenXml/Zip/MiniExcelZipArchive.cs @@ -1,16 +1,11 @@ -using System; -using System.IO; -using System.IO.Compression; -using System.Text; - -namespace MiniExcelLibs.Zip; +namespace MiniExcelLib.Core.OpenXml.Zip; public class MiniExcelZipArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding entryNameEncoding) : ZipArchive(stream, mode, leaveOpen, entryNameEncoding) { public new void Dispose() { - Dispose(disposing: true); + base.Dispose(disposing: true); GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/src/MiniExcel/Zip/ExcelOpenXmlZip.cs b/src/MiniExcel.Core/OpenXml/Zip/OpenXmlZip.cs similarity index 80% rename from src/MiniExcel/Zip/ExcelOpenXmlZip.cs rename to src/MiniExcel.Core/OpenXml/Zip/OpenXmlZip.cs index 90557dfa..abc594fd 100644 --- a/src/MiniExcel/Zip/ExcelOpenXmlZip.cs +++ b/src/MiniExcel.Core/OpenXml/Zip/OpenXmlZip.cs @@ -1,15 +1,9 @@ -using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; -using System.IO.Compression; -using System.Text; -using System.Xml; -namespace MiniExcelLibs.Zip; +namespace MiniExcelLib.Core.OpenXml.Zip; /// Copy & modified by ExcelDataReader ZipWorker @MIT License -internal class ExcelOpenXmlZip : IDisposable +internal class OpenXmlZip : IDisposable { private bool _disposed; @@ -25,7 +19,7 @@ internal class ExcelOpenXmlZip : IDisposable XmlResolver = null, }; - public ExcelOpenXmlZip(Stream fileStream, ZipArchiveMode mode = ZipArchiveMode.Read, bool leaveOpen = false, Encoding? entryNameEncoding = null, bool isUpdateMode = true) + public OpenXmlZip(Stream fileStream, ZipArchiveMode mode = ZipArchiveMode.Read, bool leaveOpen = false, Encoding? entryNameEncoding = null, bool isUpdateMode = true) { entryNameEncoding ??= Encoding.UTF8; ZipFile = new MiniExcelZipArchive(fileStream, mode, leaveOpen, entryNameEncoding); @@ -60,7 +54,7 @@ public ExcelOpenXmlZip(Stream fileStream, ZipArchiveMode mode = ZipArchiveMode.R return entry is not null ? XmlReader.Create(entry.Open(), XmlSettings) : null; } - ~ExcelOpenXmlZip() + ~OpenXmlZip() { Dispose(false); } diff --git a/src/MiniExcel/Zip/ZipPackageInfo.cs b/src/MiniExcel.Core/OpenXml/Zip/ZipPackageInfo.cs similarity index 76% rename from src/MiniExcel/Zip/ZipPackageInfo.cs rename to src/MiniExcel.Core/OpenXml/Zip/ZipPackageInfo.cs index 912ae5e9..4cee1d50 100644 --- a/src/MiniExcel/Zip/ZipPackageInfo.cs +++ b/src/MiniExcel.Core/OpenXml/Zip/ZipPackageInfo.cs @@ -1,6 +1,4 @@ -using System.IO.Compression; - -namespace MiniExcelLibs.Zip; +namespace MiniExcelLib.Core.OpenXml.Zip; internal class ZipPackageInfo(ZipArchiveEntry zipArchiveEntry, string contentType) { diff --git a/src/MiniExcel.Core/Properties/AssemblyInfo.cs b/src/MiniExcel.Core/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..2ce2a434 --- /dev/null +++ b/src/MiniExcel.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +#if PLAT_SKIP_LOCALS_INIT +[module: System.Runtime.CompilerServices.SkipLocalsInit] +#endif diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel.Core/Reflection/CustomPropertyHelper.cs similarity index 66% rename from src/MiniExcel/Utils/CustomPropertyHelper.cs rename to src/MiniExcel.Core/Reflection/CustomPropertyHelper.cs index 621b5052..06df7707 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel.Core/Reflection/CustomPropertyHelper.cs @@ -1,50 +1,12 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.OpenXml.Models; - -namespace MiniExcelLibs.Utils; - -internal class ExcelColumnInfo -{ - public object Key { get; set; } - public int? ExcelColumnIndex { get; set; } - public string? ExcelColumnName { get; set; } - public string[]? ExcelColumnAliases { get; set; } = []; - public Property Property { get; set; } - public Type ExcludeNullableType { get; set; } - public bool Nullable { get; internal set; } - public string? ExcelFormat { get; internal set; } - public double? ExcelColumnWidth { get; internal set; } - public string? ExcelIndexName { get; internal set; } - public bool ExcelIgnore { get; internal set; } - public int ExcelFormatId { get; internal set; } - public ColumnType ExcelColumnType { get; internal set; } - public Func? CustomFormatter { get; set; } -} - -internal class ExcellSheetInfo -{ - public object Key { get; set; } - public string? ExcelSheetName { get; set; } - public SheetState ExcelSheetState { get; set; } - - private string ExcelSheetStateAsString => ExcelSheetState.ToString().ToLower(); +using System.ComponentModel; +using MiniExcelLib.Core.Attributes; +using MiniExcelLib.Core.Helpers; - public SheetDto ToDto(int sheetIndex) - { - return new SheetDto { Name = ExcelSheetName, SheetIdx = sheetIndex, State = ExcelSheetStateAsString }; - } -} +namespace MiniExcelLib.Core.Reflection; -internal static class CustomPropertyHelper +public static class CustomPropertyHelper { - internal static IDictionary GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex) + public static IDictionary GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex) { var cell = new Dictionary(); for (int i = startCellIndex; i <= maxColumnIndex; i++) @@ -60,7 +22,7 @@ internal static class CustomPropertyHelper return cell; } - internal static IDictionary GetEmptyExpandoObject(Dictionary hearrows) + public static IDictionary GetEmptyExpandoObject(Dictionary hearrows) { var cell = new Dictionary(); foreach (var hr in hearrows) @@ -76,7 +38,7 @@ internal static class CustomPropertyHelper return cell; } - internal static List GetSaveAsProperties(this Type type, MiniExcelConfiguration configuration) + private static List GetSaveAsProperties(this Type type, MiniExcelBaseConfiguration configuration) { var props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance, configuration) .Where(prop => prop.Property.CanRead) @@ -88,7 +50,7 @@ internal static List GetSaveAsProperties(this Type type, MiniEx return SortCustomProps(props); } - internal static List SortCustomProps(List props) + private static List SortCustomProps(List props) { // https://github.com/mini-software/MiniExcel/issues/142 //TODO: need optimize performance @@ -104,7 +66,7 @@ internal static List GetSaveAsProperties(this Type type, MiniEx var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex is null or -1).ToList(); var index = 0; - var newProps = new List(); + var newProps = new List(); for (int i = 0; i <= maxColumnIndex; i++) { var p1 = withCustomIndexProps.SingleOrDefault(s => s.ExcelColumnIndex == i); @@ -130,13 +92,13 @@ internal static List GetSaveAsProperties(this Type type, MiniEx return newProps; } - internal static List GetExcelCustomPropertyInfos(Type type, string[] keys, MiniExcelConfiguration configuration) + internal static List GetExcelCustomPropertyInfos(Type type, string[] keys, MiniExcelBaseConfiguration configuration) { const BindingFlags flags = BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance; var props = GetExcelPropertyInfo(type, flags, configuration) .Where(prop => prop?.Property.Info.GetSetMethod() is not null // why not .Property.CanWrite? because it will use private setter - && !prop.Property.Info.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) - && !prop.Property.Info.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore)) + && !prop.Property.Info.GetAttributeValue((MiniExcelIgnoreAttribute x) => x.Ignore) + && !prop.Property.Info.GetAttributeValue((MiniExcelColumnAttribute x) => x.Ignore)) .ToList() /*ignore without set*/; if (props.Count == 0) @@ -169,38 +131,38 @@ internal static List GetSaveAsProperties(this Type type, MiniEx ?? name; } - private static IEnumerable ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, MiniExcelConfiguration configuration) + private static IEnumerable ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, MiniExcelBaseConfiguration configuration) { // solve : https://github.com/mini-software/MiniExcel/issues/138 var columnInfos = props.Select(p => { var gt = Nullable.GetUnderlyingType(p.PropertyType); - var excelColumnName = p.GetAttribute(); + var excelColumnName = p.GetAttribute(); var excludeNullableType = gt ?? p.PropertyType; - var excelFormat = p.GetAttribute()?.Format; - var excelColumn = p.GetAttribute(); - var dynamicColumn = configuration?.DynamicColumns?.SingleOrDefault(_ => _.Key == p.Name); + var excelFormat = p.GetAttribute()?.Format; + var excelColumn = p.GetAttribute(); + var dynamicColumn = configuration?.DynamicColumns?.SingleOrDefault(dc => dc.Key == p.Name); if (dynamicColumn is not null) excelColumn = dynamicColumn; - var ignore = p.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) || - p.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore) || + var ignore = p.GetAttributeValue((MiniExcelIgnoreAttribute x) => x.Ignore) || + p.GetAttributeValue((MiniExcelColumnAttribute x) => x.Ignore) || (excelColumn?.Ignore ?? false); if (ignore) return null; //TODO:or configulation Dynamic int? excelColumnIndex = excelColumn?.Index > -1 ? excelColumn.Index : null; - return new ExcelColumnInfo + return new MiniExcelColumnInfo { - Property = new Property(p), + Property = new MiniExcelProperty(p), ExcludeNullableType = excludeNullableType, Nullable = gt is not null, ExcelColumnAliases = excelColumnName?.Aliases ?? excelColumn?.Aliases ?? [], ExcelColumnName = excelColumnName?.ExcelColumnName ?? p.GetAttribute()?.DisplayName ?? excelColumn?.Name ?? p.Name, - ExcelColumnIndex = p.GetAttribute()?.ExcelColumnIndex ?? excelColumnIndex, - ExcelIndexName = p.GetAttribute()?.ExcelXName ?? excelColumn?.IndexName, - ExcelColumnWidth = p.GetAttribute()?.ExcelColumnWidth ?? excelColumn?.Width, + ExcelColumnIndex = p.GetAttribute()?.ExcelColumnIndex ?? excelColumnIndex, + ExcelIndexName = p.GetAttribute()?.ExcelXName ?? excelColumn?.IndexName, + ExcelColumnWidth = p.GetAttribute()?.ExcelColumnWidth ?? excelColumn?.Width, ExcelFormat = excelFormat ?? excelColumn?.Format, ExcelFormatId = excelColumn?.FormatId ?? -1, ExcelColumnType = excelColumn?.Type ?? ColumnType.Value, @@ -211,47 +173,15 @@ internal static List GetSaveAsProperties(this Type type, MiniEx return columnInfos.Where(x => x is not null); } - private static IEnumerable GetExcelPropertyInfo(Type type, BindingFlags bindingFlags, MiniExcelConfiguration configuration) + private static IEnumerable GetExcelPropertyInfo(Type type, BindingFlags bindingFlags, MiniExcelBaseConfiguration configuration) { //TODO:assign column index return ConvertToExcelCustomPropertyInfo(type.GetProperties(bindingFlags), configuration); } - internal static ExcellSheetInfo GetExcellSheetInfo(Type type, MiniExcelConfiguration configuration) - { - // default options - var sheetInfo = new ExcellSheetInfo - { - Key = type.Name, - ExcelSheetName = null, // will be generated automatically as Sheet - ExcelSheetState = SheetState.Visible - }; - - // options from ExcelSheetAttribute - if (type.GetCustomAttribute(typeof(ExcelSheetAttribute)) is ExcelSheetAttribute excelSheetAttr) - { - sheetInfo.ExcelSheetName = excelSheetAttr.Name ?? type.Name; - sheetInfo.ExcelSheetState = excelSheetAttr.State; - } - - // options from DynamicSheets configuration - var openXmlCOnfiguration = configuration as OpenXmlConfiguration; - if (openXmlCOnfiguration?.DynamicSheets?.Length > 0) - { - var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(x => x.Key == type.Name); - if (dynamicSheet is not null) - { - sheetInfo.ExcelSheetName = dynamicSheet.Name; - sheetInfo.ExcelSheetState = dynamicSheet.State; - } - } - - return sheetInfo; - } - - internal static List GetDictionaryColumnInfo(IDictionary? dicString, IDictionary? dic, MiniExcelConfiguration configuration) + private static List GetDictionaryColumnInfo(IDictionary? dicString, IDictionary? dic, MiniExcelBaseConfiguration configuration) { - var props = new List(); + var props = new List(); var keys = dicString?.Keys.ToList() ?? dic?.Keys @@ -265,9 +195,9 @@ internal static List GetDictionaryColumnInfo(IDictionary props, object key, MiniExcelConfiguration configuration) + private static void SetDictionaryColumnInfo(List props, object key, MiniExcelBaseConfiguration configuration) { - var p = new ExcelColumnInfo + var p = new MiniExcelColumnInfo { Key = key, ExcelColumnName = key?.ToString() @@ -310,7 +240,7 @@ internal static void SetDictionaryColumnInfo(List props, object props.Add(p); } - internal static bool TryGetTypeColumnInfo(Type? type, MiniExcelConfiguration configuration, out List? props) + internal static bool TryGetTypeColumnInfo(Type? type, MiniExcelBaseConfiguration configuration, out List? props) { // Unknown type if (type is null) @@ -331,7 +261,7 @@ internal static bool TryGetTypeColumnInfo(Type? type, MiniExcelConfiguration con props = GetSaveAsProperties(type, configuration); return true; } - internal static List GetColumnInfoFromValue(object value, MiniExcelConfiguration configuration) => value switch + internal static List GetColumnInfoFromValue(object value, MiniExcelBaseConfiguration configuration) => value switch { IDictionary genericDictionary => GetDictionaryColumnInfo(genericDictionary, null, configuration), IDictionary dictionary => GetDictionaryColumnInfo(null, dictionary, configuration), @@ -343,9 +273,9 @@ private static bool ValueIsNeededToDetermineProperties(Type type) => typeof(IDictionary).IsAssignableFrom(type) || typeof(IDictionary).IsAssignableFrom(type); - internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName, MiniExcelConfiguration configuration) + internal static MiniExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName, MiniExcelBaseConfiguration configuration) { - var prop = new ExcelColumnInfo + var prop = new MiniExcelColumnInfo { ExcelColumnName = columnName, Key = columnName diff --git a/src/MiniExcel/Reflection/MemberGetter.cs b/src/MiniExcel.Core/Reflection/MemberGetter.cs similarity index 89% rename from src/MiniExcel/Reflection/MemberGetter.cs rename to src/MiniExcel.Core/Reflection/MemberGetter.cs index 5c2929cb..233fde62 100644 --- a/src/MiniExcel/Reflection/MemberGetter.cs +++ b/src/MiniExcel.Core/Reflection/MemberGetter.cs @@ -1,8 +1,6 @@ -using System; using System.Linq.Expressions; -using System.Reflection; -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.Reflection; public class MemberGetter(PropertyInfo property) { diff --git a/src/MiniExcel/Reflection/MemberSetter.cs b/src/MiniExcel.Core/Reflection/MemberSetter.cs similarity index 91% rename from src/MiniExcel/Reflection/MemberSetter.cs rename to src/MiniExcel.Core/Reflection/MemberSetter.cs index 4eeefe41..1e81c84c 100644 --- a/src/MiniExcel/Reflection/MemberSetter.cs +++ b/src/MiniExcel.Core/Reflection/MemberSetter.cs @@ -1,8 +1,6 @@ -using System; using System.Linq.Expressions; -using System.Reflection; -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.Reflection; public class MemberSetter { diff --git a/src/MiniExcel.Core/Reflection/MiniExcelColumnInfo.cs b/src/MiniExcel.Core/Reflection/MiniExcelColumnInfo.cs new file mode 100644 index 00000000..076332a7 --- /dev/null +++ b/src/MiniExcel.Core/Reflection/MiniExcelColumnInfo.cs @@ -0,0 +1,21 @@ +using MiniExcelLib.Core.Attributes; + +namespace MiniExcelLib.Core.Reflection; + +public class MiniExcelColumnInfo +{ + public object Key { get; set; } + public int? ExcelColumnIndex { get; set; } + public string? ExcelColumnName { get; set; } + public string[]? ExcelColumnAliases { get; set; } = []; + public MiniExcelProperty Property { get; set; } + public Type ExcludeNullableType { get; set; } + public bool Nullable { get; internal set; } + public string? ExcelFormat { get; internal set; } + public double? ExcelColumnWidth { get; internal set; } + public string? ExcelIndexName { get; internal set; } + public bool ExcelIgnore { get; internal set; } + public int ExcelFormatId { get; internal set; } + public ColumnType ExcelColumnType { get; internal set; } + public Func? CustomFormatter { get; set; } +} \ No newline at end of file diff --git a/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs new file mode 100644 index 00000000..bd50802d --- /dev/null +++ b/src/MiniExcel.Core/Reflection/MiniExcelMapper.cs @@ -0,0 +1,250 @@ +using System.ComponentModel; +using MiniExcelLib.Core.Exceptions; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.OpenXml.Utils; + +namespace MiniExcelLib.Core.Reflection; + +public static partial class MiniExcelMapper +{ + [CreateSyncVersion] + public static async IAsyncEnumerable MapQueryAsync(IAsyncEnumerable> values, string startCell, bool mapHeaderAsData, bool trimColumnNames, MiniExcelBaseConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + cancellationToken.ThrowIfCancellationRequested(); + + var type = typeof(T); + + //TODO:need to optimize + List props = []; + Dictionary headersDic = []; + string[] keys = []; + var first = true; + var rowIndex = 0; + + await foreach (var item in values.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (first) + { + keys = item.Keys.ToArray(); + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + + //TODO: alert don't duplicate column name + props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); + first = false; + + // if we treat the header as data we move forwards with the mapping otherwise we jump to the next iteration + if (!mapHeaderAsData) + continue; + } + + var v = new T(); + foreach (var pInfo in props) + { + if (pInfo.ExcelColumnAliases is not null) + { + foreach (var alias in pInfo.ExcelColumnAliases) + { + if (headersDic?.TryGetValue(alias, out var columnId) ?? false) + { + var columnName = keys[columnId]; + item.TryGetValue(columnName, out var aliasItemValue); + + if (aliasItemValue is not null) + { + var newAliasValue = MapValue(v, pInfo, aliasItemValue, rowIndex, startCell, configuration); + } + } + } + } + + //Q: Why need to check every time? A: it needs to check everytime, because it's dictionary + object? itemValue = null; + if (pInfo.ExcelIndexName is not null && (keys?.Contains(pInfo.ExcelIndexName) ?? false)) + { + item.TryGetValue(pInfo.ExcelIndexName, out itemValue); + } + else if (pInfo.ExcelColumnName is not null && (headersDic?.TryGetValue(pInfo.ExcelColumnName, out var columnId) ?? false)) + { + var columnName = keys[columnId]; + item.TryGetValue(columnName, out itemValue); + } + + if (itemValue is not null) + { + var newValue = MapValue(v, pInfo, itemValue, rowIndex, startCell, configuration); + } + } + + rowIndex++; + yield return v; + } + } + + internal static object? MapValue(T v, MiniExcelColumnInfo pInfo, object itemValue, int rowIndex, string startCell, MiniExcelBaseConfiguration config) where T : class, new() + { + try + { + object? newValue = null; + if (pInfo.Nullable && string.IsNullOrWhiteSpace(itemValue?.ToString())) + { + } + else if (pInfo.ExcludeNullableType == typeof(Guid)) + { + newValue = Guid.Parse(itemValue?.ToString() ?? Guid.Empty.ToString()); + } + else if (pInfo.ExcludeNullableType == typeof(DateTimeOffset)) + { + var vs = itemValue?.ToString(); + if (pInfo.ExcelFormat is not null) + { + if (DateTimeOffset.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + newValue = value; + } + } + else if (DateTimeOffset.TryParse(vs, config.Culture, DateTimeStyles.None, out var value)) + { + newValue = value; + } + else + { + throw new InvalidCastException($"{vs} cannot be cast to DateTime"); + } + } + else if (pInfo.ExcludeNullableType == typeof(DateTime)) + { + // fix issue 257 https://github.com/mini-software/MiniExcel/issues/257 + if (itemValue is DateTime) + { + newValue = itemValue; + pInfo.Property.SetValue(v, newValue); + return newValue; + } + + var vs = itemValue?.ToString(); + if (pInfo.ExcelFormat is not null) + { + if (pInfo.Property.Info.PropertyType == typeof(DateTimeOffset) && DateTimeOffset.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var offsetValue)) + { + newValue = offsetValue; + } + else if (DateTime.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) + { + newValue = value; + } + } + else if (DateTime.TryParse(vs, config.Culture, DateTimeStyles.None, out var dtValue)) + newValue = dtValue; + else if (DateTime.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dtExactValue)) + newValue = dtExactValue; + else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var doubleValue)) + newValue = DateTime.FromOADate(doubleValue); + else + throw new InvalidCastException($"{vs} cannot be cast to DateTime"); + } + #if NET6_0_OR_GREATER + else if (pInfo.ExcludeNullableType == typeof(DateOnly)) + { + if (itemValue is DateOnly) + { + newValue = itemValue; + pInfo.Property.SetValue(v, newValue); + return newValue; + } + + var vs = itemValue?.ToString(); + if (pInfo.ExcelFormat is not null) + { + if (DateOnly.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnlyCustom)) + { + newValue = dateOnlyCustom; + } + } + else if (DateOnly.TryParse(vs, config.Culture, DateTimeStyles.None, out var dateOnly)) + newValue = dateOnly; + else if (DateOnly.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnlyExact)) + newValue = dateOnlyExact; + else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var dateOnlyDouble)) + newValue = DateOnly.FromDateTime(DateTime.FromOADate(dateOnlyDouble)); + else + throw new InvalidCastException($"{vs} cannot be cast to DateOnly"); + } + #endif + else if (pInfo.ExcludeNullableType == typeof(TimeSpan)) + { + if (itemValue is TimeSpan) + { + newValue = itemValue; + pInfo.Property.SetValue(v, newValue); + return newValue; + } + + var vs = itemValue?.ToString(); + if (pInfo.ExcelFormat is not null) + { + if (TimeSpan.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, out var value)) + { + newValue = value; + } + } + else if (TimeSpan.TryParse(vs, config.Culture, out var tsValue)) + newValue = tsValue; + else if (TimeSpan.TryParseExact(vs, @"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture, out var tsExactValue)) + newValue = tsExactValue; + else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var msValue)) + newValue = TimeSpan.FromMilliseconds(msValue); + else + throw new InvalidCastException($"{vs} cannot be cast to TimeSpan"); + } + else if (pInfo.ExcludeNullableType == typeof(double)) // && (!Regex.IsMatch(itemValue.ToString(), @"^-?\d+(\.\d+)?([eE][-+]?\d+)?$") || itemValue.ToString().Trim().Equals("NaN"))) + { + var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture); + newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value) ? value : double.NaN; + } + else if (pInfo.ExcludeNullableType == typeof(bool)) + { + var vs = itemValue?.ToString(); + newValue = vs switch + { + "1" => true, + "0" => false, + _ => bool.TryParse(vs, out var parsed) ? parsed : null + }; + } + else if (pInfo.Property.Info.PropertyType == typeof(string)) + { + newValue = XmlHelper.DecodeString(itemValue?.ToString()); + } + else if (pInfo.ExcludeNullableType.IsEnum) + { + var fieldInfo = pInfo.ExcludeNullableType.GetFields().FirstOrDefault(e => e.GetCustomAttribute(false)?.Description == itemValue?.ToString()); + var value = fieldInfo?.Name ?? itemValue?.ToString() ?? ""; + newValue = Enum.Parse(pInfo.ExcludeNullableType, value, true); + } + else if (pInfo.ExcludeNullableType == typeof(Uri)) + { + var rawValue = itemValue?.ToString(); + if (!Uri.TryCreate(rawValue, UriKind.RelativeOrAbsolute, out var uri)) + throw new InvalidCastException($"Value \"{rawValue}\" cannot be converted to Uri"); + newValue = uri; + } + else + { + // Use pInfo.ExcludeNullableType to resolve : https://github.com/mini-software/MiniExcel/issues/138 + newValue = Convert.ChangeType(itemValue, pInfo.ExcludeNullableType, config.Culture); + } + + pInfo.Property.SetValue(v, newValue); + return newValue; + } + catch (Exception ex) when (ex is InvalidCastException or FormatException) + { + var columnName = pInfo.ExcelColumnName ?? pInfo.Property.Name; + var startRowIndex = ReferenceHelper.ConvertCellToCoordinates(startCell).Item2; + var errorRow = startRowIndex + rowIndex + 1; + + var msg = $"ColumnName: {columnName}, CellRow: {errorRow}, Value: {itemValue}. The value cannot be cast to type {pInfo.Property.Info.PropertyType.Name}."; + throw new MiniExcelInvalidCastException(columnName, errorRow, itemValue, pInfo.Property.Info.PropertyType, msg); + } + } +} \ No newline at end of file diff --git a/src/MiniExcel/Reflection/Property.cs b/src/MiniExcel.Core/Reflection/MiniExcelProperty.cs similarity index 84% rename from src/MiniExcel/Reflection/Property.cs rename to src/MiniExcel.Core/Reflection/MiniExcelProperty.cs index 5b11d5a7..3c2dafe7 100644 --- a/src/MiniExcel/Reflection/Property.cs +++ b/src/MiniExcel.Core/Reflection/MiniExcelProperty.cs @@ -1,16 +1,13 @@ -using System; -using System.Reflection; - -namespace MiniExcelLibs; +namespace MiniExcelLib.Core.Reflection; public abstract class Member; -public class Property : Member +public class MiniExcelProperty : Member { private readonly MemberGetter? _getter; private readonly MemberSetter? _setter; - public Property(PropertyInfo property) + public MiniExcelProperty(PropertyInfo property) { Name = property.Name; Info = property; diff --git a/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs b/src/MiniExcel.Core/WriteAdapters/AsyncEnumerableWriteAdapter.cs similarity index 76% rename from src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs rename to src/MiniExcel.Core/WriteAdapters/AsyncEnumerableWriteAdapter.cs index c711defb..64ec78db 100644 --- a/src/MiniExcel/WriteAdapter/AsyncEnumerableWriteAdapter.cs +++ b/src/MiniExcel.Core/WriteAdapters/AsyncEnumerableWriteAdapter.cs @@ -1,20 +1,16 @@ -using MiniExcelLibs.Utils; -using System.Collections; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal class AsyncEnumerableWriteAdapter(IAsyncEnumerable values, MiniExcelConfiguration configuration) : IAsyncMiniExcelWriteAdapter +internal class AsyncEnumerableWriteAdapter(IAsyncEnumerable values, MiniExcelBaseConfiguration configuration) : IMiniExcelWriteAdapterAsync { private readonly IAsyncEnumerable _values = values; - private readonly MiniExcelConfiguration _configuration = configuration; + private readonly MiniExcelBaseConfiguration _configuration = configuration; private IAsyncEnumerator? _enumerator; private bool _empty; - public async Task?> GetColumnsAsync() + public async Task?> GetColumnsAsync() { if (CustomPropertyHelper.TryGetTypeColumnInfo(typeof(T), _configuration, out var props)) { @@ -30,7 +26,7 @@ internal class AsyncEnumerableWriteAdapter(IAsyncEnumerable values, MiniEx return CustomPropertyHelper.GetColumnInfoFromValue(_enumerator.Current, _configuration); } - public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) { if (_empty) { @@ -55,7 +51,7 @@ public async IAsyncEnumerable> GetRowsAsync(List } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - public static async IAsyncEnumerable GetRowValuesAsync(T currentValue, List props) + public static async IAsyncEnumerable GetRowValuesAsync(T currentValue, List props) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { var column = 1; diff --git a/src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs b/src/MiniExcel.Core/WriteAdapters/DataReaderWriteAdapter.cs similarity index 70% rename from src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs rename to src/MiniExcel.Core/WriteAdapters/DataReaderWriteAdapter.cs index 3aa801c8..eb678e3a 100644 --- a/src/MiniExcel/WriteAdapter/DataReaderWriteAdapter.cs +++ b/src/MiniExcel.Core/WriteAdapters/DataReaderWriteAdapter.cs @@ -1,16 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal class DataReaderWriteAdapter(IDataReader reader, MiniExcelConfiguration configuration) : IMiniExcelWriteAdapter +internal class DataReaderWriteAdapter(IDataReader reader, MiniExcelBaseConfiguration configuration) : IMiniExcelWriteAdapter { private readonly IDataReader _reader = reader; - private readonly MiniExcelConfiguration _configuration = configuration; + private readonly MiniExcelBaseConfiguration _configuration = configuration; public bool TryGetKnownCount(out int count) { @@ -18,9 +14,9 @@ public bool TryGetKnownCount(out int count) return false; } - public List GetColumns() + public List GetColumns() { - var props = new List(); + var props = new List(); for (var i = 0; i < _reader.FieldCount; i++) { var columnName = _reader.GetName(i); @@ -34,7 +30,7 @@ public List GetColumns() return props; } - public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) { while (_reader.Read()) { @@ -43,7 +39,7 @@ public IEnumerable> GetRows(List pro } } - private IEnumerable GetRowValues(List props) + private IEnumerable GetRowValues(List props) { var column = 1; for (int i = 0; i < _reader.FieldCount; i++) diff --git a/src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs b/src/MiniExcel.Core/WriteAdapters/DataTableWriteAdapter.cs similarity index 68% rename from src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs rename to src/MiniExcel.Core/WriteAdapters/DataTableWriteAdapter.cs index 7008a010..33715ced 100644 --- a/src/MiniExcel/WriteAdapter/DataTableWriteAdapter.cs +++ b/src/MiniExcel.Core/WriteAdapters/DataTableWriteAdapter.cs @@ -1,14 +1,12 @@ -using System.Collections.Generic; -using System.Data; -using System.Threading; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal class DataTableWriteAdapter(DataTable dataTable, MiniExcelConfiguration configuration) : IMiniExcelWriteAdapter +internal class DataTableWriteAdapter(DataTable dataTable, MiniExcelBaseConfiguration configuration) : IMiniExcelWriteAdapter { private readonly DataTable _dataTable = dataTable; - private readonly MiniExcelConfiguration _configuration = configuration; + private readonly MiniExcelBaseConfiguration _configuration = configuration; public bool TryGetKnownCount(out int count) { @@ -16,9 +14,9 @@ public bool TryGetKnownCount(out int count) return true; } - public List GetColumns() + public List GetColumns() { - var props = new List(); + var props = new List(); for (var i = 0; i < _dataTable.Columns.Count; i++) { var columnName = _dataTable.Columns[i].Caption ?? _dataTable.Columns[i].ColumnName; @@ -28,7 +26,7 @@ public List GetColumns() return props; } - public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) { for (int row = 0; row < _dataTable.Rows.Count; row++) { @@ -37,7 +35,7 @@ public IEnumerable> GetRows(List pro } } - private IEnumerable GetRowValues(int row, List props) + private IEnumerable GetRowValues(int row, List props) { for (int i = 0, column = 1; i < _dataTable.Columns.Count; i++, column++) { diff --git a/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs b/src/MiniExcel.Core/WriteAdapters/EnumerableWriteAdapter.cs similarity index 72% rename from src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs rename to src/MiniExcel.Core/WriteAdapters/EnumerableWriteAdapter.cs index 4ad98a78..4f64c137 100644 --- a/src/MiniExcel/WriteAdapter/EnumerableWriteAdapter.cs +++ b/src/MiniExcel.Core/WriteAdapters/EnumerableWriteAdapter.cs @@ -1,28 +1,18 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal class EnumerableWriteAdapter : IMiniExcelWriteAdapter +internal class EnumerableWriteAdapter(IEnumerable values, MiniExcelBaseConfiguration configuration) : IMiniExcelWriteAdapter { - private readonly MiniExcelConfiguration _configuration; - private readonly IEnumerable _values; - private readonly Type _genericType; + private readonly IEnumerable _values = values; + private readonly MiniExcelBaseConfiguration _configuration = configuration; + private readonly Type? _genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); private IEnumerator? _enumerator; private bool _empty; - public EnumerableWriteAdapter(IEnumerable values, MiniExcelConfiguration configuration) - { - _values = values; - _configuration = configuration; - _genericType = TypeHelper.GetGenericIEnumerables(values).FirstOrDefault(); - } - public bool TryGetKnownCount(out int count) { count = 0; @@ -35,7 +25,7 @@ public bool TryGetKnownCount(out int count) return false; } - public List? GetColumns() + public List? GetColumns() { if (CustomPropertyHelper.TryGetTypeColumnInfo(_genericType, _configuration, out var props)) return props; @@ -56,7 +46,7 @@ public bool TryGetKnownCount(out int count) } } - public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) + public IEnumerable> GetRows(List props, CancellationToken cancellationToken = default) { if (_empty) yield break; @@ -84,7 +74,7 @@ public IEnumerable> GetRows(List pro } } - public static IEnumerable GetRowValues(object currentValue, List props) + public static IEnumerable GetRowValues(object currentValue, List props) { var column = 1; foreach (var prop in props) diff --git a/src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs b/src/MiniExcel.Core/WriteAdapters/MiniExcelDataReaderWriteAdapter.cs similarity index 72% rename from src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs rename to src/MiniExcel.Core/WriteAdapters/MiniExcelDataReaderWriteAdapter.cs index d19dbca2..494b6ea3 100644 --- a/src/MiniExcel/WriteAdapter/MiniExcelDataReaderWriteAdapter.cs +++ b/src/MiniExcel.Core/WriteAdapters/MiniExcelDataReaderWriteAdapter.cs @@ -1,21 +1,16 @@ -using MiniExcelLibs.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Reflection; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal class MiniExcelDataReaderWriteAdapter(IMiniExcelDataReader reader, MiniExcelConfiguration configuration) : IAsyncMiniExcelWriteAdapter +internal class MiniExcelDataReaderWriteAdapter(IMiniExcelDataReader reader, MiniExcelBaseConfiguration configuration) : IMiniExcelWriteAdapterAsync { private readonly IMiniExcelDataReader _reader = reader; - private readonly MiniExcelConfiguration _configuration = configuration; + private readonly MiniExcelBaseConfiguration _configuration = configuration; - public async Task?> GetColumnsAsync() + public async Task?> GetColumnsAsync() { - List props = []; + List props = []; for (var i = 0; i < _reader.FieldCount; i++) { var columnName = await _reader.GetNameAsync(i).ConfigureAwait(false); @@ -36,7 +31,7 @@ internal class MiniExcelDataReaderWriteAdapter(IMiniExcelDataReader reader, Mini return props; } - public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) + public async IAsyncEnumerable> GetRowsAsync(List props, [EnumeratorCancellation] CancellationToken cancellationToken) { while (await _reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { @@ -45,7 +40,7 @@ public async IAsyncEnumerable> GetRowsAsync(List } } - private async IAsyncEnumerable GetRowValuesAsync(List props) + private async IAsyncEnumerable GetRowValuesAsync(List props) { for (int i = 0, column = 1; i < _reader.FieldCount; i++, column++) { diff --git a/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs b/src/MiniExcel.Core/WriteAdapters/MiniExcelWriteAdapterFactory.cs similarity index 72% rename from src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs rename to src/MiniExcel.Core/WriteAdapters/MiniExcelWriteAdapterFactory.cs index c3f8b9ef..07e6641a 100644 --- a/src/MiniExcel/WriteAdapter/MiniExcelWriteAdapterFactory.cs +++ b/src/MiniExcel.Core/WriteAdapters/MiniExcelWriteAdapterFactory.cs @@ -1,19 +1,17 @@ -using System; -using System.Collections; -using System.Data; -using MiniExcelLibs.Utils; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Helpers; -namespace MiniExcelLibs.WriteAdapter; +namespace MiniExcelLib.Core.WriteAdapters; -internal static class MiniExcelWriteAdapterFactory +public static class MiniExcelWriteAdapterFactory { - public static bool TryGetAsyncWriteAdapter(object values, MiniExcelConfiguration configuration, out IAsyncMiniExcelWriteAdapter? writeAdapter) + public static bool TryGetAsyncWriteAdapter(object values, MiniExcelBaseConfiguration configuration, out IMiniExcelWriteAdapterAsync? writeAdapter) { writeAdapter = null; if (values.GetType().IsAsyncEnumerable(out var genericArgument)) { var writeAdapterType = typeof(AsyncEnumerableWriteAdapter<>).MakeGenericType(genericArgument); - writeAdapter = (IAsyncMiniExcelWriteAdapter)Activator.CreateInstance(writeAdapterType, values, configuration); + writeAdapter = (IMiniExcelWriteAdapterAsync)Activator.CreateInstance(writeAdapterType, values, configuration); return true; } @@ -26,7 +24,7 @@ public static bool TryGetAsyncWriteAdapter(object values, MiniExcelConfiguration return false; } - public static IMiniExcelWriteAdapter GetWriteAdapter(object values, MiniExcelConfiguration configuration) + public static IMiniExcelWriteAdapter GetWriteAdapter(object values, MiniExcelBaseConfiguration configuration) { return values switch { diff --git a/src/MiniExcel.Csv/Api/CsvExporter.cs b/src/MiniExcel.Csv/Api/CsvExporter.cs new file mode 100644 index 00000000..c4c71702 --- /dev/null +++ b/src/MiniExcel.Csv/Api/CsvExporter.cs @@ -0,0 +1,99 @@ +using MiniExcelLib.Core; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Csv; + +public partial class CsvExporter +{ + internal CsvExporter() { } + + + #region Append / Export + + [CreateSyncVersion] + public async Task AppendAsync(string path, object value, bool printHeader = true, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + if (!File.Exists(path)) + { + var rowsWritten = await ExportAsync(path, value, printHeader, false, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + return rowsWritten.FirstOrDefault(); + } + + using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan); + return await AppendAsync(stream, value, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task AppendAsync(Stream stream, object value, CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + stream.Seek(0, SeekOrigin.End); + + var newValue = value is IEnumerable or IDataReader ? value : new[] { value }; + + using var writer = new CsvWriter(stream, newValue, false, configuration); + return await writer.InsertAsync(false, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ExportAsync(string path, object value, bool printHeader = true, bool overwriteFile = false, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew); + return await ExportAsync(stream, value, printHeader, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ExportAsync(Stream stream, object value, bool printHeader = true, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var writer = new CsvWriter(stream, value, printHeader, configuration); + return await writer.SaveAsAsync(cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region Convert + + [CreateSyncVersion] + public async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, bool csvHasHeader = false, CancellationToken cancellationToken = default) + { + var value = new CsvImporter().QueryAsync(csv, useHeaderRow: csvHasHeader, cancellationToken: cancellationToken); + + await MiniExcel.Exporters + .GetOpenXmlExporter() + .ExportAsync(xlsx, value, printHeader: csvHasHeader, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ConvertCsvToXlsxAsync(string csvPath, string xlsx, bool csvHasHeader = false, CancellationToken cancellationToken = default) + { + using var csvStream = FileHelper.OpenSharedRead(csvPath); + using var xlsxStream = new FileStream(xlsx, FileMode.CreateNew); + + await ConvertCsvToXlsxAsync(csvStream, xlsxStream, csvHasHeader, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ConvertXlsxToCsvAsync(string xlsx, string csvPath, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) + { + using var xlsxStream = FileHelper.OpenSharedRead(xlsx); + using var csvStream = new FileStream(csvPath, FileMode.CreateNew); + + await ConvertXlsxToCsvAsync(xlsxStream, csvStream, xlsxHasHeader, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) + { + var value = MiniExcel.Importers + .GetOpenXmlImporter() + .QueryAsync(xlsx, useHeaderRow: xlsxHasHeader, cancellationToken: cancellationToken) + .ConfigureAwait(false); + + await ExportAsync(csv, value, printHeader: xlsxHasHeader, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + #endregion +} \ No newline at end of file diff --git a/src/MiniExcel.Csv/Api/CsvImporter.cs b/src/MiniExcel.Csv/Api/CsvImporter.cs new file mode 100644 index 00000000..a63048f2 --- /dev/null +++ b/src/MiniExcel.Csv/Api/CsvImporter.cs @@ -0,0 +1,204 @@ +using MiniExcelLib.Core.DataReader; +using MiniExcelLib.Core.Helpers; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Csv; + +public partial class CsvImporter +{ + internal CsvImporter() { } + + + #region Query + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(string path, bool treatHeaderAsData = false, + CsvConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + where T : class, new() + { + using var stream = FileHelper.OpenSharedRead(path); + + var query = QueryAsync(stream, treatHeaderAsData, configuration, cancellationToken); + + //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable + await foreach (var item in query.ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(Stream stream, bool treatHeaderAsData = false, + CsvConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + where T : class, new() + { + using var csv = new CsvReader(stream, configuration); + await foreach (var item in csv.QueryAsync(null, "A1", treatHeaderAsData, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, + CsvConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryAsync(stream, useHeaderRow, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } + + [CreateSyncVersion] + public async IAsyncEnumerable QueryAsync(Stream stream, bool useHeaderRow = false, + CsvConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = new CsvReader(stream, configuration); + await foreach (var item in excelReader.QueryAsync(useHeaderRow, null, "A1", cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate(seed: GetNewExpandoObject(), func: AddPairToDict); + } + + #endregion + + #region Query As DataTable + + /// + /// QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + public async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await QueryAsDataTableAsync(stream, useHeaderRow, configuration, cancellationToken).ConfigureAwait(false); + } + + /// + /// QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + public async Task QueryAsDataTableAsync(Stream stream, bool useHeaderRow = true, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var dt = new DataTable(); + var first = true; + using var reader = new CsvReader(stream, configuration); + var rows = reader.QueryAsync(false, null, "A1", cancellationToken); + + var columnDict = new Dictionary(); + await foreach (var row in rows.ConfigureAwait(false)) + { + if (first) + { + foreach (var entry in row) + { + cancellationToken.ThrowIfCancellationRequested(); + + var columnName = useHeaderRow ? entry.Value?.ToString() : entry.Key; + if (!string.IsNullOrWhiteSpace(columnName)) // avoid #298 : Column '' does not belong to table + { + var column = new DataColumn(columnName, typeof(object)) { Caption = columnName }; + dt.Columns.Add(column); + columnDict.Add(entry.Key, columnName!); //same column name throw exception??? + } + } + + dt.BeginLoadData(); + first = false; + if (useHeaderRow) + { + continue; + } + } + + var newRow = dt.NewRow(); + foreach (var entry in columnDict) + { + newRow[entry.Value] = row[entry.Key]; //TODO: optimize not using string key + } + + dt.Rows.Add(newRow); + } + + dt.EndLoadData(); + return dt; + } + + #endregion + + #region Info + + [CreateSyncVersion] + public async Task> GetColumnNamesAsync(string path, bool useHeaderRow = false, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetColumnNamesAsync(stream, useHeaderRow, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public async Task> GetColumnNamesAsync(Stream stream, bool useHeaderRow = false, + CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + { +#pragma warning disable CA2007 // We need to assign the AsyncEnumerator before we can call ConfigureAwait on it + await using var enumerator = QueryAsync(stream, useHeaderRow, configuration, cancellationToken).GetAsyncEnumerator(cancellationToken); +#pragma warning restore CA2007 + + _ = enumerator.ConfigureAwait(false); + if (await enumerator.MoveNextAsync().ConfigureAwait(false)) + { + return (enumerator.Current as IDictionary)?.Keys ?? []; + } + + return []; + } + + #endregion + + #region DataReader + + public MiniExcelDataReader GetDataReader(string path, bool useHeaderRow = false, CsvConfiguration? configuration = null) + { + var stream = FileHelper.OpenSharedRead(path); + var values = Query(stream, useHeaderRow, configuration).Cast>(); + + return MiniExcelDataReader.Create(stream, values); + } + + public MiniExcelDataReader GetDataReader(Stream stream, bool useHeaderRow = false, CsvConfiguration? configuration = null) + { + var values = Query(stream, useHeaderRow, configuration).Cast>(); + return MiniExcelDataReader.Create(stream, values); + } + + public async Task GetAsyncDataReader(string path, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", CsvConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + var stream = FileHelper.OpenSharedRead(path); + var values = QueryAsync(stream, useHeaderRow, configuration, cancellationToken); + + return await MiniExcelAsyncDataReader.CreateAsync(stream, CastAsync(values, cancellationToken)).ConfigureAwait(false); + } + + public async Task GetAsyncDataReader(Stream stream, bool useHeaderRow = false, + string? sheetName = null, string startCell = "A1", CsvConfiguration? configuration = null, + CancellationToken cancellationToken = default) + { + var values = QueryAsync(stream, useHeaderRow, configuration, cancellationToken); + return await MiniExcelAsyncDataReader.CreateAsync(stream, CastAsync(values, cancellationToken)).ConfigureAwait(false); + } + + #endregion + + private static IDictionary GetNewExpandoObject() => new ExpandoObject(); + private static IDictionary AddPairToDict(IDictionary dict, KeyValuePair pair) + { + dict.Add(pair); + return dict; + } + + private static async IAsyncEnumerable> CastAsync(IAsyncEnumerable enumerable, CancellationToken cancellationToken = default) + { + await foreach (var item in enumerable.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (item is IDictionary dict) + yield return dict; + } + } +} \ No newline at end of file diff --git a/src/MiniExcel.Csv/Api/ProviderExtensions.cs b/src/MiniExcel.Csv/Api/ProviderExtensions.cs new file mode 100644 index 00000000..20f7937d --- /dev/null +++ b/src/MiniExcel.Csv/Api/ProviderExtensions.cs @@ -0,0 +1,10 @@ +using MiniExcelLib.Core; + +// ReSharper disable once CheckNamespace +namespace MiniExcelLib.Csv; + +public static class ProviderExtensions +{ + public static CsvExporter GetCsvExporter(this MiniExcelExporterProvider exporterProvider) => new(); + public static CsvImporter GetCsvImporter(this MiniExcelImporterProvider importerProvider) => new(); +} \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvConfiguration.cs b/src/MiniExcel.Csv/CsvConfiguration.cs similarity index 61% rename from src/MiniExcel/Csv/CsvConfiguration.cs rename to src/MiniExcel.Csv/CsvConfiguration.cs index 729e0059..3334f5c7 100644 --- a/src/MiniExcel/Csv/CsvConfiguration.cs +++ b/src/MiniExcel.Csv/CsvConfiguration.cs @@ -1,13 +1,13 @@ -using System; -using System.IO; -using System.Text; +using MiniExcelLib.Core; -namespace MiniExcelLibs.Csv; +namespace MiniExcelLib.Csv; -public class CsvConfiguration : MiniExcelConfiguration +public class CsvConfiguration : MiniExcelBaseConfiguration { private static readonly Encoding DefaultEncoding = new UTF8Encoding(true); + internal static CsvConfiguration Default => new(); + public char Seperator { get; set; } = ','; public string NewLine { get; set; } = "\r\n"; public bool ReadLineBreaksWithinQuotes { get; set; } = true; @@ -15,8 +15,6 @@ public class CsvConfiguration : MiniExcelConfiguration public bool AlwaysQuote { get; set; } = false; public bool QuoteWhitespaces { get; set; } = true; public Func? SplitFn { get; set; } - public Func StreamReaderFunc { get; set; } = (stream) => new StreamReader(stream, DefaultEncoding); - public Func StreamWriterFunc { get; set; } = (stream) => new StreamWriter(stream, DefaultEncoding); - - internal static readonly CsvConfiguration DefaultConfiguration = new CsvConfiguration(); + public Func StreamReaderFunc { get; set; } = stream => new StreamReader(stream, DefaultEncoding); + public Func StreamWriterFunc { get; set; } = stream => new StreamWriter(stream, DefaultEncoding); } \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvHelpers.cs b/src/MiniExcel.Csv/CsvHelper.cs similarity index 72% rename from src/MiniExcel/Csv/CsvHelpers.cs rename to src/MiniExcel.Csv/CsvHelper.cs index 54ca3b46..8ccc9a3a 100644 --- a/src/MiniExcel/Csv/CsvHelpers.cs +++ b/src/MiniExcel.Csv/CsvHelper.cs @@ -1,6 +1,6 @@ -namespace MiniExcelLibs.Csv; +namespace MiniExcelLib.Csv; -internal static class CsvHelpers +internal static class CsvHelper { /// If content contains special characters then use "{value}" format public static string ConvertToCsvValue(string? value, CsvConfiguration configuration) @@ -8,17 +8,17 @@ public static string ConvertToCsvValue(string? value, CsvConfiguration configura if (value is null) return string.Empty; - if (value.Contains("\"")) + if (value.Contains('"')) { value = value.Replace("\"", "\"\""); return $"\"{value}\""; } var shouldQuote = configuration.AlwaysQuote || - (configuration.QuoteWhitespaces && value.Contains(" ")) || + (configuration.QuoteWhitespaces && value.Contains(' ')) || value.Contains(configuration.Seperator.ToString()) || - value.Contains("\r") || - value.Contains("\n"); + value.Contains('\r') || + value.Contains('\n'); return shouldQuote ? $"\"{value}\"" : value; } diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel.Csv/CsvReader.cs similarity index 65% rename from src/MiniExcel/Csv/CsvReader.cs rename to src/MiniExcel.Csv/CsvReader.cs index 93496c05..0d0108f4 100644 --- a/src/MiniExcel/Csv/CsvReader.cs +++ b/src/MiniExcel.Csv/CsvReader.cs @@ -1,21 +1,22 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; -using System.Threading; -using MiniExcelLibs.Exceptions; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Utils; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.Csv; - -internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configuration) : IExcelReader +using MiniExcelLib.Core; +using MiniExcelLib.Core.Exceptions; +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Core.Reflection; +using IMiniExcelReader = MiniExcelLib.Core.Abstractions.IMiniExcelReader; +using MiniExcelMapper = MiniExcelLib.Core.Reflection.MiniExcelMapper; + +namespace MiniExcelLib.Csv; + +internal partial class CsvReader : IMiniExcelReader { - private readonly Stream _stream = stream; - private readonly CsvConfiguration _config = configuration is null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; + private readonly Stream _stream; + private readonly CsvConfiguration _config; + + internal CsvReader(Stream stream, IMiniExcelConfiguration? configuration) + { + _stream = stream; + _config = configuration as CsvConfiguration ?? CsvConfiguration.Default; + } [CreateSyncVersion] public async IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, [EnumeratorCancellation] CancellationToken cancellationToken = default) @@ -32,14 +33,16 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu var firstRow = true; var headRows = new Dictionary(); - string row; - for (var rowIndex = 1; (row = await reader.ReadLineAsync( + var rowIndex = 0; + while (await reader.ReadLineAsync( #if NET7_0_OR_GREATER cancellationToken #endif - ).ConfigureAwait(false)) is not null; rowIndex++) + ).ConfigureAwait(false) is { } row) { + rowIndex++; string finalRow = row; + if (_config.ReadLineBreaksWithinQuotes) { while (finalRow.Count(c => c == '"') % 2 != 0) @@ -49,6 +52,7 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu cancellationToken #endif ).ConfigureAwait(false); + if (nextPart is null) { break; @@ -67,7 +71,7 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu .Select((x, i) => new KeyValuePair(headRows[i], x)) .ToDictionary(x => x.Key, x => x.Value); - throw new ExcelColumnNotFoundException(columnIndex: null, headRows[colIndex], [], rowIndex, headers, rowValues, $"Csv read error: Column {colIndex} not found in Row {rowIndex}"); + throw new MiniExcelColumnNotFoundException(columnIndex: null, headRows[colIndex], [], rowIndex, headers, rowValues, $"Csv read error: Column {colIndex} not found in Row {rowIndex}"); } //header @@ -97,6 +101,7 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu headRows.Add(i, $"c{i + 1}"); } + // todo: can we find a way to remove the redundant cell conversions for CSV? var cell = CustomPropertyHelper.GetEmptyExpandoObject(read.Length - 1, 0); if (_config.ReadEmptyStringAsNull) { @@ -114,10 +119,10 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu } [CreateSyncVersion] - public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool mapHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { - var dynamicRecords = QueryAsync(false, sheetName, startCell, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, _config, cancellationToken); + var records = QueryAsync(false, sheetName, startCell, cancellationToken); + return MiniExcelMapper.MapQueryAsync(records, startCell, mapHeaderAsData, false, _config, cancellationToken); } [CreateSyncVersion] @@ -127,10 +132,10 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu } [CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { var dynamicRecords = QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, this._config, cancellationToken); + return MiniExcelMapper.MapQueryAsync(dynamicRecords, startCell, treatHeaderAsData, false, _config, cancellationToken); } [CreateSyncVersion] @@ -140,12 +145,26 @@ internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configu } [CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool treatHeaderAsData, CancellationToken cancellationToken = default) where T : class, new() { var dynamicRecords = QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, ReferenceHelper.ConvertXyToCell(startRowIndex, startColumnIndex), hasHeader, this._config, cancellationToken); + return MiniExcelMapper.MapQueryAsync(dynamicRecords, ConvertXyToCell(startRowIndex, startColumnIndex), treatHeaderAsData, false, _config, cancellationToken); } + private static string ConvertXyToCell(int x, int y) + { + int dividend = x; + string columnName = string.Empty; + + while (dividend > 0) + { + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; + } + return $"{columnName}{y}"; + } + private string[] Split(string row) { if (_config.SplitFn is not null) @@ -159,5 +178,6 @@ private string[] Split(string row) public void Dispose() { + _stream?.Dispose(); } } \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel.Csv/CsvWriter.cs similarity index 76% rename from src/MiniExcel/Csv/CsvWriter.cs rename to src/MiniExcel.Csv/CsvWriter.cs index 8e5e308e..709cf49f 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel.Csv/CsvWriter.cs @@ -1,53 +1,45 @@ -using MiniExcelLibs.Utils; -using MiniExcelLibs.WriteAdapter; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs.Csv; - -internal partial class CsvWriter : IExcelWriter, IDisposable +using MiniExcelLib.Core; +using MiniExcelLib.Core.Abstractions; +using MiniExcelLib.Core.Reflection; +using MiniExcelLib.Core.WriteAdapters; +using IMiniExcelWriter = MiniExcelLib.Core.Abstractions.IMiniExcelWriter; + +namespace MiniExcelLib.Csv; + +internal partial class CsvWriter : IMiniExcelWriter, IDisposable { private readonly StreamWriter _writer; private readonly CsvConfiguration _configuration; - private readonly bool _printHeader; private readonly object? _value; + private readonly bool _printHeader; - private bool _disposedValue; + private bool _disposed; - public CsvWriter(Stream stream, object? value, IMiniExcelConfiguration? configuration, bool printHeader) + // todo: should we add an explicit parameter to leave the stream open instead of the convoluted way to do it through a Func? + internal CsvWriter(Stream stream, object? value, bool printHeader, IMiniExcelConfiguration? configuration) { - _configuration = configuration is null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; + _configuration = configuration as CsvConfiguration ?? CsvConfiguration.Default; _writer = _configuration.StreamWriterFunc(stream); _printHeader = printHeader; _value = value; } - + private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) { - rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration)); + rowBuilder.Append(CsvHelper.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration)); rowBuilder.Append(_configuration.Seperator); } private static void RemoveTrailingSeparator(StringBuilder rowBuilder) { - if (rowBuilder.Length == 0) - return; - - rowBuilder.Remove(rowBuilder.Length - 1, 1); + if (rowBuilder.Length is var len and > 0) + { + rowBuilder.Remove(len - 1, 1); + } } - private string GetHeader(List props) => string.Join( - _configuration.Seperator.ToString(), - props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken = default) + [CreateSyncVersion] + private async Task WriteValuesAsync(StreamWriter writer, object values, string separator, string newLine, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -56,11 +48,14 @@ private async Task WriteValuesAsync(StreamWriter writer, object values, str { writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); } - List? props; + + List? props; #if SYNC_ONLY props = writeAdapter?.GetColumns(); #else - props = writeAdapter is not null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); + props = writeAdapter is not null + ? writeAdapter.GetColumns() + : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); #endif if (props is null) @@ -91,7 +86,7 @@ await _writer.WriteAsync(newLine #endif ).ConfigureAwait(false); } - + var rowBuilder = new StringBuilder(); var rowsWritten = 0; @@ -124,6 +119,7 @@ await _writer.WriteAsync(newLine else { #if !SYNC_ONLY +#pragma warning disable CA2007 await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) { cancellationToken.ThrowIfCancellationRequested(); @@ -133,6 +129,7 @@ await _writer.WriteAsync(newLine { AppendColumn(rowBuilder, column); } +#pragma warning restore CA2007 RemoveTrailingSeparator(rowBuilder); await _writer.WriteAsync(rowBuilder.ToString() @@ -153,7 +150,7 @@ await _writer.WriteAsync(newLine return rowsWritten; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task SaveAsAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -169,7 +166,7 @@ await _writer.WriteAsync("" #endif ).ConfigureAwait(false); await _writer.FlushAsync( -#if NET8_0_OR_GREATER +#if NET5_0_OR_GREATER cancellationToken #endif ).ConfigureAwait(false); @@ -178,22 +175,22 @@ await _writer.FlushAsync( var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken).ConfigureAwait(false); await _writer.FlushAsync( -#if NET8_0_OR_GREATER - cancellationToken +#if NET5_0_OR_GREATER + cancellationToken #endif ).ConfigureAwait(false); return [rowsWritten]; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] + [CreateSyncVersion] public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) { var rowsWritten = await SaveAsAsync(cancellationToken).ConfigureAwait(false); return rowsWritten.FirstOrDefault(); } - public string ToCsvString(object? value, ExcelColumnInfo? p) + public string ToCsvString(object? value, MiniExcelColumnInfo? p) { if (value is null) return ""; @@ -213,10 +210,14 @@ public string ToCsvString(object? value, ExcelColumnInfo? p) return Convert.ToString(value, _configuration.Culture) ?? ""; } - + + private string GetHeader(List props) => string.Join( + _configuration.Seperator.ToString(), + props.Select(s => CsvHelper.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); + protected virtual void Dispose(bool disposing) { - if (!_disposedValue) + if (!_disposed) { if (disposing) { @@ -226,7 +227,7 @@ protected virtual void Dispose(bool disposing) // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null - _disposedValue = true; + _disposed = true; } } diff --git a/src/MiniExcel.Csv/GlobalUsings.cs b/src/MiniExcel.Csv/GlobalUsings.cs new file mode 100644 index 00000000..eaf45918 --- /dev/null +++ b/src/MiniExcel.Csv/GlobalUsings.cs @@ -0,0 +1,13 @@ +// Global using directives + +global using System.Collections; +global using System.Data; +global using System.Dynamic; +global using System.Globalization; +global using System.Runtime.CompilerServices; +global using System.Text; +global using System.Text.RegularExpressions; +global using MiniExcelLib.Core.Abstractions; +global using MiniExcelLib.Core.Helpers; +global using MiniExcelLib.Core.Reflection; +global using Zomp.SyncMethodGenerator; \ No newline at end of file diff --git a/src/MiniExcel.Csv/MiniExcel.Csv.csproj b/src/MiniExcel.Csv/MiniExcel.Csv.csproj new file mode 100644 index 00000000..2d147dad --- /dev/null +++ b/src/MiniExcel.Csv/MiniExcel.Csv.csproj @@ -0,0 +1,20 @@ + + + + MiniExcelLib.Csv + + + + MiniExcel.Csv + MiniExcel.Csv + + + + + + + + + + + diff --git a/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs deleted file mode 100644 index ff271b5b..00000000 --- a/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace MiniExcelLibs.Attributes; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelColumnNameAttribute(string excelColumnName, string[]? aliases = null) : Attribute -{ - public string ExcelColumnName { get; set; } = excelColumnName; - public string[] Aliases { get; set; } = aliases ?? []; -} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs deleted file mode 100644 index 699b6f6f..00000000 --- a/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MiniExcelLibs.Attributes; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelColumnWidthAttribute(double excelColumnWidth) : Attribute -{ - public double ExcelColumnWidth { get; set; } = excelColumnWidth; -} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs b/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs deleted file mode 100644 index 0f06f105..00000000 --- a/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MiniExcelLibs.Attributes; - -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] -public class ExcelIgnoreAttribute(bool excelIgnore = true) : Attribute -{ - public bool ExcelIgnore { get; set; } = excelIgnore; -} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelSheetAttribute.cs b/src/MiniExcel/Attributes/ExcelSheetAttribute.cs deleted file mode 100644 index 3a4285a2..00000000 --- a/src/MiniExcel/Attributes/ExcelSheetAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using MiniExcelLibs.OpenXml; - -namespace MiniExcelLibs.Attributes; - -[AttributeUsage(AttributeTargets.Class)] -public class ExcelSheetAttribute : Attribute -{ - public string? Name { get; set; } - public SheetState State { get; set; } = SheetState.Visible; -} - -public class DynamicExcelSheet(string key) : ExcelSheetAttribute -{ - public string Key { get; set; } = key; -} \ No newline at end of file diff --git a/src/MiniExcel/ExcelFactory.cs b/src/MiniExcel/ExcelFactory.cs deleted file mode 100644 index 714d7474..00000000 --- a/src/MiniExcel/ExcelFactory.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using MiniExcelLibs.Csv; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.SaveByTemplate; -using Zomp.SyncMethodGenerator; -using ExcelOpenXmlTemplate = MiniExcelLibs.SaveByTemplate.ExcelOpenXmlTemplate; - -namespace MiniExcelLibs; - -internal static partial class ExcelReaderFactory -{ - [CreateSyncVersion] - internal static async Task GetProviderAsync(Stream stream, ExcelType excelType, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) - { - return excelType switch - { - ExcelType.CSV => new CsvReader(stream, configuration), - ExcelType.XLSX => await ExcelOpenXmlSheetReader.CreateAsync(stream, configuration, cancellationToken: cancellationToken).ConfigureAwait(false), - _ => throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel.") - }; - } -} - -internal static class ExcelWriterFactory -{ - internal static IExcelWriter GetProvider(Stream stream, object value, string? sheetName, ExcelType excelType, IMiniExcelConfiguration? configuration, bool printHeader) - { - if (string.IsNullOrEmpty(sheetName)) - throw new ArgumentException("Sheet names cannot be empty or null", nameof(sheetName)); - if (sheetName?.Length > 31 && excelType == ExcelType.XLSX) - throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); - if (excelType == ExcelType.UNKNOWN) - throw new ArgumentException("Excel type cannot be ExcelType.UNKNOWN", nameof(excelType)); - - return excelType switch - { - ExcelType.CSV => new CsvWriter(stream, value, configuration, printHeader), - ExcelType.XLSX => new ExcelOpenXmlSheetWriter(stream, value, sheetName, configuration, printHeader), - _ => throw new NotSupportedException($"The {excelType} Excel format is not supported") - }; - } -} - -internal static class ExcelTemplateFactory -{ - internal static IExcelTemplate GetProvider(Stream stream, IMiniExcelConfiguration? configuration, ExcelType excelType = ExcelType.XLSX) - { - if (excelType != ExcelType.XLSX) - throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel."); - - var valueExtractor = new InputValueExtractor(); - return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor); - } -} \ No newline at end of file diff --git a/src/MiniExcel/ExcelType.cs b/src/MiniExcel/ExcelType.cs deleted file mode 100644 index 4a610571..00000000 --- a/src/MiniExcel/ExcelType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace MiniExcelLibs; - -public enum ExcelType -{ - XLSX, - //XLS, - CSV, - /// - /// Will auto check excel type by stream or file path - /// - UNKNOWN -} \ No newline at end of file diff --git a/src/MiniExcel/Legacy/Attributes.cs b/src/MiniExcel/Legacy/Attributes.cs new file mode 100644 index 00000000..8feba35f --- /dev/null +++ b/src/MiniExcel/Legacy/Attributes.cs @@ -0,0 +1,33 @@ +using MiniExcelLib.Core.Attributes; + +// ReSharper disable CheckNamespace +namespace MiniExcelLibs.Attributes; + + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelColumnAttribute : MiniExcelColumnAttribute; + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelColumnIndexAttribute : MiniExcelColumnIndexAttribute +{ + public ExcelColumnIndexAttribute(int excelColumnIndex) : base(excelColumnIndex) { } + public ExcelColumnIndexAttribute(string excelColumnName) : base(excelColumnName) { } +} + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelColumnNameAttribute(string excelColumnName) : MiniExcelColumnNameAttribute(columnName: excelColumnName); + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelColumnWidthAttribute(double width) : MiniExcelColumnWidthAttribute(width); + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelFormatAttribute(string format) : MiniExcelFormatAttribute(format); + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelIgnoreAttribute(bool excelIgnore = true) : MiniExcelIgnoreAttribute(excelIgnore); + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class ExcelSheetAttribute : MiniExcelSheetAttribute; + +[Obsolete("This is a legacy attribute that will be removed in a future version. Please use the corresponding one from MiniExcelLib.Core.Attributes instead.")] +public sealed class DynamicExcelSheetAttribute(string key) : MiniExcelLib.Core.Attributes.DynamicExcelSheetAttribute(key); \ No newline at end of file diff --git a/src/MiniExcel/Legacy/Configuration.cs b/src/MiniExcel/Legacy/Configuration.cs new file mode 100644 index 00000000..3d8c396a --- /dev/null +++ b/src/MiniExcel/Legacy/Configuration.cs @@ -0,0 +1,17 @@ +using MiniExcelLib.Core; + + +namespace MiniExcelLibs +{ + public interface IConfiguration : IMiniExcelConfiguration; +} + +namespace MiniExcelLibs.OpenXml +{ + public sealed class OpenXmlConfiguration : MiniExcelLib.Core.OpenXml.OpenXmlConfiguration, IConfiguration; +} + +namespace MiniExcelLibs.Csv +{ + public sealed class CsvConfiguration : MiniExcelLib.Csv.CsvConfiguration, IConfiguration; +} diff --git a/src/MiniExcel/Legacy/ExcelType.cs b/src/MiniExcel/Legacy/ExcelType.cs new file mode 100644 index 00000000..a0b2ee83 --- /dev/null +++ b/src/MiniExcel/Legacy/ExcelType.cs @@ -0,0 +1,5 @@ +// ReSharper disable CheckNamespace +namespace MiniExcelLibs; + + +public enum ExcelType { XLSX, CSV, UNKNOWN } diff --git a/src/MiniExcel/Utils/ExcelTypeHelper.cs b/src/MiniExcel/Legacy/ExcelTypeHelper.cs similarity index 68% rename from src/MiniExcel/Utils/ExcelTypeHelper.cs rename to src/MiniExcel/Legacy/ExcelTypeHelper.cs index 60e78ad6..a4fd7f94 100644 --- a/src/MiniExcel/Utils/ExcelTypeHelper.cs +++ b/src/MiniExcel/Legacy/ExcelTypeHelper.cs @@ -1,43 +1,37 @@ -using System; -using System.IO; +// ReSharper disable CheckNamespace +namespace MiniExcelLibs; -namespace MiniExcelLibs.Utils; - -public static class ExcelTypeHelper +internal static class ExcelTypeHelper { - internal static ExcelType GetExcelType(string filePath, ExcelType excelType) + internal static ExcelType GetExcelType(this string filePath, ExcelType excelType) { if (excelType != ExcelType.UNKNOWN) return excelType; - + var extension = Path.GetExtension(filePath).ToLowerInvariant(); return extension switch { ".csv" => ExcelType.CSV, - //".xls" => ExcelType.XLS, ".xlsx" or ".xlsm" => ExcelType.XLSX, _ => throw new NotSupportedException($"Extension {extension} is not suppprted. Try specifying the ExcelType if you know what the underlying format is.") }; } - internal static ExcelType GetExcelType(Stream stream, ExcelType excelType) + internal static ExcelType GetExcelType(this Stream stream, ExcelType excelType) { if (excelType != ExcelType.UNKNOWN) return excelType; var probe = new byte[8]; stream.Seek(0, SeekOrigin.Begin); + var read = stream.Read(probe, 0, probe.Length); if (read != probe.Length) throw new InvalidDataException("The file/stream does not contain enough data to process"); - - stream.Seek(0, SeekOrigin.Begin); - // New office format (can be any ZIP archive) - if (probe is [0x50, 0x4B, ..]) - { + stream.Seek(0, SeekOrigin.Begin); + if (probe[0] == 0x50 && probe[1] == 0x4B) return ExcelType.XLSX; - } throw new InvalidDataException("The file type could not be inferred automatically, please specify ExcelType manually"); } diff --git a/src/MiniExcel/Legacy/MiniExcel.cs b/src/MiniExcel/Legacy/MiniExcel.cs new file mode 100644 index 00000000..b2ff870a --- /dev/null +++ b/src/MiniExcel/Legacy/MiniExcel.cs @@ -0,0 +1,399 @@ +using System.Data; +using MiniExcelLib.Core.DataReader; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Core.OpenXml.Picture; +using MiniExcelLib.Csv; +using MiniExcelLibs.OpenXml; +using Zomp.SyncMethodGenerator; + +using NewMiniExcel = MiniExcelLib.Core.MiniExcel; +using OpenXmlExporter = MiniExcelLib.Core.OpenXmlExporter; +using OpenXmlImporter = MiniExcelLib.Core.OpenXmlImporter; +using OpenXmlTemplater = MiniExcelLib.Core.OpenXmlTemplater; + +namespace MiniExcelLibs; + +public static partial class MiniExcel +{ + private static readonly OpenXmlExporter ExcelExporter = NewMiniExcel.Exporters.GetOpenXmlExporter(); + private static readonly OpenXmlImporter ExcelImporter = NewMiniExcel.Importers.GetOpenXmlImporter(); + private static readonly OpenXmlTemplater ExcelTemplater = NewMiniExcel.Templaters.GetOpenXmlTemplater(); + + private static readonly CsvExporter CsvExporter = NewMiniExcel.Exporters.GetCsvExporter(); + private static readonly CsvImporter CsvImporter = NewMiniExcel.Importers.GetCsvImporter(); + + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) + => await ExcelExporter.AddPictureAsync(path, cancellationToken, images).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) + => await ExcelExporter.AddPictureAsync(excelStream, cancellationToken, images).ConfigureAwait(false); + + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static MiniExcelDataReader GetReader(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.GetDataReader(path, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration), + ExcelType.CSV => CsvImporter.GetDataReader(path, useHeaderRow, configuration as Csv.CsvConfiguration), + _ => throw new NotSupportedException($"Excel type {type} is not a valid Excel type") + }; + } + + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.GetDataReader(stream, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration), + ExcelType.CSV => CsvImporter.GetDataReader(stream, useHeaderRow, configuration as Csv.CsvConfiguration), + _ => throw new NotSupportedException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task InsertAsync(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelExporter.InsertSheetAsync(path, value, sheetName, printHeader, overwriteSheet, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.AppendAsync(path, value, printHeader, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task InsertAsync(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelExporter.InsertSheetAsync(stream, value, sheetName, printHeader, overwriteSheet, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.AppendAsync(stream, value, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration? configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelExporter.ExportAsync(path, value, printHeader, sheetName, printHeader, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.ExportAsync(path, value, printHeader, overwriteFile, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelExporter.ExportAsync(stream, value, printHeader, sheetName, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.ExportAsync(stream, value, printHeader, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryAsync(string path, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, bool hasHeader = true, CancellationToken cancellationToken = default) where T : class, new() + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryAsync(path, sheetName, startCell, hasHeader, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => CsvImporter.QueryAsync(path, hasHeader, configuration as Csv.CsvConfiguration, cancellationToken), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryAsync(this Stream stream, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, bool hasHeader = true, CancellationToken cancellationToken = default) where T : class, new() + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryAsync(stream, sheetName, startCell, hasHeader, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => CsvImporter.QueryAsync(stream, hasHeader, configuration as Csv.CsvConfiguration, cancellationToken), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryAsync(path, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => CsvImporter.QueryAsync(path, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryAsync(stream, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => CsvImporter.QueryAsync(stream, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + #region QueryRange + + /// + /// Extract the given range。 Only uppercase letters are effective。 + /// e.g. + /// MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3") + /// A2 represents the second row of column A, C3 represents the third row of column C + /// If you don't want to restrict rows, just don't include numbers + /// + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryRangeAsync(path, useHeaderRow, sheetName, startCell, endCell, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => throw new NotSupportedException("QueryRange is not supported for csv"), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryRangeAsync(stream, useHeaderRow, sheetName, startCell, endCell, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => CsvImporter.QueryAsync(stream, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryRangeAsync(path, useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => throw new NotSupportedException("QueryRange is not supported for csv"), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => ExcelImporter.QueryRangeAsync(stream, useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration as OpenXmlConfiguration, cancellationToken), + ExcelType.CSV => throw new NotSupportedException("QueryRange is not supported for csv"), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + #endregion QueryRange + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + => await ExcelTemplater.ApplyTemplateAsync(path, templatePath, value, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IConfiguration? configuration = null) + => await ExcelTemplater.ApplyTemplateAsync(path, templateBytes, value, configuration as OpenXmlConfiguration).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IConfiguration? configuration = null) + => await ExcelTemplater.ApplyTemplateAsync(stream, templatePath, value, configuration as OpenXmlConfiguration).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IConfiguration? configuration = null) + => await ExcelTemplater.ApplyTemplateAsync(stream, templateBytes, value, configuration as OpenXmlConfiguration).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task SaveAsByTemplateAsync(string path, Stream templateStream, object value, IConfiguration? configuration = null) + => await ExcelTemplater.ApplyTemplateAsync(path, templateStream, value, configuration as OpenXmlConfiguration).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task SaveAsByTemplateAsync(this Stream stream, Stream templateStream, object value, IConfiguration? configuration = null) + => await ExcelTemplater.ApplyTemplateAsync(stream, templateStream, value, configuration as OpenXmlConfiguration).ConfigureAwait(false); + + #region MergeCells + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + if (excelType != ExcelType.XLSX) + throw new NotSupportedException("MergeSameCells is only supported for Xlsx files"); + + await ExcelTemplater.MergeSameCellsAsync(mergedFilePath, path, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + if (excelType != ExcelType.XLSX) + throw new NotSupportedException("MergeSameCells is only supported for Xlsx files"); + + await ExcelTemplater.MergeSameCellsAsync(stream, path, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Templater instead.")] + public static async Task MergeSameCellsAsync(this Stream stream, byte[] filePath, ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + if (excelType != ExcelType.XLSX) + throw new NotSupportedException("MergeSameCells is only supported for Xlsx files"); + + await ExcelTemplater.MergeSameCellsAsync(stream, filePath, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false); + } + + #endregion + + /// + /// The use of QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelImporter.QueryAsDataTableAsync(path, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvImporter.QueryAsDataTableAsync(path, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + /// + /// The use of QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelImporter.QueryAsDataTableAsync(stream, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvImporter.QueryAsDataTableAsync(stream, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetNamesAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetNamesAsync(path, config, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetNamesAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetInformationsAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetInformationsAsync(path, config, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetInformationsAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetColumnsAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = path.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelImporter.GetColumnNamesAsync(path, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvImporter.GetColumnNamesAsync(path, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetColumnsAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + var type = stream.GetExcelType(excelType); + return type switch + { + ExcelType.XLSX => await ExcelImporter.GetColumnNamesAsync(stream, useHeaderRow, sheetName, startCell, configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvImporter.GetColumnNamesAsync(stream, useHeaderRow, configuration as Csv.CsvConfiguration, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Excel type {type} is not a valid Excel type") + }; + } + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetDimensionsAsync(path, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Importer instead.")] + public static async Task> GetSheetDimensionsAsync(this Stream stream, CancellationToken cancellationToken = default) + => await ExcelImporter.GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task ConvertCsvToXlsxAsync(string csv, string xlsx, CancellationToken cancellationToken = default) + => await CsvExporter.ConvertCsvToXlsxAsync(csv, xlsx, cancellationToken: cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, CancellationToken cancellationToken = default) + => await CsvExporter.ConvertCsvToXlsxAsync(csv, xlsx, cancellationToken: cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task ConvertXlsxToCsvAsync(string xlsx, string csv, CancellationToken cancellationToken = default) + => await CsvExporter.ConvertXlsxToCsvAsync(xlsx, csv, cancellationToken: cancellationToken).ConfigureAwait(false); + + [CreateSyncVersion] + [Obsolete("This is a legacy method signature that will be removed in a future version. Please use the methods from one of the providers in MiniExcelLib.Core.MiniExcel.Exporter instead.")] + public static async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, CancellationToken cancellationToken = default) + => await CsvExporter.ConvertXlsxToCsvAsync(xlsx, csv, cancellationToken: cancellationToken).ConfigureAwait(false); + +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs deleted file mode 100644 index 461343d4..00000000 --- a/src/MiniExcel/MiniExcel.cs +++ /dev/null @@ -1,425 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.CodeAnalysis; -using System.Dynamic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.Picture; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs; - -public static partial class MiniExcel -{ - [CreateSyncVersion] - public static async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) - { - using var stream = File.Open(path, FileMode.OpenOrCreate); - await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) - { - await MiniExcelPictureImplement.AddPictureAsync(excelStream, cancellationToken, images).ConfigureAwait(false); - } - - public static MiniExcelDataReader GetReader(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) - { - var stream = FileHelper.OpenSharedRead(path); - return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); - } - - public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) - { - return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); - } - - [CreateSyncVersion] - public static async Task InsertAsync(string path, object value, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IMiniExcelConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) - throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format"); - - if (!File.Exists(path)) - { - var rowsWritten = await SaveAsAsync(path, value, printHeader, sheetName, excelType, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); - return rowsWritten.FirstOrDefault(); - } - - if (excelType == ExcelType.CSV) - { - using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan); - return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); - } - else - { - using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan); - return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); - } - } - - [CreateSyncVersion] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] - public static async Task InsertAsync(this Stream stream, object value, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - stream.Seek(0, SeekOrigin.End); - if (excelType == ExcelType.CSV) - { - var newValue = value is IEnumerable or IDataReader ? value : new[] { value }; - var provider = ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false); - return await provider.InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); - } - else - { - configuration ??= new OpenXmlConfiguration { FastMode = true }; - return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); - } - } - - [CreateSyncVersion] - public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IMiniExcelConfiguration? configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default) - { - if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); - - using var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew); - return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] - public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader) - .SaveAsAsync(cancellationToken) - .ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(string path, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() - { - using var stream = FileHelper.OpenSharedRead(path); - - var query = QueryAsync(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, hasHeader, cancellationToken); - await foreach (var item in query.ConfigureAwait(false)) - yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(this Stream stream, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() - { - using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - await foreach (var item in excelReader.QueryAsync(sheetName, startCell, hasHeader, cancellationToken).ConfigureAwait(false)) - yield return item; - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - await foreach (var item in QueryAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - await foreach (var item in excelReader.QueryAsync(useHeaderRow, sheetName, startCell, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate( - new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } - - #region QueryRange - - /// - /// Extract the given range。 Only uppercase letters are effective。 - /// e.g. - /// MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3") - /// A2 represents the second row of column A, C3 represents the third row of column C - /// If you don't want to restrict rows, just don't include numbers - /// - /// - /// - /// - /// - /// top left corner - /// lower right corner - /// - /// - /// - [CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, endCell, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startCell, endCell, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate( - new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } - - [CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate( - new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } - - #endregion QueryRange - - [CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - using var stream = File.Create(path); - await SaveAsByTemplateAsync(stream, templatePath, value, configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - using var stream = File.Create(path); - await SaveAsByTemplateAsync(stream, templateBytes, value, configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration) - .SaveAsByTemplateAsync(templatePath, value, cancellationToken) - .ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration) - .SaveAsByTemplateAsync(templateBytes, value, cancellationToken) - .ConfigureAwait(false); - } - - #region MergeCells - - [CreateSyncVersion] - public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - using var stream = File.Create(mergedFilePath); - await MergeSameCellsAsync(stream, path, excelType, configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration, excelType) - .MergeSameCellsAsync(path, cancellationToken) - .ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task MergeSameCellsAsync(this Stream stream, byte[] filePath, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration, excelType) - .MergeSameCellsAsync(filePath, cancellationToken) - .ConfigureAwait(false); - } - - #endregion - - /// - /// QueryAsDataTable is not recommended, because it'll load all data into memory. - /// - [Obsolete("QueryAsDataTable is not recommended, because it'll load all data into memory.")] - [CreateSyncVersion] - public static async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - return await QueryAsDataTableAsync(stream, useHeaderRow, sheetName, excelType: ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - /*Issue #279*/ - if (sheetName is null && excelType != ExcelType.CSV) - sheetName = (await stream.GetSheetNamesAsync(configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false)).First(); - - var dt = new DataTable(sheetName); - var first = true; - using var provider = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - var rows = provider.QueryAsync(false, sheetName, startCell, cancellationToken); - - var columnDict = new Dictionary(); - await foreach (var row in rows.ConfigureAwait(false)) - { - if (first) - { - foreach (var entry in row) - { - cancellationToken.ThrowIfCancellationRequested(); - - var columnName = useHeaderRow ? entry.Value?.ToString() : entry.Key; - if (!string.IsNullOrWhiteSpace(columnName)) // avoid #298 : Column '' does not belong to table - { - var column = new DataColumn(columnName, typeof(object)) { Caption = columnName }; - dt.Columns.Add(column); - columnDict.Add(entry.Key, columnName!);//same column name throw exception??? - } - } - - dt.BeginLoadData(); - first = false; - if (useHeaderRow) - { - continue; - } - } - - var newRow = dt.NewRow(); - foreach (var entry in columnDict) - { - newRow[entry.Value] = row[entry.Key]; //TODO: optimize not using string key - } - - dt.Rows.Add(newRow); - } - - dt.EndLoadData(); - return dt; - } - - [CreateSyncVersion] - public static async Task> GetSheetNamesAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - return await GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task> GetSheetNamesAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) - { - config ??= OpenXmlConfiguration.DefaultConfig; - - // todo: figure out why adding using statement breaks the tests -#pragma warning disable CA2000 // Dispose objects before losing scope - var archive = new ExcelOpenXmlZip(stream); -#pragma warning restore CA2000 // Dispose objects before losing scope - - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); - var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); - - return rels?.Select(s => s.Name).ToList() ?? []; - } - - [CreateSyncVersion] - public static async Task> GetSheetInformationsAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - return await GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task> GetSheetInformationsAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) - { - config ??= OpenXmlConfiguration.DefaultConfig; - - using var archive = new ExcelOpenXmlZip(stream); - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); - var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); - - return rels?.Select((s, i) => s.ToSheetInfo((uint)i)).ToList() ?? []; - } - - [CreateSyncVersion] - public static async Task> GetColumnsAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - return await GetColumnsAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task> GetColumnsAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) - { -#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using var enumerator = QueryAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).GetAsyncEnumerator(cancellationToken); -#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task - - _ = enumerator.ConfigureAwait(false); - if (!await enumerator.MoveNextAsync().ConfigureAwait(false)) - return []; - - return (enumerator.Current as IDictionary)?.Keys ?? []; - } - - [CreateSyncVersion] - public static async Task> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default) - { - using var stream = FileHelper.OpenSharedRead(path); - return await GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task> GetSheetDimensionsAsync(this Stream stream, CancellationToken cancellationToken = default) - { - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, null, cancellationToken: cancellationToken).ConfigureAwait(false); - return await reader.GetDimensionsAsync(cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(string csv, string xlsx, CancellationToken cancellationToken = default) - { - using var csvStream = FileHelper.OpenSharedRead(csv); - using var xlsxStream = new FileStream(xlsx, FileMode.CreateNew); - await ConvertCsvToXlsxAsync(csvStream, xlsxStream, cancellationToken: cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, CancellationToken cancellationToken = default) - { - var value = QueryAsync(csv, useHeaderRow: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken).ConfigureAwait(false); - await SaveAsAsync(xlsx, value, printHeader: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(string xlsx, string csv, CancellationToken cancellationToken = default) - { - using var xlsxStream = FileHelper.OpenSharedRead(xlsx); - using var csvStream = new FileStream(csv, FileMode.CreateNew); - await ConvertXlsxToCsvAsync(xlsxStream, csvStream, cancellationToken).ConfigureAwait(false); - } - - [CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, CancellationToken cancellationToken = default) - { - var value = QueryAsync(xlsx, useHeaderRow: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken).ConfigureAwait(false); - await SaveAsAsync(csv, value, printHeader: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken).ConfigureAwait(false); - } -} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcel.csproj b/src/MiniExcel/MiniExcel.csproj new file mode 100644 index 00000000..c05de8ac --- /dev/null +++ b/src/MiniExcel/MiniExcel.csproj @@ -0,0 +1,17 @@ + + + + MiniExcelLib + + + + MiniExcel + MiniExcel + + + + + + + + diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj deleted file mode 100644 index f7144b3a..00000000 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ /dev/null @@ -1,63 +0,0 @@ - - - netstandard2.0;net8.0;net10.0 - 2.0.0-beta.1 - enable - - - 13 - - - - MiniExcel - Mini-Software - MiniExcel - MiniExcel - excel;xlsx;csv;micro-helper;mini;openxml;helper; - Fast, Low-Memory, Easy Excel .NET helper to import/export/template spreadsheet -Github : https://github.com/mini-software/MiniExcel -Gitee : https://gitee.com/dotnetchina/MiniExcel -Issues : https://github.com/mini-software/MiniExcel/issues -Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true - Wei Lin, Michele Bastione, PING-HSIU SHIH, Amos(izanhzh), eynarhaji, Mini-Software team - MiniExcel - Wei Lin, 2021 onwards - en - https://raw.githubusercontent.com/mini-software/MiniExcel/master/LICENSE - MiniExcelLibs - Apache-2.0 - https://github.com/mini-software/MiniExcel - https://github.com/mini-software/MiniExcel - icon.png - Please Check [Release Notes](https://github.com/mini-software/MiniExcel/tree/master/docs) - Github - miniexcel.snk - True - {097903C9-1F81-4427-B4C8-530CB59687B8} - true - true - snupkg - README.md - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs deleted file mode 100644 index c1c74cfc..00000000 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using MiniExcelLibs.Utils; - -namespace MiniExcelLibs.OpenXml; - -internal static class ExcelOpenXmlUtils -{ - public static string MinifyXml(string xml) => xml - .Replace("\r", "") - .Replace("\n", "") - .Replace("\t", "") - .Trim(); - - /// - /// Encode to XML (special characteres: ' " > < &) - /// - public static string EncodeXml(string? value) => value is null ? "" - : XmlEncoder.EncodeString(value) - ?.Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">") - .Replace("\"", """) - .Replace("'", "'") - .ToString() ?? ""; - - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static string ConvertXYToCell(int x, int y) - { - int dividend = x; - string columnName = string.Empty; - - while (dividend > 0) - { - var modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo) + columnName; - dividend = (dividend - modulo) / 26; - } - return $"{columnName}{y}"; - } - - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static string ConvertXYToCell(Tuple xy) => ConvertXYToCell(xy.Item1, xy.Item2); - - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static Tuple ConvertCellToXY(string cell) => Tuple.Create(GetCellColumnIndex(cell), GetCellRowNumber(cell)); - - public static int GetColumnNumber(string name) - { - int number = -1; - int pow = 1; - for (int i = name.Length - 1; i >= 0; i--) - { - number += (name[i] - 'A' + 1) * pow; - pow *= 26; - } - - return number; - } - - public static int GetCellColumnIndex(string cell) - { - const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const int mode = 26; - - var x = 0; - var cellLetter = GetCellColumnLetter(cell); - //AA=27,ZZ=702 - foreach (var t in cellLetter) - x = x * mode + keys.IndexOf(t); - - return x; - } - - public static int GetCellRowNumber(string cell) - { - if (string.IsNullOrEmpty(cell)) - throw new Exception("cell is null or empty"); - var cellNumber = string.Empty; - foreach (var t in cell) - { - if (char.IsDigit(t)) - cellNumber += t; - } - return int.Parse(cellNumber); - } - - public static string GetCellColumnLetter(string cell) - { - string GetCellLetter = string.Empty; - foreach (var t in cell) - { - if (char.IsLetter(t)) - GetCellLetter += t; - } - return GetCellLetter; - } - - public static string ConvertColumnName(int x) - { - int dividend = x; - string columnName = string.Empty; - - while (dividend > 0) - { - var modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo) + columnName; - dividend = (dividend - modulo) / 26; - } - return columnName; - } -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs b/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs deleted file mode 100644 index f0b826a7..00000000 --- a/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs.OpenXml.Styles; - -internal partial interface ISheetStyleBuilder -{ - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task BuildAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildResult.cs b/src/MiniExcel/OpenXml/Styles/SheetStyleBuildResult.cs deleted file mode 100644 index 9ca9d0aa..00000000 --- a/src/MiniExcel/OpenXml/Styles/SheetStyleBuildResult.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; - -namespace MiniExcelLibs.OpenXml.Styles; - -internal class SheetStyleBuildResult -{ - public SheetStyleBuildResult(Dictionary cellXfIdMap) - { - CellXfIdMap = cellXfIdMap; - } - - public Dictionary CellXfIdMap { get; set; } -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/TableStyles.cs b/src/MiniExcel/OpenXml/TableStyles.cs deleted file mode 100644 index 7a451b1b..00000000 --- a/src/MiniExcel/OpenXml/TableStyles.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MiniExcelLibs.OpenXml; - -public enum TableStyles -{ - None, - Default -} \ No newline at end of file diff --git a/src/MiniExcel/Properties/AssemblyInfo.cs b/src/MiniExcel/Properties/AssemblyInfo.cs deleted file mode 100644 index 118b9d39..00000000 --- a/src/MiniExcel/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("MiniExcelTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010091c2c6c10d20b6c884dbc48892f91cc773d33c3a1f43ba3352700031d2d5f6a2b37cccd60469733a4597bdd94b54ee63f514dc487b21be797b1b63063941630b46ad090baabaf0650d05b9c590ea497644f0c296bb223e17dc785f0fbb255ef780905aabf4cf14ee5bca087cbd41d2231169a620529626035215604261b533c9")] - -#if PLAT_SKIP_LOCALS_INIT -[module: System.Runtime.CompilerServices.SkipLocalsInit] -#endif diff --git a/src/MiniExcel/SaveByTemplate/IInputValueExtractor.cs b/src/MiniExcel/SaveByTemplate/IInputValueExtractor.cs deleted file mode 100644 index b404a382..00000000 --- a/src/MiniExcel/SaveByTemplate/IInputValueExtractor.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace MiniExcelLibs.SaveByTemplate; - -public interface IInputValueExtractor -{ - IDictionary ToValueDictionary(object valueObject); -} \ No newline at end of file diff --git a/src/MiniExcel/Utils/ListHelper.cs b/src/MiniExcel/Utils/ListHelper.cs deleted file mode 100644 index af38fcd9..00000000 --- a/src/MiniExcel/Utils/ListHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MiniExcelLibs.Utils; - -internal static class IEnumerableHelper -{ - internal static bool StartsWith(this IList span, IList value) where T : IEquatable - { - if (value is []) - return true; - - var b = span.Take(value.Count); - var bCount = b.Count(); - if (bCount != value.Count) - return false; - - for (int i = 0; i < bCount; i++) - if (!span[i].Equals(value[i])) - return false; - - return true; - } -} \ No newline at end of file diff --git a/src/MiniExcel/Utils/StringHelper.cs b/src/MiniExcel/Utils/StringHelper.cs deleted file mode 100644 index 37981410..00000000 --- a/src/MiniExcel/Utils/StringHelper.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MiniExcelLibs.OpenXml.Constants; -using Zomp.SyncMethodGenerator; - -namespace MiniExcelLibs.Utils; - -internal static partial class StringHelper -{ - private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; - - public static string GetLetters(string content) => new([..content.Where(char.IsLetter)]); - public static int GetNumber(string content) => int.Parse(new string([..content.Where(char.IsNumber)])); - - /// - /// Copied and modified from ExcelDataReader - @MIT License - /// - [CreateSyncVersion] - public static async Task ReadStringItemAsync(XmlReader reader, CancellationToken cancellationToken = default) - { - var result = new StringBuilder(); - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return string.Empty; - - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "t", Ns)) - { - // There are multiple in a . Concatenate within an . - result.Append(await reader.ReadElementContentAsStringAsync() -#if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) -#endif - .ConfigureAwait(false)); - } - else if (XmlReaderHelper.IsStartElement(reader, "r", Ns)) - { - result.Append(await ReadRichTextRunAsync(reader, cancellationToken).ConfigureAwait(false)); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - return result.ToString(); - } - - /// - /// Copied and modified from ExcelDataReader - @MIT License - /// - [CreateSyncVersion] - private static async Task ReadRichTextRunAsync(XmlReader reader, CancellationToken cancellationToken = default) - { - var result = new StringBuilder(); - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return string.Empty; - - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "t", Ns)) - { - result.Append(await reader.ReadElementContentAsStringAsync() -#if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) -#endif - .ConfigureAwait(false)); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { - break; - } - } - - return result.ToString(); - } -} \ No newline at end of file diff --git a/src/MiniExcel/Utils/TypeHelper.cs b/src/MiniExcel/Utils/TypeHelper.cs deleted file mode 100644 index 39f9e341..00000000 --- a/src/MiniExcel/Utils/TypeHelper.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Globalization; -using System.Linq; -using System.Reflection; -using MiniExcelLibs.Exceptions; - -namespace MiniExcelLibs.Utils; - -internal static class TypeHelper -{ - public static IEnumerable> ConvertToEnumerableDictionary(IDataReader reader) - { - while (reader.Read()) - { - yield return Enumerable.Range(0, reader.FieldCount) - .ToDictionary( - reader.GetName, - reader.GetValue); - } - } - - /// - /// From : https://stackoverflow.com/questions/906499/getting-type-t-from-ienumerablet - /// - public static IEnumerable GetGenericIEnumerables(object o) - { - return o.GetType() - .GetInterfaces() - .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - .Select(t => t.GetGenericArguments()[0]); - } - - public static bool IsNumericType(Type type, bool isNullableUnderlyingType = false) - { - if (isNullableUnderlyingType) - type = Nullable.GetUnderlyingType(type) ?? type; - - // True for all numeric types except bool, sbyte and byte - return Type.GetTypeCode(type) is >= TypeCode.Int16 and <= TypeCode.Decimal; - } - - public static object? TypeMapping(T v, ExcelColumnInfo pInfo, object itemValue, int rowIndex, string startCell, MiniExcelConfiguration config) where T : class, new() - { - try - { - return TypeMappingImpl(v, pInfo, itemValue, config); - } - catch (Exception ex) when (ex is InvalidCastException or FormatException) - { - var columnName = pInfo.ExcelColumnName ?? pInfo.Property.Name; - var startRowIndex = ReferenceHelper.ConvertCellToXY(startCell).Item2; - var errorRow = startRowIndex + rowIndex + 1; - - var msg = $"ColumnName: {columnName}, CellRow: {errorRow}, Value: {itemValue}. The value cannot be cast to type {pInfo.Property.Info.PropertyType.Name}."; - throw new ExcelInvalidCastException(columnName, errorRow, itemValue, pInfo.Property.Info.PropertyType, msg); - } - } - - private static object? TypeMappingImpl(T v, ExcelColumnInfo pInfo, object? itemValue, MiniExcelConfiguration config) where T : class, new() - { - object? newValue = null; - if (pInfo.Nullable && string.IsNullOrWhiteSpace(itemValue?.ToString())) - { - } - else if (pInfo.ExcludeNullableType == typeof(Guid)) - { - newValue = Guid.Parse(itemValue?.ToString() ?? Guid.Empty.ToString()); - } - else if (pInfo.ExcludeNullableType == typeof(DateTimeOffset)) - { - var vs = itemValue?.ToString(); - if (pInfo.ExcelFormat is not null) - { - if (DateTimeOffset.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) - { - newValue = value; - } - } - else if (DateTimeOffset.TryParse(vs, config.Culture, DateTimeStyles.None, out var value)) - { - newValue = value; - } - else - { - throw new InvalidCastException($"{vs} cannot be cast to DateTime"); - } - } - else if (pInfo.ExcludeNullableType == typeof(DateTime)) - { - // fix issue 257 https://github.com/mini-software/MiniExcel/issues/257 - if (itemValue is DateTime) - { - newValue = itemValue; - pInfo.Property.SetValue(v, newValue); - return newValue; - } - - var vs = itemValue?.ToString(); - if (pInfo.ExcelFormat is not null) - { - if (pInfo.Property.Info.PropertyType == typeof(DateTimeOffset) && DateTimeOffset.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var offsetValue)) - { - newValue = offsetValue; - } - else if (DateTime.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var value)) - { - newValue = value; - } - } - else if (DateTime.TryParse(vs, config.Culture, DateTimeStyles.None, out var dtValue)) - newValue = dtValue; - else if (DateTime.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dtExactValue)) - newValue = dtExactValue; - else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var doubleValue)) - newValue = DateTime.FromOADate(doubleValue); - else - throw new InvalidCastException($"{vs} cannot be cast to DateTime"); - } -#if NET6_0_OR_GREATER - else if (pInfo.ExcludeNullableType == typeof(DateOnly)) - { - if (itemValue is DateOnly) - { - newValue = itemValue; - pInfo.Property.SetValue(v, newValue); - return newValue; - } - - var vs = itemValue?.ToString(); - if (pInfo.ExcelFormat is not null) - { - if (DateOnly.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnlyCustom)) - { - newValue = dateOnlyCustom; - } - } - else if (DateOnly.TryParse(vs, config.Culture, DateTimeStyles.None, out var dateOnly)) - newValue = dateOnly; - else if (DateOnly.TryParseExact(vs, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dateOnlyExact)) - newValue = dateOnlyExact; - else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var dateOnlyDouble)) - newValue = DateOnly.FromDateTime(DateTime.FromOADate(dateOnlyDouble)); - else - throw new InvalidCastException($"{vs} cannot be cast to DateOnly"); - } -#endif - else if (pInfo.ExcludeNullableType == typeof(TimeSpan)) - { - if (itemValue is TimeSpan) - { - newValue = itemValue; - pInfo.Property.SetValue(v, newValue); - return newValue; - } - - var vs = itemValue?.ToString(); - if (pInfo.ExcelFormat is not null) - { - if (TimeSpan.TryParseExact(vs, pInfo.ExcelFormat, CultureInfo.InvariantCulture, out var value)) - { - newValue = value; - } - } - else if (TimeSpan.TryParse(vs, config.Culture, out var tsValue)) - newValue = tsValue; - else if (TimeSpan.TryParseExact(vs, @"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture, out var tsExactValue)) - newValue = tsExactValue; - else if (double.TryParse(vs, NumberStyles.None, CultureInfo.InvariantCulture, out var msValue)) - newValue = TimeSpan.FromMilliseconds(msValue); - else - throw new InvalidCastException($"{vs} cannot be cast to TimeSpan"); - } - else if (pInfo.ExcludeNullableType == typeof(double)) // && (!Regex.IsMatch(itemValue.ToString(), @"^-?\d+(\.\d+)?([eE][-+]?\d+)?$") || itemValue.ToString().Trim().Equals("NaN"))) - { - var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture); - newValue = double.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value) ? value : double.NaN; - } - else if (pInfo.ExcludeNullableType == typeof(bool)) - { - var vs = itemValue?.ToString(); - newValue = vs switch - { - "1" => true, - "0" => false, - _ => bool.TryParse(vs, out var parsed) ? parsed : null - }; - } - else if (pInfo.Property.Info.PropertyType == typeof(string)) - { - newValue = XmlEncoder.DecodeString(itemValue?.ToString()); - } - else if (pInfo.ExcludeNullableType.IsEnum) - { - var fieldInfo = pInfo.ExcludeNullableType.GetFields().FirstOrDefault(e => e.GetCustomAttribute(false)?.Description == itemValue?.ToString()); - var value = fieldInfo?.Name ?? itemValue?.ToString() ?? ""; - newValue = Enum.Parse(pInfo.ExcludeNullableType, value, true); - } - else if (pInfo.ExcludeNullableType == typeof(Uri)) - { - var rawValue = itemValue?.ToString(); - if (!Uri.TryCreate(rawValue, UriKind.RelativeOrAbsolute, out var uri)) - throw new InvalidCastException($"Value \"{rawValue}\" cannot be converted to Uri"); - newValue = uri; - } - else - { - // Use pInfo.ExcludeNullableType to resolve : https://github.com/mini-software/MiniExcel/issues/138 - newValue = Convert.ChangeType(itemValue, pInfo.ExcludeNullableType, config.Culture); - } - - pInfo.Property.SetValue(v, newValue); - return newValue; - } - - public static bool IsAsyncEnumerable(this Type type, out Type? genericArgument) - { - var asyncEnumrableInterfaceType = type - .GetInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>)); - - genericArgument = asyncEnumrableInterfaceType?.GetGenericArguments().FirstOrDefault(); - return genericArgument is not null; - } -} \ No newline at end of file diff --git a/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs b/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs deleted file mode 100644 index de7c1af9..00000000 --- a/src/MiniExcel/WriteAdapter/IAsyncMiniExcelWriteAdapter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using MiniExcelLibs.Utils; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs.WriteAdapter; - -internal interface IAsyncMiniExcelWriteAdapter -{ - Task?> GetColumnsAsync(); - - IAsyncEnumerable> GetRowsAsync(List props, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs b/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs deleted file mode 100644 index e2e8afb3..00000000 --- a/src/MiniExcel/WriteAdapter/IMiniExcelWriteAdapter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using MiniExcelLibs.Utils; - -namespace MiniExcelLibs.WriteAdapter; - -internal interface IMiniExcelWriteAdapter -{ - bool TryGetKnownCount(out int count); - List? GetColumns(); - IEnumerable> GetRows(List props, CancellationToken cancellationToken = default); -} - -internal readonly struct CellWriteInfo(object value, int cellIndex, ExcelColumnInfo prop) -{ - public object Value { get; } = value; - public int CellIndex { get; } = cellIndex; - public ExcelColumnInfo Prop { get; } = prop; -} \ No newline at end of file diff --git a/src/MiniExcel/icon.png b/src/icon.png similarity index 100% rename from src/MiniExcel/icon.png rename to src/icon.png diff --git a/src/MiniExcel/miniexcel.snk b/src/miniexcel.snk similarity index 100% rename from src/MiniExcel/miniexcel.snk rename to src/miniexcel.snk diff --git a/tests/MiniExcelTests/MiniExcelTests.csproj b/tests/MiniExcel.Core.Tests/MiniExcel.Core.Tests.csproj similarity index 51% rename from tests/MiniExcelTests/MiniExcelTests.csproj rename to tests/MiniExcel.Core.Tests/MiniExcel.Core.Tests.csproj index 50838497..0c874746 100644 --- a/tests/MiniExcelTests/MiniExcelTests.csproj +++ b/tests/MiniExcel.Core.Tests/MiniExcel.Core.Tests.csproj @@ -7,21 +7,18 @@ false true - ..\..\src\MiniExcel\miniexcel.snk + ..\..\src\miniexcel.snk $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA1849;CA2000;CA2007;CA2208 - MiniExcelLibs.Tests + MiniExcelLib.Tests - - - @@ -30,13 +27,33 @@ all - + - + + - + - + + + + + + + + + + + + + + + + + + + + diff --git a/tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelAutoAdjustWidthTests.cs similarity index 83% rename from tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelAutoAdjustWidthTests.cs index 41265180..84b6e6bc 100644 --- a/tests/MiniExcelTests/MiniExcelAutoAdjustWidthTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelAutoAdjustWidthTests.cs @@ -1,22 +1,21 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using System.Data; -using System.Data.SQLite; -using Xunit; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Tests; public class MiniExcelAutoAdjustWidthTests { + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + [Fact] public async Task AutoAdjustWidthThrowsExceptionWithoutFastMode_Async() { using var file = AutoDeletingPath.Create(); var path = file.ToString(); - await Assert.ThrowsAsync(() => MiniExcel.SaveAsAsync(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: new OpenXmlConfiguration + await Assert.ThrowsAsync(() => _excelExporter.ExportAsync(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: new OpenXmlConfiguration { EnableAutoWidth = true, })); @@ -28,7 +27,7 @@ public void AutoAdjustWidthThrowsExceptionWithoutFastMode() using var file = AutoDeletingPath.Create(); var path = file.ToString(); - Assert.Throws(() => MiniExcel.SaveAs(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: new OpenXmlConfiguration + Assert.Throws(() => _excelExporter.Export(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: new OpenXmlConfiguration { EnableAutoWidth = true, })); @@ -41,7 +40,7 @@ public async Task AutoAdjustWidthEnumerable_Async() var path = file.ToString(); var configuration = AutoAdjustTestParameters.GetConfiguration(); - await MiniExcel.SaveAsAsync(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: configuration); + await _excelExporter.ExportAsync(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: configuration); AssertExpectedWidth(path, configuration); } @@ -53,7 +52,7 @@ public void AutoAdjustWidthEnumerable() var path = file.ToString(); var configuration = AutoAdjustTestParameters.GetConfiguration(); - MiniExcel.SaveAs(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: configuration); + _excelExporter.Export(path, AutoAdjustTestParameters.GetDictionaryTestData(), configuration: configuration); AssertExpectedWidth(path, configuration); } @@ -71,7 +70,7 @@ public async Task AutoAdjustWidthDataReader_Async() await using var command = new SQLiteCommand(Db.GenerateDummyQuery(AutoAdjustTestParameters.GetDictionaryTestData()), connection); connection.Open(); await using var reader = command.ExecuteReader(); - await MiniExcel.SaveAsAsync(path, reader, configuration: configuration); + await _excelExporter.ExportAsync(path, reader, configuration: configuration); } AssertExpectedWidth(path, configuration); @@ -90,7 +89,7 @@ public void AutoAdjustWidthDataReader() using var command = new SQLiteCommand(Db.GenerateDummyQuery(AutoAdjustTestParameters.GetDictionaryTestData()), connection); connection.Open(); using var reader = command.ExecuteReader(); - MiniExcel.SaveAs(path, reader, configuration: configuration); + _excelExporter.Export(path, reader, configuration: configuration); } AssertExpectedWidth(path, configuration); @@ -115,7 +114,7 @@ public async Task AutoAdjustWidthDataTable_Async() } var configuration = AutoAdjustTestParameters.GetConfiguration(); - await MiniExcel.SaveAsAsync(path, table, configuration: configuration); + await _excelExporter.ExportAsync(path, table, configuration: configuration); AssertExpectedWidth(path, configuration); } @@ -138,7 +137,7 @@ public void AutoAdjustWidthDataTable() } var configuration = AutoAdjustTestParameters.GetConfiguration(); - MiniExcel.SaveAs(path, table, configuration: configuration); + _excelExporter.Export(path, table, configuration: configuration); AssertExpectedWidth(path, configuration); } diff --git a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs similarity index 66% rename from tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs index f81a1ba2..94dfde91 100644 --- a/tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueAsyncTests.cs @@ -1,23 +1,15 @@ -using Dapper; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using Newtonsoft.Json; -using OfficeOpenXml; -using System.Data; -using System.Data.SQLite; -using System.Globalization; -using System.Text; -using Xunit; -using Xunit.Abstractions; -using static MiniExcelLibs.Tests.MiniExcelOpenXmlTests; - -namespace MiniExcelLibs.Tests; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.Tests; public class MiniExcelIssueAsyncTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - + + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + /// /// [SaveAsByTemplate support DateTime custom format · Issue #255 · mini-software/MiniExcel] /// (https://github.com/mini-software/MiniExcel/issues/255) @@ -37,8 +29,8 @@ public async Task Issue255() } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); - var q = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable(); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); + var q = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("2021", rows[1].A.ToString()); @@ -55,11 +47,11 @@ public async Task Issue255() Time2 = new DateTime(2021, 01, 01) } }; - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), value); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); Assert.Single(rowsWritten); Assert.Equal(1, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("2021", rows[1].A.ToString()); Assert.Equal("2021", rows[1].B.ToString()); @@ -68,10 +60,10 @@ public async Task Issue255() private class Issue255DTO { - [ExcelFormat("yyyy")] + [MiniExcelFormat("yyyy")] public DateTime Time { get; set; } - [ExcelColumn(Format = "yyyy")] + [MiniExcelColumn(Format = "yyyy")] public DateTime Time2 { get; set; } } @@ -83,7 +75,7 @@ private class Issue255DTO public async Task Issue256() { var path = PathHelper.GetFile("xlsx/TestIssue256.xlsx"); - var q = MiniExcel.QueryAsync(path, false).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, false).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(new DateTime(2003, 4, 16), rows[1].A); @@ -91,91 +83,6 @@ public async Task Issue256() } - /// - /// 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 MiniExcel.SaveAsAsync(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 Csv.CsvConfiguration - { - StreamWriterFunc = stream => new StreamWriter(stream, Encoding.GetEncoding("gb2312")) - }; - - await MiniExcel.SaveAsAsync(path.ToString(), value, excelType: ExcelType.CSV, 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 MiniExcel.SaveAsAsync(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 MiniExcel.SaveAsAsync(path.ToString(), reader); - - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - const string expected = - """" - a,b - """<>+-*//}{\\n",1234567890 - "Hello World",-1234567890 - - """"; - - Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); - } - /// /// No error exception throw when reading xls file #242 /// @@ -183,46 +90,10 @@ public async Task Issue251() public async Task Issue242() { var path = PathHelper.GetFile("xls/TestIssue242.xls"); - Assert.Throws(() => _ = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList()); + Assert.Throws(() => _ = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList()); await using var stream = File.OpenRead(path); - Assert.Throws(() => _ = (stream.QueryAsync().ToBlockingEnumerable()).ToList()); - } - - /// - /// Csv type mapping QueryAsync error "cannot be converted to xxx type" #243 - /// - [Fact] - public async Task Issue243() - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - 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 rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), value); - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - var q = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal("Jack", rows[0].Name); - Assert.Equal(25, rows[0].Age); - Assert.Equal(new DateTime(2021, 01, 03), rows[0].InDate); - - Assert.Equal("Henry", rows[1].Name); - Assert.Equal(36, rows[1].Age); - 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; } + Assert.Throws(() => _ = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().ToList()); } /// @@ -237,43 +108,17 @@ public async Task Issue241() new() { Name="Henry",InDate=new DateTime(2020,04,05) } ]; - // csv - { - using var file = AutoDeletingPath.Create(ExcelType.CSV); - var path = file.ToString(); - var rowsWritten = await MiniExcel.SaveAsAsync(path, value); - - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); - - { - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(rows[0].InDate, "01 04, 2021"); - Assert.Equal(rows[1].InDate, "04 05, 2020"); - } - - { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(rows[0].InDate, new DateTime(2021, 01, 04)); - Assert.Equal(rows[1].InDate, new DateTime(2020, 04, 05)); - } - } - // xlsx { using var file = AutoDeletingPath.Create(); var path = file.ToString(); - var rowsWritten = await MiniExcel.SaveAsAsync(path, value); + var rowsWritten = await _excelExporter.ExportAsync(path, value); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); { - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(rows[0].InDate, "01 04, 2021"); @@ -281,7 +126,7 @@ public async Task Issue241() } { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(rows[0].InDate, new DateTime(2021, 01, 04)); @@ -294,7 +139,7 @@ private class Issue241Dto { public string Name { get; set; } - [ExcelFormat("MM dd, yyyy")] + [MiniExcelFormat("MM dd, yyyy")] public DateTime InDate { get; set; } } @@ -312,7 +157,7 @@ public async Task Issue132() new { Name ="Henry", Age=36, InDate=new DateTime(2020,05,03)}, }; - await MiniExcel.SaveAsAsync(path.ToString(), value); + await _excelExporter.ExportAsync(path.ToString(), value); } { @@ -326,7 +171,7 @@ public async Task Issue132() { TableStyles = TableStyles.None }; - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), value, configuration: config); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value, configuration: config); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); @@ -341,7 +186,7 @@ public async Task Issue132() new { Name ="Henry", Age=36,InDate=new DateTime(2020,05,03)}, }) ); - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), value); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); @@ -366,16 +211,16 @@ public async Task Issue235() department.TableName = "department"; sheets.Tables.Add(department); - var rowsWritten = await MiniExcel.SaveAsAsync(path, sheets); + var rowsWritten = await _excelExporter.ExportAsync(path, sheets); Assert.Equal(2, rowsWritten.Length); Assert.Equal(2, rowsWritten[0]); - var sheetNames = MiniExcel.GetSheetNames(path); + var sheetNames = await _excelImporter.GetSheetNamesAsync(path); Assert.Equal("users", sheetNames[0]); Assert.Equal("department", sheetNames[1]); { - var q = MiniExcel.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("Jack", rows[0].Name); Assert.Equal(25, rows[0].Age); @@ -383,7 +228,7 @@ public async Task Issue235() Assert.Equal(44, rows[1].Age); } { - var q = MiniExcel.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("01", rows[0].ID); Assert.Equal("HR", rows[0].Name); @@ -400,9 +245,9 @@ public async Task Issue233() { var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); -#pragma warning disable CS0618 // Type or member is obsolete - var dt = await MiniExcel.QueryAsDataTableAsync(path); -#pragma warning restore CS0618 // Type or member is obsolete + + var dt = await _excelImporter.QueryAsDataTableAsync(path); + var rows = dt.Rows; @@ -410,28 +255,6 @@ public async Task Issue233() Assert.Equal("0.55/1.1", rows[1]["Size"]); } - /// - /// Csv QueryAsync split comma not correct #237 - /// https://github.com/mini-software/MiniExcel/issues/237 - /// - [Fact] - public async Task Issue237() - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - var value = new[] - { - new{ id="\"\"1,2,3\"\""}, - new{ id="1,2,3"}, - }; - await MiniExcel.SaveAsAsync(path.ToString(), value); - - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal("\"\"1,2,3\"\"", rows[0].id); - Assert.Equal("1,2,3", rows[1].id); - } - /// /// SaveAs support multiple sheets #234 /// @@ -456,18 +279,18 @@ public async Task Issue234() ["users"] = users, ["department"] = department }; - var rowsWritten = await MiniExcel.SaveAsAsync(path, sheets); + var rowsWritten = await _excelExporter.ExportAsync(path, sheets); Assert.Equal(2, rowsWritten.Length); Assert.Equal(2, rowsWritten[0]); - var sheetNames = MiniExcel.GetSheetNames(path); + var sheetNames = _excelImporter.GetSheetNames(path); Assert.Equal("users", sheetNames[0]); Assert.Equal("department", sheetNames[1]); { - var q = MiniExcel.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, sheetName: "users").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("Jack", rows[0].Name); @@ -476,7 +299,7 @@ public async Task Issue234() Assert.Equal(44, rows[1].Age); } { - var q = MiniExcel.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true, sheetName: "department").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("01", rows[0].ID); @@ -535,12 +358,12 @@ public async Task Issue230() await using (var reader = await cmd3.ExecuteReaderAsync(CommandBehavior.CloseConnection)) { using var path = AutoDeletingPath.Create(); - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), reader, printHeader: true); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader, printHeader: true); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(1, rows[0].id); @@ -557,9 +380,7 @@ public async Task Issue229() { var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); -#pragma warning disable CS0618 // Type or member is obsolete - var dt = await MiniExcel.QueryAsDataTableAsync(path); -#pragma warning restore CS0618 // Type or member is obsolete + var dt = await _excelImporter.QueryAsDataTableAsync(path); foreach (DataColumn column in dt.Columns) { @@ -581,7 +402,7 @@ public Task Issue122() }; var path1 = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); - var rows1 = MiniExcel.QueryAsync(path1, useHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); + var rows1 = _excelImporter.QueryAsync(path1, useHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); Assert.Equal("HR", rows1[0].Department); Assert.Equal("HR", rows1[1].Department); @@ -591,7 +412,7 @@ public Task Issue122() Assert.Equal("IT", rows1[5].Department); var path2 = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); - var rows2 = MiniExcel.QueryAsync(path2, useHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); + var rows2 = _excelImporter.QueryAsync(path2, useHeaderRow: true, configuration: config).ToBlockingEnumerable().ToList(); Assert.Equal("V1", rows2[2].Test1); Assert.Equal("V2", rows2[5].Test2); @@ -612,14 +433,14 @@ public async Task Issue227() { { var path = PathHelper.GetTempPath("xlsm"); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { new { V = "A1" }, new { V = "A2" } })); + Assert.Throws(() => _excelExporter.Export(path, new[] { new { V = "A1" }, new { V = "A2" } })); File.Delete(path); } { var path = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(100, rows.Count); @@ -633,7 +454,7 @@ public async Task Issue227() } { await using var stream = File.OpenRead(path); - var q = stream.QueryAsync().ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(100, rows.Count); @@ -657,8 +478,8 @@ public async Task Issue226() { using var path = AutoDeletingPath.Create(); var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); - Assert.Equal("A1:A3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } /// @@ -675,12 +496,12 @@ public async Task Issue223() new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } ]; using var path = AutoDeletingPath.Create(); - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), value); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); -#pragma warning disable CS0618 // Type or member is obsolete - var dt = await MiniExcel.QueryAsDataTableAsync(path.ToString()); + + var dt = await _excelImporter.QueryAsDataTableAsync(path.ToString()); #pragma warning restore CS0618 var columns = dt.Columns; Assert.Equal(typeof(object), columns[0].DataType); @@ -698,7 +519,7 @@ public async Task Issue223() public async Task Issue222() { var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + 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); @@ -713,7 +534,7 @@ public async Task Issue147() { { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var q = MiniExcel.QueryAsync(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(["C", "D", "E"], (rows[0] as IDictionary)?.Keys); @@ -729,13 +550,13 @@ public async Task Issue147() } Assert.Equal(11, rows.Count); - var columns = MiniExcel.GetColumns(path, startCell: "C3"); + var columns = await _excelImporter.GetColumnNamesAsync(path, startCell: "C3"); Assert.Equal(["C", "D", "E"], columns); } { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var q = MiniExcel.QueryAsync(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(["Column1", "Column2", "Column3"], (rows[0] as IDictionary)?.Keys); @@ -750,7 +571,7 @@ public async Task Issue147() } Assert.Equal(10, rows.Count); - var columns = MiniExcel.GetColumns(path, useHeaderRow: true, startCell: "C3"); + var columns = await _excelImporter.GetColumnNamesAsync(path, useHeaderRow: true, startCell: "C3"); Assert.Equal(["Column1", "Column2", "Column3"], columns); } } @@ -770,11 +591,11 @@ public async Task Issue211() 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 MiniExcel.SaveAsAsync(path.ToString(), reader); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); Assert.Single(rowsWritten); Assert.Equal(3, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + 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); @@ -797,11 +618,11 @@ public async Task EmptyDataReaderIssue() await using var connection2 = new SQLiteConnection(connectionString); using var reader = await connection2.ExecuteReaderAsync("SELECT * FROM test"); - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), reader); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), reader); Assert.Single(rowsWritten); Assert.Equal(0, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Empty(rows); } @@ -814,15 +635,12 @@ 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 MiniExcel.SaveAsAsync(path.ToString(), value); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), value); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); { -#pragma warning disable CS0618 // Type or member is obsolete - var table = await MiniExcel.QueryAsDataTableAsync(path.ToString()); -#pragma warning restore CS0618 // Type or member is obsolete - + 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"]); @@ -832,10 +650,7 @@ public async Task Issue216() } { -#pragma warning disable CS0618 // Type or member is obsolete - var dt = await MiniExcel.QueryAsDataTableAsync(path.ToString(), false); -#pragma warning restore CS0618 - + 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"]); @@ -855,9 +670,9 @@ public async Task IssueI3OSKV() { using var path = AutoDeletingPath.Create(); var value = new[] { new { Test = "12345678901234567890" } }; - await MiniExcel.SaveAsAsync(path.ToString(), value); + await _excelExporter.ExportAsync(path.ToString(), value); - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var A2 = q.First().Test; Assert.Equal("12345678901234567890", A2); } @@ -865,9 +680,9 @@ public async Task IssueI3OSKV() { using var path = AutoDeletingPath.Create(); var value = new[] { new { Test = 123456.789 } }; - await MiniExcel.SaveAsAsync(path.ToString(), value); + await _excelExporter.ExportAsync(path.ToString(), value); - var q = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var A2 = q.First().Test; Assert.Equal(123456.789, A2); } @@ -881,7 +696,7 @@ public async Task IssueI3OSKV() public async Task Issue220() { var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); - var rows = MiniExcel.QueryAsync(path, useHeaderRow: true).ToBlockingEnumerable(); + var rows = _excelImporter.QueryAsync(path, useHeaderRow: true).ToBlockingEnumerable(); var result = rows .GroupBy(s => s.PRT_ID) .Select(g => new @@ -905,91 +720,15 @@ public async Task Issue220() public async Task Issue215() { await using var stream = new MemoryStream(); - await stream.SaveAsAsync(new[] { new { V = "test1" }, new { V = "test2" } }); + await _excelExporter.ExportAsync(stream, new[] { new { V = "test1" }, new { V = "test2" } }); - var q = (stream.QueryAsync(true).ToBlockingEnumerable()).Cast>(); + var q = _excelImporter.QueryAsync(stream, true).ToBlockingEnumerable().Cast>(); var rows = q.ToList(); Assert.Equal("test1", rows[0]["V"]); Assert.Equal("test2", rows[1]["V"]); } - /// - /// Support Enum Mapping - /// https://github.com/mini-software/MiniExcel/issues/89 - /// - [Fact] - public async Task Issue89() - { - //csv - { - const string text = - """ - State - OnDuty - Fired - Leave - """; - await using var stream = new MemoryStream(); - await using var writer = new StreamWriter(stream); - - await writer.WriteAsync(text); - await writer.FlushAsync(); - - stream.Position = 0; - var q = stream.QueryAsync(excelType: ExcelType.CSV).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); - - var outputPath = PathHelper.GetTempPath("xlsx"); - var rowsWritten = await MiniExcel.SaveAsAsync(outputPath, rows); - Assert.Single(rowsWritten); - Assert.Equal(3, rowsWritten[0]); - - var q2 = MiniExcel.QueryAsync(outputPath).ToBlockingEnumerable(); - var rows2 = q2.ToList(); - Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); - } - - //xlsx - { - var path = PathHelper.GetFile("xlsx/TestIssue89.xlsx"); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); - - var outputPath = PathHelper.GetTempPath(); - var rowsWritten = await MiniExcel.SaveAsAsync(outputPath, rows); - Assert.Single(rowsWritten); - Assert.Equal(3, rowsWritten[0]); - - var q1 = MiniExcel.QueryAsync(outputPath).ToBlockingEnumerable(); - var rows2 = q1.ToList(); - Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); - } - } - - private class Issue89VO - { - public WorkState State { get; set; } - - public enum WorkState - { - OnDuty, - Leave, - Fired - } - } - /// /// DataTable recommended to use Caption for column name first, then use columname /// https://github.com/mini-software/MiniExcel/issues/217 @@ -1004,33 +743,19 @@ public async Task Issue217() table.Rows.Add(1, "Jonathan", 23.44); table.Rows.Add(2, "Bill", 56.87); - // openxml - { - using var path = AutoDeletingPath.Create(); - var rowsWritten = await MiniExcel.SaveAsAsync(path.ToString(), table); - Assert.Single(rowsWritten); - Assert.Equal(2, rowsWritten[0]); + using var path = AutoDeletingPath.Create(); + var rowsWritten = await _excelExporter.ExportAsync(path.ToString(), table); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); - } - - // csv - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - await MiniExcel.SaveAsAsync(path.ToString(), table); - - var q = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); - } + var q = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); + var rows = q.ToList(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); } /// - /// MiniExcel.SaveAs(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 + /// _ _exporter.ExportXlsx(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 /// https://github.com/mini-software/MiniExcel/issues/212 /// [Fact] @@ -1039,9 +764,9 @@ public async Task Issue212() const string sheetName = "Demo"; using var path = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); + await _excelExporter.ExportAsync(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); - var actualSheetName = MiniExcel.GetSheetNames(path.ToString()).ToList()[0]; + var actualSheetName = _excelImporter.GetSheetNames(path.ToString()).ToList()[0]; Assert.Equal(sheetName, actualSheetName); } @@ -1068,8 +793,8 @@ public async Task Issue207() } }; - await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + await _excelTemplater.ApplyTemplateAsync(path, templatePath, value); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("項目1", rows[0].A); @@ -1094,7 +819,7 @@ public async Task Issue207() Assert.Equal("項目4", rows[15].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[15].B); - var demension = Helpers.GetFirstSheetDimensionRefValue(path); + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C16", demension); } @@ -1114,9 +839,9 @@ public async Task Issue207() } }; - await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path, templatePath, value); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("項目1", rows[0].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[0].C); @@ -1127,7 +852,7 @@ public async Task Issue207() Assert.Equal("項目4", rows[9].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[9].C); - var demension = Helpers.GetFirstSheetDimensionRefValue(path); + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:E15", demension); } } @@ -1146,10 +871,10 @@ public async Task Issue87() }; await using var stream = File.OpenRead(templatePath); - var q = MiniExcel.QueryAsync(templatePath).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(templatePath).ToBlockingEnumerable(); var rows = q.ToList(); - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); } /// @@ -1171,9 +896,9 @@ public async Task Issue206() { ["employees"] = dt }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B2", dimension); } @@ -1187,9 +912,9 @@ public async Task Issue206() dt.Rows.Add("Jack", "HR"); } var value = new Dictionary { ["employees"] = dt }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B2", dimension); } } @@ -1223,11 +948,11 @@ public async Task Issue193() new {name="Keaton",department="IT"} } }; - await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path, templatePath, value); - foreach (var sheetName in MiniExcel.GetSheetNames(path)) + foreach (var sheetName in _excelImporter.GetSheetNames(path)) { - var q = MiniExcel.QueryAsync(path, sheetName: sheetName).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, sheetName: sheetName).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(9, rows.Count); @@ -1247,7 +972,7 @@ public async Task Issue193() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var demension = Helpers.GetFirstSheetDimensionRefValue(path); + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", demension); //TODO:row can't contain xmlns @@ -1278,9 +1003,9 @@ public async Task Issue193() new {name="Keaton",department="IT"} } }; - await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path, templatePath, value); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("FooCompany", rows[0].A); Assert.Equal("Jack", rows[2].B); @@ -1298,105 +1023,30 @@ public async Task Issue193() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var demension = Helpers.GetFirstSheetDimensionRefValue(path); + var demension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", demension); } } - [Fact] - public async Task Issue142() - { - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); - - { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - Assert.Equal("MyProperty4", rows[0].A); - Assert.Equal("CustomColumnName", rows[0].B); //note - Assert.Equal("MyProperty5", rows[0].C); - Assert.Equal("MyProperty2", rows[0].D); - Assert.Equal("MyProperty6", rows[0].E); - Assert.Equal(null, rows[0].F); - Assert.Equal("MyProperty3", rows[0].G); - - Assert.Equal("MyProperty4", rows[0].A); - Assert.Equal("CustomColumnName", rows[0].B); //note - Assert.Equal("MyProperty5", rows[0].C); - Assert.Equal("MyProperty2", rows[0].D); - Assert.Equal("MyProperty6", rows[0].E); - Assert.Equal(null, rows[0].F); - Assert.Equal("MyProperty3", rows[0].G); - } - - { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.ToList(); - - Assert.Equal("MyProperty4", rows[0].MyProperty4); - Assert.Equal("MyProperty1", rows[0].MyProperty1); //note - 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); - } - } - - { - using var file = AutoDeletingPath.Create(ExcelType.CSV); - var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new [] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); - const string expected = - """ - MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 - MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 - - """; - Assert.Equal(expected, await File.ReadAllTextAsync(path)); - - { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); - var rows = q.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); - } - } - - { - using var path = AutoDeletingPath.Create(); - Issue142VoDuplicateColumnName[] input = [ new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } ]; - Assert.Throws(() => MiniExcel.SaveAs(path.ToString(), input)); - } - } - [Fact] public async Task Issue142_Query() { const string path = "../../../../../samples/xlsx/TestIssue142.xlsx"; const string pathCsv = "../../../../../samples/xlsx/TestIssue142.csv"; { - var rows = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); Assert.Equal(0, rows[0].MyProperty1); } { await Assert.ThrowsAsync(async () => { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); }); } { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("CustomColumnName", rows[0].MyProperty1); Assert.Null(rows[0].MyProperty7); @@ -1408,7 +1058,7 @@ await Assert.ThrowsAsync(async () => } { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("CustomColumnName", rows[0].MyProperty1); Assert.Null(rows[0].MyProperty7); @@ -1422,41 +1072,29 @@ await Assert.ThrowsAsync(async () => private class Issue142VO { - [ExcelColumnName("CustomColumnName")] + [MiniExcelColumnName("CustomColumnName")] public string MyProperty1 { get; set; } //index = 1 - [ExcelIgnore] + [MiniExcelIgnore] public string MyProperty7 { get; set; } //index = null public string MyProperty2 { get; set; } //index = 3 - [ExcelColumnIndex(6)] + [MiniExcelColumnIndex(6)] public string MyProperty3 { get; set; } //index = 6 - [ExcelColumnIndex("A")] // equal column index 0 + [MiniExcelColumnIndex("A")] // equal column index 0 public string MyProperty4 { get; set; } - [ExcelColumnIndex(2)] + [MiniExcelColumnIndex(2)] public string MyProperty5 { get; set; } //index = 2 public string MyProperty6 { get; set; } //index = 4 } - private class Issue142VoDuplicateColumnName - { - [ExcelColumnIndex("A")] - public int MyProperty1 { get; set; } - [ExcelColumnIndex("A")] - public int MyProperty2 { get; set; } - - public int MyProperty3 { get; set; } - [ExcelColumnIndex("B")] - public int MyProperty4 { get; set; } - } - private class Issue142VoOverIndex { - [ExcelColumnIndex("Z")] + [MiniExcelColumnIndex("Z")] public int MyProperty1 { get; set; } } private class Issue142VoExcelColumnNameNotFound { - [ExcelColumnIndex("B")] + [MiniExcelColumnIndex("B")] public int MyProperty1 { get; set; } } @@ -1468,19 +1106,19 @@ public async Task Issue150() { var path = PathHelper.GetTempFilePath(); - await Assert.ThrowsAnyAsync(async () => await MiniExcel.SaveAsAsync(path, new[] { 1, 2 })); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { 1, 2 })); File.Delete(path); - await Assert.ThrowsAnyAsync(async () => await MiniExcel.SaveAsAsync(path, new[] { "1", "2" })); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { "1", "2" })); File.Delete(path); - await Assert.ThrowsAnyAsync(async () => await MiniExcel.SaveAsAsync(path, new[] { '1', '2' })); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { '1', '2' })); File.Delete(path); - await Assert.ThrowsAnyAsync(async () => await MiniExcel.SaveAsAsync(path, new[] { DateTime.Now })); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { DateTime.Now })); File.Delete(path); - await Assert.ThrowsAnyAsync(async () => await MiniExcel.SaveAsAsync(path, new[] { Guid.NewGuid() })); + await Assert.ThrowsAnyAsync(async () => await _excelExporter.ExportAsync(path, new[] { Guid.NewGuid() })); File.Delete(path); } @@ -1495,7 +1133,7 @@ public async Task Issue157() var path = file.ToString(); _output.WriteLine("==== SaveAs by strongly type ===="); - var input = JsonConvert.DeserializeObject>( + var input = JsonConvert.DeserializeObject>( """ [ { @@ -1545,14 +1183,14 @@ public async Task Issue157() } ] """); - var rowsWritten = await MiniExcel.SaveAsAsync(path, input); + var rowsWritten = await _excelExporter.ExportAsync(path, input); Assert.Single(rowsWritten); Assert.Equal(5, rowsWritten[0]); - var q = MiniExcel.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First()); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); using var p = new ExcelPackage(new FileInfo(path)); var ws = p.Workbook.Worksheets.First(); @@ -1562,10 +1200,10 @@ public async Task Issue157() { const string path = "../../../../../samples/xlsx/TestIssue157.xlsx"; { - var q = MiniExcel.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First()); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); } using (var p = new ExcelPackage(new FileInfo(path))) { @@ -1575,7 +1213,7 @@ public async Task Issue157() } { - var q = MiniExcel.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(5, rows.Count); @@ -1609,7 +1247,7 @@ public async Task Issue149() { const string path = "../../../../../samples/xlsx/TestIssue149.xlsx"; - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.Select(s => (string)s.A).ToList(); for (int i = 0; i < chars.Length; i++) @@ -1626,9 +1264,9 @@ public async Task Issue149() var path = file.ToString(); var input = chars.Select(s => new { Test = s.ToString() }); - await MiniExcel.SaveAsAsync(path, input); + await _excelExporter.ExportAsync(path, input); - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); var rows = q.Select(s => (string)s.Test).ToList(); for (int i = 0; i < chars.Length; i++) @@ -1646,9 +1284,9 @@ public async Task Issue149() var path = file.ToString(); var input = chars.Select(s => new { Test = s.ToString() }); - await MiniExcel.SaveAsAsync(path, input); + await _excelExporter.ExportAsync(path, input); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.Select(s => s.Test).ToList(); for (int i = 0; i < chars.Length; i++) @@ -1674,7 +1312,7 @@ private class Issue149VO public async Task Issue153() { const string path = "../../../../../samples/xlsx/TestIssue153.xlsx"; - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); var rows = q.First() as IDictionary; Assert.Equal( @@ -1693,7 +1331,7 @@ public async Task Issue137() var path = "../../../../../samples/xlsx/TestIssue137.xlsx"; { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + 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()); @@ -1729,7 +1367,7 @@ public async Task Issue137() // dynamic query with head { - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); + 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()); @@ -1751,7 +1389,7 @@ public async Task Issue137() } { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(10, rows.Count); @@ -1785,7 +1423,7 @@ public async Task Issue138() { const string path = "../../../../../samples/xlsx/TestIssue138.xlsx"; { - var q = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(6, rows.Count); @@ -1811,7 +1449,7 @@ public async Task Issue138() } { - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + 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); diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs similarity index 63% rename from tests/MiniExcelTests/MiniExcelIssueTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs index 9181a16e..0bec8f51 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelIssueTests.cs @@ -1,34 +1,24 @@ -using Dapper; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.Csv; -using MiniExcelLibs.Exceptions; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using Newtonsoft.Json; -using NPOI.XSSF.UserModel; -using OfficeOpenXml; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Data.SQLite; -using System.Globalization; -using System.Text; +using System.ComponentModel; using System.Text.RegularExpressions; -using MiniExcelLibs.Utils; -using Xunit; -using Xunit.Abstractions; -using static MiniExcelLibs.Tests.MiniExcelOpenXmlTests; -using MiniExcelLibs.Picture; -using TableStyles = MiniExcelLibs.OpenXml.TableStyles; -using System.Threading.Tasks; -using LicenseContext = OfficeOpenXml.LicenseContext; +using MiniExcelLib.Core.Exceptions; +using MiniExcelLib.Core.OpenXml.Picture; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Tests.Common.Utils; +using NPOI.XSSF.UserModel; -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Tests; public class MiniExcelIssueTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; + + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + // private readonly OpenXmlImporter _csvImporter = MiniExcel.Importer.GetCsvImporter(); + // private readonly OpenXmlExporter _csvExporter = MiniExcel.Exporter.GetCsvExporter(); + /// /// https://github.com/mini-software/MiniExcel/issues/549 /// @@ -37,14 +27,15 @@ public void TestIssue549() { var data = new[] { - new{id=1,name="jack"}, - new{id=2,name="mike"}, + new{ id = 1, name = "jack" }, + new{ id = 2, name = "mike" } }; + using var file = AutoDeletingPath.Create(); var path = file.ToString(); - MiniExcel.SaveAs(path, data); - var rows = MiniExcel.Query(path, true).ToList(); + _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); @@ -52,7 +43,7 @@ public void TestIssue549() 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()); + Assert.Equal((string)rows[0].id.ToString(), a2.NumericCellValue.ToString(CultureInfo.InvariantCulture)); Assert.Equal((string)rows[0].name.ToString(), b2.StringCellValue); } } @@ -72,7 +63,7 @@ public void TestIssue24020201() new() { { "specialMark", 3 } }, } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, data); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, data); } [Fact] @@ -89,27 +80,14 @@ public void TestIssue553() new{ ITM=3 } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, data); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, data); - var rows = MiniExcel.Query(path.ToString()).ToList(); + 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 TestPR10() - { - 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 = MiniExcel.Query(path, configuration: config).ToList(); - } - [Fact] public void TestIssue289() { @@ -120,9 +98,9 @@ public void TestIssue289() new() { Name="0002", UserType=DescriptionEnum.V2 }, new() { Name="0003", UserType=DescriptionEnum.V3 } ]; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(DescriptionEnum.V1, rows[0].UserType); Assert.Equal(DescriptionEnum.V2, rows[1].UserType); @@ -143,74 +121,7 @@ private enum DescriptionEnum } /// - /// 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)} - }; - MiniExcel.SaveAs(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 = MiniExcel.Insert(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 = MiniExcel.Insert(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); - } - } - - - /// - /// Exception : MiniExcelLibs.Exceptions.ExcelInvalidCastException: 'ColumnName : Date, CellRow : 2, Value : 2021-01-31 10:03:00 +08:00, it can't cast to DateTimeOffset type.' + /// 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() @@ -220,14 +131,14 @@ public void TestIssue430() { new TestIssue430Dto{ Date=DateTimeOffset.Parse("2021-01-31 10:03:00 +05:00")} }; - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToArray(); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToArray(); Assert.Equal("2021-01-31 10:03:00 +05:00", rows[0].Date.ToString("yyyy-MM-dd HH:mm:ss zzz")); } private class TestIssue430Dto { - [ExcelFormat("yyyy-MM-dd HH:mm:ss zzz")] + [MiniExcelFormat("yyyy-MM-dd HH:mm:ss zzz")] public DateTimeOffset Date { get; set; } } @@ -244,8 +155,8 @@ public void TestIssue_DataReaderSupportDimension() using var path = AutoDeletingPath.Create(); using var reader = table.CreateDataReader(); var config = new OpenXmlConfiguration { FastMode = true }; - MiniExcel.SaveAs(path.ToString(), reader, configuration: config); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _excelExporter.Export(path.ToString(), reader, configuration: config); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("", xml); Assert.Contains("", xml); @@ -268,8 +179,8 @@ public void TestIssue413() } }; var templatePath = PathHelper.GetFile("xlsx/TestIssue413.xlsx"); - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelTemplater.ApplyTemplate(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); @@ -284,31 +195,12 @@ public void TestIssue405() { using var path = AutoDeletingPath.Create(); var value = new[] { new { id = 1, name = "test" } }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/sharedStrings.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/sharedStrings.xml"); Assert.StartsWith(" - /// Using stream.SaveAs will close the Stream automatically when Specifying excelType - /// https://gitee.com/dotnetchina/MiniExcel/issues/I57WMM - /// - [Fact] - public void TestIssueI57WMM() - { - Dictionary[] sheets = [new() { ["ID"] = "0001", ["Name"] = "Jack" }]; - using var stream = new MemoryStream(); - stream.SaveAs(sheets, excelType: ExcelType.CSV); - stream.Position = 0; - - // convert stream to string - using var reader = new StreamReader(stream); - var text = reader.ReadToEnd(); - - Assert.Equal("ID,Name\r\n0001,Jack\r\n", text); - } - [Fact] public void TestIssue370() { @@ -335,9 +227,9 @@ public void TestIssue370() }, Formatting.Indented); var value = JsonConvert.DeserializeObject>>(json); - MiniExcel.SaveAs(path.ToString(), value, configuration: config); + _excelExporter.Export(path.ToString(), value, configuration: config); - var rows = MiniExcel.Query(path.ToString()).ToList(); + 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); @@ -361,9 +253,9 @@ public void TestIssue369() }; using var path = AutoDeletingPath.Create(); var value = new[] { new { id = 1, name = "Jack", createdate = new DateTime(2022, 04, 12), point = 123.456 } }; - MiniExcel.SaveAs(path.ToString(), value, configuration: config); + _excelExporter.Export(path.ToString(), value, configuration: config); - var rows = MiniExcel.Query(path.ToString()).ToList(); + 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); @@ -377,9 +269,9 @@ public void TestIssueI4ZYUU() { using var path = AutoDeletingPath.Create(); TestIssueI4ZYUUDto[] value = [new() { MyProperty = "1", MyProperty2 = new DateTime(2022, 10, 15) }]; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("2022-10", rows[1].B); using var workbook = new ClosedXML.Excel.XLWorkbook(path.ToString()); @@ -391,9 +283,9 @@ public void TestIssueI4ZYUU() private class TestIssueI4ZYUUDto { - [ExcelColumn(Name = "ID", Index = 0)] + [MiniExcelColumn(Name = "ID", Index = 0)] public string MyProperty { get; set; } - [ExcelColumn(Name = "CreateDate", Index = 1, Format = "yyyy-MM", Width = 100)] + [MiniExcelColumn(Name = "CreateDate", Index = 1, Format = "yyyy-MM", Width = 100)] public DateTime MyProperty2 { get; set; } } @@ -402,10 +294,10 @@ public void TestIssue360() { var path = PathHelper.GetFile("xlsx/NotDuplicateSharedStrings_10x100.xlsx"); var config = new OpenXmlConfiguration { SharedStringCacheSize = 1 }; - var sheets = MiniExcel.GetSheetNames(path); + var sheets = _excelImporter.GetSheetNames(path); foreach (var sheetName in sheets) { - var dt = MiniExcel.QueryAsDataTable(path, useHeaderRow: true, sheetName: sheetName, configuration: config); + var dt = _excelImporter.QueryAsDataTable(path, useHeaderRow: true, sheetName: sheetName, configuration: config); } } @@ -442,8 +334,8 @@ public void TestIssue352() using var path = AutoDeletingPath.Create(); var reader = table.CreateDataReader(); - MiniExcel.SaveAs(path.ToString(), reader); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _excelExporter.Export(path.ToString(), reader); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { @@ -455,8 +347,8 @@ public void TestIssue352() using var path = AutoDeletingPath.Create(); var reader = table.CreateDataReader(); - MiniExcel.SaveAs(path.ToString(), reader, false); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _excelExporter.Export(path.ToString(), reader, false); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { @@ -466,8 +358,8 @@ public void TestIssue352() using var path = AutoDeletingPath.Create(); var reader = table.CreateDataReader(); - MiniExcel.SaveAs(path.ToString(), reader); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _excelExporter.Export(path.ToString(), reader); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } } @@ -490,9 +382,9 @@ public void TestIssue401(bool autoFilter, int count) var reader = table.CreateDataReader(); using var path = AutoDeletingPath.Create(); var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - MiniExcel.SaveAs(path.ToString(), reader, configuration: config); + _excelExporter.Export(path.ToString(), reader, configuration: config); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; Assert.Equal(count, cnt); } @@ -507,9 +399,9 @@ public void TestIssue401(bool autoFilter, int count) var reader = table.CreateDataReader(); using var path = AutoDeletingPath.Create(); var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - MiniExcel.SaveAs(path.ToString(), reader, false, configuration: config); + _excelExporter.Export(path.ToString(), reader, false, configuration: config); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; Assert.Equal(count, cnt); } @@ -522,9 +414,9 @@ public void TestIssue401(bool autoFilter, int count) var reader = table.CreateDataReader(); using var path = AutoDeletingPath.Create(); var config = new OpenXmlConfiguration { AutoFilter = autoFilter }; - MiniExcel.SaveAs(path.ToString(), reader, configuration: config); + _excelExporter.Export(path.ToString(), reader, configuration: config); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; Assert.Equal(count, cnt); } @@ -540,7 +432,7 @@ public void TestIssue401(bool autoFilter, int count) using var command = connection.CreateCommand(); command.CommandText = """ - SELECT + SELECT 'MiniExcel' as Column1, 1 as Column2 @@ -549,10 +441,10 @@ UNION ALL """; using var reader = command.ExecuteReader(); - MiniExcel.SaveAs(path.ToString(), reader, configuration: config); + _excelExporter.Export(path.ToString(), reader, configuration: config); } - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "autoFilter").Count; Assert.Equal(count, cnt); } @@ -573,7 +465,7 @@ UNION ALL using (var transaction = connection.BeginTransaction()) using (var stream = File.OpenRead(xlsxPath)) { - var rows = stream.Query(); + var rows = _excelImporter.Query(stream); foreach (var row in rows) connection.Execute( "insert into T (A,B) values (@A,@B)", @@ -591,10 +483,10 @@ UNION ALL using var command = new SQLiteCommand("select * from T", connection); connection.Open(); using var reader = command.ExecuteReader(); - MiniExcel.SaveAs(path.ToString(), reader, configuration: config); + _excelExporter.Export(path.ToString(), reader, configuration: config); } - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "autoFilter").Count; Assert.Equal(count, cnt); } @@ -607,12 +499,12 @@ public async Task TestIssue307() var path = file.ToString(); var value = new[] { new { id = 1, name = "Jack" } }; - await MiniExcel.SaveAsAsync(path, value); - Assert.Throws(() => MiniExcel.SaveAs(path, value)); + await _excelExporter.ExportAsync(path, value); + Assert.Throws(() => _excelExporter.Export(path, value)); - await MiniExcel.SaveAsAsync(path, value, overwriteFile: true); - await Assert.ThrowsAsync(async () => await MiniExcel.SaveAsAsync(path, value)); - await MiniExcel.SaveAsAsync(path, value, overwriteFile: true); + await _excelExporter.ExportAsync(path, value, overwriteFile: true); + await Assert.ThrowsAsync(async () => await _excelExporter.ExportAsync(path, value)); + await _excelExporter.ExportAsync(path, value, overwriteFile: true); } [Fact] @@ -620,8 +512,8 @@ public void TestIssue310() { using var path = AutoDeletingPath.Create(); var value = new[] { new TestIssue310Dto { V1 = null }, new TestIssue310Dto { V1 = 2 } }; - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToList(); } [Fact] @@ -633,8 +525,8 @@ public void TestIssue310Fix497() new TestIssue310Dto { V1 = null }, new TestIssue310Dto { V1 = 2 } }; - MiniExcel.SaveAs(path.ToString(), value, configuration: new OpenXmlConfiguration { EnableWriteNullValueCell = false }); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { EnableWriteNullValueCell = false }); + var rows = _excelImporter.Query(path.ToString()).ToList(); } private class TestIssue310Dto @@ -660,9 +552,9 @@ public void TestIssue343() table.Rows.Add(date, date); } var reader = table.CreateDataReader(); - MiniExcel.SaveAs(path.ToString(), reader); + _excelExporter.Export(path.ToString(), reader); - var rows = MiniExcel.Query(path.ToString(), true).ToArray(); + var rows = _excelImporter.Query(path.ToString(), true).ToArray(); Assert.Equal(date, rows[0].time1); Assert.Equal(date, rows[0].time2); } @@ -670,10 +562,10 @@ public void TestIssue343() [Fact] public void TestIssueI4YCLQ_2() { - var c = ExcelOpenXmlUtils.ConvertColumnName(1); - var c2 = ExcelOpenXmlUtils.ConvertColumnName(3); + var c = GeneralHelper.ConvertColumnName(1); + var c2 = GeneralHelper.ConvertColumnName(3); var path = PathHelper.GetFile("xlsx/TestIssueI4YCLQ_2.xlsx"); - var rows = MiniExcel.Query(path, startCell: "B2").ToList(); + var rows = _excelImporter.Query(path, startCell: "B2").ToList(); Assert.Null(rows[0].站点编码); Assert.Equal("N1", rows[0].站址名称); @@ -690,60 +582,29 @@ public void TestIssueI4YCLQ_2() private class TestIssueI4YCLQ_2Dto { - [ExcelColumnIndex("A")] + [MiniExcelColumnIndex("A")] public string 站点编码 { get; set; } - [ExcelColumnIndex("B")] + [MiniExcelColumnIndex("B")] public string 站址名称 { get; set; } - [ExcelColumnIndex("C")] + [MiniExcelColumnIndex("C")] public string 值1 { get; set; } - [ExcelColumnIndex("D")] + [MiniExcelColumnIndex("D")] public string 值2 { get; set; } - [ExcelColumnIndex("E")] + [MiniExcelColumnIndex("E")] public string 值3 { get; set; } - [ExcelColumnIndex("F")] + [MiniExcelColumnIndex("F")] public string 资源ID { get; set; } - [ExcelColumnIndex("G")] + [MiniExcelColumnIndex("G")] public string 值4 { get; set; } - [ExcelColumnIndex("H")] + [MiniExcelColumnIndex("H")] public string 值5 { get; set; } - [ExcelColumnIndex("I")] + [MiniExcelColumnIndex("I")] public string 值6 { get; set; } public string 值7 { get; set; } - [ExcelColumnName("NotExist")] + [MiniExcelColumnName("NotExist")] public string 值8 { get; set; } } - [Fact] - public async Task TestIssue338() - { - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - { - var path = PathHelper.GetFile("csv/TestIssue338.csv"); - var row = MiniExcel.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 = MiniExcel.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 = stream.QueryAsync(configuration: config, excelType: ExcelType.CSV).ToBlockingEnumerable().FirstOrDefault(); - Assert.Equal("中文测试内容", row!.A); - } - } - } [Fact] public void TestIssueI4WM67() @@ -754,8 +615,8 @@ public void TestIssueI4WM67() { ["users"] = Array.Empty() }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Single(rows); } @@ -777,7 +638,7 @@ public void TestIssueI4WXFB() ["Amount"] = 1000, ["Department"] = "HR" }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); } { @@ -793,22 +654,8 @@ public void TestIssueI4WXFB() ["Amount"] = 1000, ["Department"] = "HR" }; - Assert.Throws(() => MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value, config)); - } - } - - [Fact] - public void TestIssueI4WDA9() - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - var value = new DataTable(); - { - value.Columns.Add("\"name\""); - value.Rows.Add("\"Jack\""); + Assert.Throws(() => _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value, config)); } - - MiniExcel.SaveAs(path.ToString(), value); - Assert.Equal("\"\"\"name\"\"\"\r\n\"\"\"Jack\"\"\"\r\n", File.ReadAllText(path.ToString())); } [Fact] @@ -832,7 +679,7 @@ public void TestIssue331_2() }); using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), data, configuration: config); + _excelExporter.Export(path.ToString(), data, configuration: config); CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(cln); } @@ -851,9 +698,9 @@ public void TestIssue331() }); using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), data); + _excelExporter.Export(path.ToString(), data); - var rows = MiniExcel.Query(path.ToString(), startCell: "A2").ToArray(); + var rows = _excelImporter.Query(path.ToString(), startCell: "A2").ToArray(); Assert.Equal(1.5, rows[2].B); Assert.Equal(1.5, rows[2].C); @@ -875,16 +722,16 @@ public void TestIssueI4TXGT() var path = file.ToString(); var value = new[] { new TestIssueI4TXGTDto { ID = 1, Name = "Apple", Spc = "X", Up = 6999 } }; - MiniExcel.SaveAs(path, value); + _excelExporter.Export(path, value); { - var rows = MiniExcel.Query(path).ToList(); + 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 = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(1, rows[0].ID); Assert.Equal("Apple", rows[0].Name); Assert.Equal("X", rows[0].Spc); @@ -923,10 +770,10 @@ public void TestIssue328() file = File.ReadAllBytes(PathHelper.GetFile("other/TestIssue327.txt")) }, }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); var rowIndx = 0; - using var reader = MiniExcel.GetReader(path.ToString(), true); + using var reader = _excelImporter.GetDataReader(path.ToString(), true); Assert.Equal("id", reader.GetName(0)); Assert.Equal("name", reader.GetName(1)); @@ -963,8 +810,8 @@ public void TestIssue327() new { id = 2, file = File.ReadAllBytes(PathHelper.GetFile("other/TestIssue327.txt")) }, new { id = 3, file = File.ReadAllBytes(PathHelper.GetFile("other/TestIssue327.html")) }, }; - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); Assert.Equal(value[0].file, rows[0].file); Assert.Equal(value[1].file, rows[1].file); @@ -973,177 +820,6 @@ public void TestIssue327() Assert.Equal("Hello MiniExcel", Encoding.UTF8.GetString(rows[2].file)); } - [Fact] - public void TestIssue316() - { - // XLSX - { - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - var value = new[] - { - new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-01-31",CultureInfo.InvariantCulture)} - }; - var config = new OpenXmlConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - MiniExcel.SaveAs(path, value, configuration: config); - - //Datetime error - { - Assert.Throws(() => - { - var conf = new OpenXmlConfiguration - { - Culture = new CultureInfo("en-US"), - }; - _ = MiniExcel.Query(path, configuration: conf).ToList(); - }); - } - - // dynamic - { - var rows = MiniExcel.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(); - var path = file.ToString(); - var value = new[] - { - new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-05-12", CultureInfo.InvariantCulture)} - }; - { - var config = new OpenXmlConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - MiniExcel.SaveAs(path, value, configuration: config); - } - - { - var rows = MiniExcel.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 OpenXmlConfiguration - { - Culture = new CultureInfo("en-US"), - }; - var rows = MiniExcel.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 OpenXmlConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - var rows = MiniExcel.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); - } - } - } - - // CSV - { - { - using var file = AutoDeletingPath.Create(ExcelType.CSV); - var path = file.ToString(); - var value = new[] - { - new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-01-31", CultureInfo.InvariantCulture)} - }; - var config = new CsvConfiguration - { - Culture = new CultureInfo("fr-FR"), - }; - MiniExcel.SaveAs(path, value, configuration: config); - - //Datetime error - { - Assert.Throws(() => - { - var conf = new CsvConfiguration - { - Culture = new CultureInfo("en-US"), - }; - _ = MiniExcel.Query(path, configuration: conf).ToList(); - }); - } - - // dynamic - { - var rows = MiniExcel.Query(path, true).ToList(); - Assert.Equal("123456,789", rows[0].Amount); - Assert.Equal("31/01/2018 00:00:00", rows[0].CreateTime); - } - } - - // type - { - var path = PathHelper.GetTempFilePath("csv"); - 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"), - }; - MiniExcel.SaveAs(path, value, configuration: config); - } - - { - var rows = MiniExcel.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 = MiniExcel.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 = MiniExcel.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); - } - } - } - } - - private class TestIssue316Dto - { - public decimal Amount { get; set; } - public DateTime CreateTime { get; set; } - } - /// /// https://github.com/mini-software/MiniExcel/issues/325 /// @@ -1156,9 +832,9 @@ public void TestIssue325() { "sheet1",new[]{ new { id = 1, date = DateTime.Parse("2022-01-01") } }}, { "sheet2",new[]{ new { id = 2, date = DateTime.Parse("2022-01-01") } }}, }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/_rels/sheet2.xml.rels"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/_rels/sheet2.xml.rels"); var cnt = Regex.Matches(xml, "Id=\"drawing2\"").Count; Assert.True(cnt == 1); } @@ -1171,37 +847,21 @@ public void TestIssue325() public void TestIssueI49RZH() { // xlsx + using var path = AutoDeletingPath.Create(); + var value = new[] { - using var path = AutoDeletingPath.Create(); - var value = new[] - { - new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, - new TestIssueI49RZHDto{ dd = null} - }; - MiniExcel.SaveAs(path.ToString(), value); - - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("2022-01-22", rows[1].A); - } - - //TODO:CSV - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - var value = new[] - { - new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, - new TestIssueI49RZHDto{ dd = null} - }; - MiniExcel.SaveAs(path.ToString(), value); + new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, + new TestIssueI49RZHDto{ dd = null} + }; + _excelExporter.Export(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("2022-01-22", rows[1].A); - } + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("2022-01-22", rows[1].A); } private class TestIssueI49RZHDto { - [ExcelFormat("yyyy-MM-dd")] + [MiniExcelFormat("yyyy-MM-dd")] public DateTimeOffset? dd { get; set; } } @@ -1211,38 +871,21 @@ private class TestIssueI49RZHDto [Fact] public void TestIssue312() { - //xlsx - { - using var path = AutoDeletingPath.Create(); - TestIssue312Dto[] value = - [ - new() { Value = 12345.6789}, - new() { Value = null} - ]; - MiniExcel.SaveAs(path.ToString(), value); - - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("12,345.68", rows[1].A); - } - - //TODO:CSV - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - TestIssue312Dto[] value = - [ - new() { Value = 12345.6789}, - new() { Value = null} - ]; - MiniExcel.SaveAs(path.ToString(), value); + using var path = AutoDeletingPath.Create(); + TestIssue312Dto[] value = + [ + new() { Value = 12345.6789}, + new() { Value = null} + ]; + _excelExporter.Export(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("12,345.68", rows[1].A); - } + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("12,345.68", rows[1].A); } private class TestIssue312Dto { - [ExcelFormat("0,0.00")] + [MiniExcelFormat("0,0.00")] public double? Value { get; set; } } @@ -1256,9 +899,9 @@ public void TestIssue209() try { var path = PathHelper.GetFile("xlsx/TestIssue309.xlsx"); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); } - catch (ExcelInvalidCastException ex) + catch (MiniExcelInvalidCastException ex) { Assert.Equal("SEQ", ex.ColumnName); Assert.Equal(4, ex.Row); @@ -1287,13 +930,13 @@ public void TestIssue318() { new { Name="github", Image=imageByte}, }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), 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 = MiniExcel.Query(path.ToString(), true).ToList(); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); var actulBase64 = Convert.ToBase64String((byte[])rows[0].Image); Assert.Equal(expectedBase64, actulBase64); } @@ -1301,7 +944,7 @@ public void TestIssue318() // import to base64 string { var config = new OpenXmlConfiguration { EnableConvertByteArray = false }; - var rows = MiniExcel.Query(path.ToString(), true, configuration: config).ToList(); + var rows = _excelImporter.Query(path.ToString(), true, configuration: config).ToList(); var image = (string)rows[0].Image; Assert.StartsWith("@@@fileid@@@,xl/media/", image); } @@ -1324,13 +967,13 @@ public void TestIssue304() new { Name="reddit", Image=File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png"))}, new { Name="statck_overflow", Image=File.ReadAllBytes(PathHelper.GetFile("images/statck_overflow_logo.png"))}, }; - MiniExcel.SaveAs(path, value); + _excelExporter.Export(path, value); - Assert.Contains("/xl/media/", Helpers.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", Helpers.GetZipFileContent(path, "xl/drawings/drawing1.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", Helpers.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=", Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); - Assert.Contains("../drawings/drawing1.xml", Helpers.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); + 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")); } /// @@ -1348,9 +991,9 @@ public void TestIssueI4HL54() { { "Texts",reader} }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); Assert.Equal("Hello World1", rows[0].Text); Assert.Equal("Hello World2", rows[1].Text); } @@ -1365,39 +1008,26 @@ public void TestIssue294() { using var path = AutoDeletingPath.Create(); var value = new[] { new { Name = " Jack" } }; - MiniExcel.SaveAs(path.ToString(), value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _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" } }; - MiniExcel.SaveAs(path.ToString(), value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _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 " } }; - MiniExcel.SaveAs(path.ToString(), value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _excelExporter.Export(path.ToString(), value); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("xml:space=\"preserve\"", sheetXml); } } - /// - /// Column '' does not belong to table when csv convert to datatable #298 - /// https://github.com/mini-software/MiniExcel/issues/298 - /// - [Fact] - public void TestIssue298() - { - var path = PathHelper.GetFile("/csv/TestIssue298.csv"); -#pragma warning disable CS0618 // Type or member is obsolete - var dt = MiniExcel.QueryAsDataTable(path); -#pragma warning restore CS0618 - Assert.Equal(["ID", "Name", "Age"], dt.Columns.Cast().Select(x => x.ColumnName)); - } /// /// SaveAsByTemplate if there is & in the cell value, it will be & @@ -1413,9 +1043,9 @@ public void TestIssueI4DQUN() { "Title", "Hello & World < , > , \" , '" }, { "Details", new[] { new { Value = "Hello & Value < , > , \" , '" } } }, }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("Hello & World < , > , \" , '", sheetXml); Assert.Contains("Hello & Value < , > , \" , '", sheetXml); } @@ -1429,17 +1059,17 @@ public void TestIssue190() { using var path = AutoDeletingPath.Create(); var value = new TestIssue190Dto[] { }; - MiniExcel.SaveAs(path.ToString(), value, configuration: new OpenXmlConfiguration { AutoFilter = false }); + _excelExporter.Export(path.ToString(), value, configuration: new OpenXmlConfiguration { AutoFilter = false }); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.DoesNotContain("", sheetXml); } { using var path = AutoDeletingPath.Create(); var value = new TestIssue190Dto[] { }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("", sheetXml); } { @@ -1449,9 +1079,9 @@ public void TestIssue190() new() { ID = 1, Name = "Jack", Age = 32 }, new() { ID = 2, Name = "Lisa", Age = 45 } ]; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var sheetXml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var sheetXml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("", sheetXml); } } @@ -1463,58 +1093,6 @@ private class TestIssue190Dto public int Age { get; set; } } - /// - /// [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) - /// - [Fact] - public void TestIssue292() - { - { - var xlsxPath = PathHelper.GetFile("/xlsx/TestIssue292.xlsx"); - using var path = AutoDeletingPath.Create(ExcelType.CSV); - MiniExcel.ConvertXlsxToCsv(xlsxPath, path.ToString()); - - var actualCotent = File.ReadAllText(path.ToString()); - Assert.Equal( - """ - Name,Age,Name,Age - Jack,22,Mike,25 - Henry,44,Jerry,44 - - """, - actualCotent); - } - - { - var csvPath = PathHelper.GetFile("/csv/TestIssue292.csv"); - using var path = AutoDeletingPath.Create(); - MiniExcel.ConvertCsvToXlsx(csvPath, path.ToString()); - - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal(3, rows.Count); - Assert.Equal("Name", rows[0].A); - Assert.Equal("Age", rows[0].B); - Assert.Equal("Name", rows[0].C); - Assert.Equal("Age", rows[0].D); - Assert.Equal("Jack", rows[1].A); - Assert.Equal("22", rows[1].B); - Assert.Equal("Mike", rows[1].C); - Assert.Equal("25", rows[1].D); - } - } - - /// - /// [Csv Query then SaveAs will throw "Stream was not readable." exception · Issue #293 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/293) - /// - [Fact] - public void TestIssue293() - { - var path = PathHelper.GetFile("/csv/Test5x2.csv"); - using var tempPath = AutoDeletingPath.Create(); - using var csv = File.OpenRead(path); - var value = csv.Query(useHeaderRow: false, excelType: ExcelType.CSV); - MiniExcel.SaveAs(tempPath.ToString(), value, printHeader: false, excelType: ExcelType.XLSX); - } [Fact] public void TestIssueI49RYZ() @@ -1527,8 +1105,8 @@ public void TestIssueI49RYZ() new() { Name="Lisa", UserType=null } ]; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), values); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _excelExporter.Export(path.ToString(), values); + 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); @@ -1544,8 +1122,8 @@ public void TestIssue286() new() { E = TestIssue286Enum.VIP2 } ]; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), values); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _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); @@ -1586,11 +1164,11 @@ public void TestIssue283() { "sheet01", cn.ExecuteReader("select 'v1' col1") }, { "sheet02", cn.ExecuteReader("select 'v2' col1") } }; - var rows = MiniExcel.SaveAs(path.ToString(), sheets); + var rows = _excelExporter.Export(path.ToString(), sheets); Assert.Equal(2, rows.Length); } - var sheetNames = MiniExcel.GetSheetNames(path.ToString()); + var sheetNames = _excelImporter.GetSheetNames(path.ToString()); Assert.Equal(["sheet01", "sheet02"], sheetNames); } @@ -1602,25 +1180,25 @@ public void TestIssueI40QA5() { { var path = PathHelper.GetFile("/xlsx/TestIssueI40QA5_1.xlsx"); - var rows = MiniExcel.Query(path).ToList(); + 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 = MiniExcel.Query(path).ToList(); + 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 = MiniExcel.Query(path).ToList(); + 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 = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Null(rows[0].Empno); Assert.Null(rows[1].Empno); } @@ -1628,7 +1206,7 @@ public void TestIssueI40QA5() private class TestIssueI40QA5Dto { - [ExcelColumnName(excelColumnName: "EmployeeNo", aliases: new[] { "EmpNo", "No" })] + [MiniExcelColumnName(columnName: "EmployeeNo", aliases: new[] { "EmpNo", "No" })] public string Empno { get; set; } public string Name { get; set; } } @@ -1642,26 +1220,26 @@ public void TestIssues133() var value = new DataTable(); value.Columns.Add("Id"); value.Columns.Add("Name"); - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _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", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } { using var path = AutoDeletingPath.Create(); var value = Array.Empty(); - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _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", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } } @@ -1680,9 +1258,9 @@ public void TestIssueI45TF5_2() { var value = new[] { new Dictionary { { "Col1&Col2", "V1&V2" } } }; var path = PathHelper.GetTempPath(); - MiniExcel.SaveAs(path, value); + _excelExporter.Export(path, value); //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. - Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not + SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not } { @@ -1690,9 +1268,9 @@ public void TestIssueI45TF5_2() dt.Columns.Add("Col1&Col2"); dt.Rows.Add("V1&V2"); var path = PathHelper.GetTempPath(); - MiniExcel.SaveAs(path, dt); + _excelExporter.Export(path, dt); //System.Xml.XmlException : '<' is an unexpected token. The expected token is ';'. - Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not + SheetHelper.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); //check illegal format or not } } @@ -1700,8 +1278,8 @@ public void TestIssueI45TF5_2() public void TestIssueI45TF5() { using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), new[] { new { C1 = "1&2;3,4", C2 = "1&2;3,4" } }); - var sheet1Xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + _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); } @@ -1717,33 +1295,17 @@ public void TestIssue280() new() { ID = 2, Name = "Mike" } ]; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); } private class TestIssue280Dto { - [ExcelColumnWidth(20)] + [MiniExcelColumnWidth(20)] public int ID { get; set; } - [ExcelColumnWidth(15.50)] + [MiniExcelColumnWidth(15.50)] public string Name { get; set; } } - /// - /// Csv not support QueryAsDataTable #279 https://github.com/mini-software/MiniExcel/issues/279 - /// - [Fact] - public void TestIssue279() - { - var path = PathHelper.GetFile("/csv/TestHeader.csv"); -#pragma warning disable CS0618 // Type or member is obsolete - using var dt = MiniExcel.QueryAsDataTable(path, true, null, ExcelType.CSV); -#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"]); - } - /// /// 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 /// @@ -1751,7 +1313,7 @@ public void TestIssue279() public void TestIssue272() { var path = PathHelper.GetFile("/xlsx/TestIssue272.xlsx"); - Assert.Throws(() => MiniExcel.Query(path).ToList()); + Assert.Throws(() => _excelImporter.Query(path).ToList()); } /// @@ -1761,7 +1323,7 @@ public void TestIssue272() public void TestIssue267() { var path = PathHelper.GetFile("/xlsx/TestIssue267.xlsx"); - var row = MiniExcel.Query(path).SingleOrDefault(); + var row = _excelImporter.Query(path).SingleOrDefault(); Assert.Equal(10618, row!.A); Assert.Equal("2021-02-23", row.B); Assert.Equal(43.199999999999996, row.C); @@ -1804,7 +1366,7 @@ public void TestIssueI3X2ZL() try { var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_datetime_error.xlsx"); - var rows = MiniExcel.Query(path, startCell: "B3").ToList(); + var rows = _excelImporter.Query(path, startCell: "B3").ToList(); } catch (InvalidCastException ex) { @@ -1817,7 +1379,7 @@ public void TestIssueI3X2ZL() try { var path = PathHelper.GetFile("xlsx/TestIssueI3X2ZL_int_error.xlsx"); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); } catch (InvalidCastException ex) { @@ -1834,35 +1396,6 @@ private class IssueI3X2ZLDTO public DateTime Col2 { get; set; } } - /// - /// [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(); - CsvToXlsx(csvPath, path.ToString()); - var rows = MiniExcel.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); - } - - private static void CsvToXlsx(string csvPath, string xlsxPath) - { - var value = MiniExcel.Query(csvPath, true); - MiniExcel.SaveAs(xlsxPath, value); - } - /// /// [SaveAsByTemplate support DateTime custom format · Issue #255 · mini-software/MiniExcel] /// (https://github.com/mini-software/MiniExcel/issues/255) @@ -1881,8 +1414,8 @@ public void Issue255() new Issue255DTO { Time = new DateTime(2021, 01, 01), Time2 = new DateTime(2021, 01, 01) } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("2021", rows[1].A.ToString()); Assert.Equal("2021", rows[1].B.ToString()); } @@ -1893,18 +1426,18 @@ public void Issue255() { new Issue255DTO { Time = new DateTime(2021, 01, 01) } }; - MiniExcel.SaveAs(path.ToString(), value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelExporter.Export(path.ToString(), value); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("2021", rows[1].A.ToString()); } } private class Issue255DTO { - [ExcelFormat("yyyy")] + [MiniExcelFormat("yyyy")] public DateTime Time { get; set; } - [ExcelColumn(Format = "yyyy")] + [MiniExcelColumn(Format = "yyyy")] public DateTime Time2 { get; set; } } @@ -1916,86 +1449,11 @@ private class Issue255DTO public void Issue256() { var path = PathHelper.GetFile("xlsx/TestIssue256.xlsx"); - var rows = MiniExcel.Query(path, false).ToList(); + var rows = _excelImporter.Query(path, false).ToList(); Assert.Equal(new DateTime(2003, 4, 16), rows[1].A); Assert.Equal(new DateTime(2004, 4, 16), rows[1].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); - MiniExcel.SaveAs(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")) - }; - MiniExcel.SaveAs(path.ToString(), value, excelType: ExcelType.CSV, 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); - MiniExcel.SaveAs(path.ToString(), value); - const string expected = - """ - col1 - 世界你好 - - """; - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } - } - - /// - /// [CSV SaveAs support datareader · Issue #251 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/251) - /// - [Fact] - 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); - MiniExcel.SaveAs(path.ToString(), reader); - const string expected = - """" - a,b - """<>+-*//}{\\n",1234567890 - "Hello World",-1234567890 - - """"; - - Assert.Equal(expected, File.ReadAllText(path.ToString())); - } - /// /// No error exception throw when reading xls file #242 /// @@ -2003,41 +1461,10 @@ public void Issue251() public void Issue242() { var path = PathHelper.GetFile("xls/TestIssue242.xls"); - Assert.Throws(() => MiniExcel.Query(path).ToList()); + Assert.Throws(() => _excelImporter.Query(path).ToList()); using var stream = File.OpenRead(path); - Assert.Throws(() => stream.Query().ToList()); - } - - /// - /// Csv type mapping Query error "cannot be converted to xxx type" #243 - /// - [Fact] - public void Issue243() - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - 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) }, - }; - MiniExcel.SaveAs(path.ToString(), value); - - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("Jack", rows[0].Name); - Assert.Equal(25, rows[0].Age); - Assert.Equal(new DateTime(2021, 01, 03), rows[0].InDate); - - Assert.Equal("Henry", rows[1].Name); - Assert.Equal(36, rows[1].Age); - 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; } + Assert.Throws(() => _excelImporter.Query(stream).ToList()); } /// @@ -2046,55 +1473,29 @@ private class Issue243Dto [Fact] public void Issue241() { - Issue241Dto[] value = [ new() { Name = "Jack", InDate = new DateTime(2021,01,04) }, new() { Name = "Henry", InDate = new DateTime(2020,04,05) } ]; - // csv - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - MiniExcel.SaveAs(path.ToString(), value); - - { - var rows = MiniExcel.Query(path.ToString(), true).ToList(); - Assert.Equal(rows[0].InDate, "01 04, 2021"); - Assert.Equal(rows[1].InDate, "04 05, 2020"); - } - - { - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal(rows[0].InDate, new DateTime(2021, 01, 04)); - Assert.Equal(rows[1].InDate, new DateTime(2020, 04, 05)); - } - } - - // xlsx - { - using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), value); + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), value); - { - var rows = MiniExcel.Query(path.ToString(), true).ToList(); - Assert.Equal(rows[0].InDate, "01 04, 2021"); - Assert.Equal(rows[1].InDate, "04 05, 2020"); - } + var rows1 = _excelImporter.Query(path.ToString(), true).ToList(); + Assert.Equal(rows1[0].InDate, "01 04, 2021"); + Assert.Equal(rows1[1].InDate, "04 05, 2020"); - { - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal(rows[0].InDate, new DateTime(2021, 01, 04)); - Assert.Equal(rows[1].InDate, new DateTime(2020, 04, 05)); - } - } + var rows2 = _excelImporter.Query(path.ToString()).ToList(); + 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; } - [ExcelFormat("MM dd, yyyy")] + [MiniExcelFormat("MM dd, yyyy")] public DateTime InDate { get; set; } } @@ -2111,7 +1512,7 @@ public void Issue132() new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); } { @@ -2125,7 +1526,7 @@ public void Issue132() { TableStyles = TableStyles.None }; - MiniExcel.SaveAs(path.ToString(), value, configuration: config); + _excelExporter.Export(path.ToString(), value, configuration: config); } { @@ -2137,7 +1538,7 @@ public void Issue132() new { name = "Henry", Age = 36, InDate = new DateTime(2020,05,03)}, }) ); - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); } } @@ -2167,23 +1568,23 @@ public void Issue235() dataSet.Tables.Add(users); dataSet.Tables.Add(department); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), dataSet); + var rowsWritten = _excelExporter.Export(path.ToString(), dataSet); Assert.Equal(2, rowsWritten.Length); Assert.Equal(2, rowsWritten[0]); - var sheetNames = MiniExcel.GetSheetNames(path.ToString()); + var sheetNames = _excelImporter.GetSheetNames(path.ToString()); Assert.Equal("users", sheetNames[0]); Assert.Equal("department", sheetNames[1]); { - var rows = MiniExcel.Query(path.ToString(), true, sheetName: "users").ToList(); + var rows = _excelImporter.Query(path.ToString(), 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 = MiniExcel.Query(path.ToString(), true, sheetName: "department").ToList(); + var rows = _excelImporter.Query(path.ToString(), true, sheetName: "department").ToList(); Assert.Equal("01", rows[0].ID); Assert.Equal("HR", rows[0].Name); Assert.Equal("02", rows[1].ID); @@ -2198,36 +1599,15 @@ public void Issue235() public void Issue233() { var path = PathHelper.GetFile("xlsx/TestIssue233.xlsx"); -#pragma warning disable CS0618 // Type or member is obsolete - var dt = MiniExcel.QueryAsDataTable(path); -#pragma warning restore CS0618 + + var dt = _excelImporter.QueryAsDataTable(path); + var rows = dt.Rows; Assert.Equal(0.55, rows[0]["Size"]); Assert.Equal("0.55/1.1", rows[1]["Size"]); } - /// - /// Csv Query split comma not correct #237 - /// https://github.com/mini-software/MiniExcel/issues/237 - /// - [Fact] - public void Issue237() - { - var value = new[] - { - new{ id="\"\"1,2,3\"\""}, - new{ id="1,2,3"}, - }; - using var path = AutoDeletingPath.Create(ExcelType.CSV); - MiniExcel.SaveAs(path.ToString(), value); - - var rows = MiniExcel.Query(path.ToString(), true).ToList(); - - Assert.Equal("\"\"1,2,3\"\"", rows[0].id); - Assert.Equal("1,2,3", rows[1].id); - } - /// /// SaveAs support multiple sheets #234 /// @@ -2252,21 +1632,21 @@ public void Issue234() ["users"] = users, ["department"] = department }; - MiniExcel.SaveAs(path, sheets); + _excelExporter.Export(path, sheets); - var sheetNames = MiniExcel.GetSheetNames(path); + var sheetNames = _excelImporter.GetSheetNames(path); Assert.Equal("users", sheetNames[0]); Assert.Equal("department", sheetNames[1]); { - var rows = MiniExcel.Query(path, true, sheetName: "users").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); } { - var rows = MiniExcel.Query(path, true, sheetName: "department").ToList(); + 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); @@ -2321,8 +1701,8 @@ public void Issue230() using (var reader = cmd3.ExecuteReader(CommandBehavior.CloseConnection)) { using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), reader, printHeader: true); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _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); } @@ -2336,9 +1716,9 @@ public void Issue230() public void Issue229() { var path = PathHelper.GetFile("xlsx/TestIssue229.xlsx"); -#pragma warning disable CS0618 // Type or member is obsolete - using var dt = MiniExcel.QueryAsDataTable(path); -#pragma warning restore CS0618 + + using var dt = _excelImporter.QueryAsDataTable(path); + foreach (DataColumn column in dt.Columns) { var v = dt.Rows[3][column]; @@ -2360,7 +1740,7 @@ public void Issue122() { var path = PathHelper.GetFile("xlsx/TestIssue122.xlsx"); { - var rows = MiniExcel.Query(path, useHeaderRow: true, configuration: config).ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: true, configuration: config).ToList(); Assert.Equal("HR", rows[0].Department); Assert.Equal("HR", rows[1].Department); Assert.Equal("HR", rows[2].Department); @@ -2373,7 +1753,7 @@ public void Issue122() { var path = PathHelper.GetFile("xlsx/TestIssue122_2.xlsx"); { - var rows = MiniExcel.Query(path, useHeaderRow: true, configuration: config).ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: true, configuration: config).ToList(); Assert.Equal("V1", rows[2].Test1); Assert.Equal("V2", rows[5].Test2); Assert.Equal("V3", rows[1].Test3); @@ -2393,14 +1773,14 @@ public void Issue227() { { var path = PathHelper.GetTempPath("xlsm"); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { new { V = "A1" }, new { V = "A2" } })); + Assert.Throws(() => _excelExporter.Export(path, new[] { new { V = "A1" }, new { V = "A2" } })); File.Delete(path); } { var path = PathHelper.GetFile("xlsx/TestIssue227.xlsm"); { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(100, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); @@ -2413,7 +1793,7 @@ public void Issue227() } { using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal(100, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); @@ -2438,8 +1818,8 @@ public void Issue226() { using var path = AutoDeletingPath.Create(); var templatePath = PathHelper.GetFile("xlsx/TestIssue226.xlsx"); - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); - Assert.Equal("A1:A3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, new { employees = new[] { new { name = "123" }, new { name = "123" } } }); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } /// @@ -2456,11 +1836,10 @@ public void Issue223() new() { { "A", Guid.NewGuid() }, { "B", "HelloWorld" } } ]; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); + + using var dt = _excelImporter.QueryAsDataTable(path.ToString()); -#pragma warning disable CS0618 // Type or member is obsolete - using var dt = MiniExcel.QueryAsDataTable(path.ToString()); -#pragma warning restore CS0618 var columns = dt.Columns; Assert.Equal(typeof(object), columns[0].DataType); Assert.Equal(typeof(object), columns[1].DataType); @@ -2477,7 +1856,7 @@ public void Issue223() public void Issue222() { var path = PathHelper.GetFile("xlsx/TestIssue222.xlsx"); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(typeof(DateTime), rows[1].A.GetType()); Assert.Equal(new DateTime(2021, 4, 29), rows[1].A); } @@ -2491,7 +1870,7 @@ public void Issue147() { { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var rows = MiniExcel.Query(path, useHeaderRow: false, startCell: "C3", sheetName: "Sheet1").ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: 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 }); @@ -2505,13 +1884,13 @@ public void Issue147() Assert.Equal(11, rows.Count); - var columns = MiniExcel.GetColumns(path, startCell: "C3"); + var columns = _excelImporter.GetColumnNames(path, startCell: "C3"); Assert.Equal(["C", "D", "E"], columns); } { var path = PathHelper.GetFile("xlsx/TestIssue147.xlsx"); - var rows = MiniExcel.Query(path, useHeaderRow: true, startCell: "C3", sheetName: "Sheet1").ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: 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 }); @@ -2523,7 +1902,7 @@ public void Issue147() Assert.Equal(10, rows.Count); - var columns = MiniExcel.GetColumns(path, useHeaderRow: true, startCell: "C3"); + var columns = _excelImporter.GetColumnNames(path, useHeaderRow: true, startCell: "C3"); Assert.Equal(["Column1", "Column2", "Column3"], columns); } } @@ -2542,8 +1921,8 @@ public void Issue211() 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"); - MiniExcel.SaveAs(path.ToString(), reader); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _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); @@ -2563,12 +1942,11 @@ public void Issue216() new { Test1 = "1", Test2 = 2 }, new { Test1 = "3", Test2 = 4 } }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); { -#pragma warning disable CS0618 // Type or member is obsolete - using var table = MiniExcel.QueryAsDataTable(path.ToString()); -#pragma warning restore CS0618 + 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"]); @@ -2578,9 +1956,8 @@ public void Issue216() } { -#pragma warning disable CS0618 // Type or member is obsolete - using var dt = MiniExcel.QueryAsDataTable(path.ToString(), false); -#pragma warning restore CS0618 + 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"]); @@ -2600,18 +1977,18 @@ public void IssueI3OSKV() { using var path = AutoDeletingPath.Create(); var value = new[] { new { Test = "12345678901234567890" } }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var A2 = MiniExcel.Query(path.ToString(), true).First().Test; + 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 } }; - MiniExcel.SaveAs(path.ToString(), value); + _excelExporter.Export(path.ToString(), value); - var A2 = MiniExcel.Query(path.ToString(), true).First().Test; + var A2 = _excelImporter.Query(path.ToString(), true).First().Test; Assert.Equal(123456.789, A2); } } @@ -2625,7 +2002,7 @@ public void IssueI3OSKV() public void Issue220() { var path = PathHelper.GetFile("xlsx/TestIssue220.xlsx"); - var rows = MiniExcel.Query(path, useHeaderRow: true); + var rows = _excelImporter.Query(path, useHeaderRow: true); var result = rows .GroupBy(s => s.PRT_ID) .Select(g => new @@ -2649,82 +2026,13 @@ public void Issue220() public void Issue215() { using var stream = new MemoryStream(); - stream.SaveAs(new[] { new { V = "test1" }, new { V = "test2" } }); - var rows = stream.Query(true).ToList(); + _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); } - /// - /// Support Enum Mapping - /// https://github.com/mini-software/MiniExcel/issues/89 - /// - [Fact] - public void Issue89() - { - //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 = stream.Query(excelType: ExcelType.CSV).ToList(); - - Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); - - using var path = AutoDeletingPath.Create(ExcelType.CSV); - MiniExcel.SaveAs(path.ToString(), rows); - var rows2 = MiniExcel.Query(path.ToString()).ToList(); - - Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); - } - - //xlsx - { - var path = PathHelper.GetFile("xlsx/TestIssue89.xlsx"); - var rows = MiniExcel.Query(path).ToList(); - - Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); - - using var xlsxPath = AutoDeletingPath.Create(); - MiniExcel.SaveAs(xlsxPath.ToString(), rows); - var rows2 = MiniExcel.Query(xlsxPath.ToString()).ToList(); - - Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); - Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); - Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); - } - } - - public class Issue89VO - { - public WorkState State { get; set; } - - public enum WorkState - { - OnDuty, - Leave, - Fired - } - } - /// /// DataTable recommended to use Caption for column name first, then use columname /// https://github.com/mini-software/MiniExcel/issues/217 @@ -2739,29 +2047,16 @@ public void Issue217() table.Rows.Add(1, "Jonathan", 23.44); table.Rows.Add(2, "Bill", 56.87); - // openxml - { - using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), table); - - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); - } - - // csv - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - MiniExcel.SaveAs(path.ToString(), table); + using var path = AutoDeletingPath.Create(); + _excelExporter.Export(path.ToString(), table); - var rows = MiniExcel.Query(path.ToString()).ToList(); - Assert.Equal("Name", rows[0].B); - Assert.Equal("Limit", rows[0].C); - } + var rows = _excelImporter.Query(path.ToString()).ToList(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); } /// - /// MiniExcel.SaveAs(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 + /// _ _exporter.Export(path, table,sheetName:“Name”) ,the actual sheetName is Sheet1 /// https://github.com/mini-software/MiniExcel/issues/212 /// [Fact] @@ -2769,9 +2064,9 @@ public void Issue212() { const string sheetName = "Demo"; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); + _excelExporter.Export(path.ToString(), new[] { new { x = 1, y = 2 } }, sheetName: sheetName); - var actualSheetName = MiniExcel.GetSheetNames(path.ToString()).ToList()[0]; + var actualSheetName = _excelImporter.GetSheetNames(path.ToString()).ToList()[0]; Assert.Equal(sheetName, actualSheetName); } @@ -2797,8 +2092,8 @@ public void Issue207() } }; - MiniExcel.SaveAsByTemplate(path, tempaltePath, value); - var rows = MiniExcel.Query(path).ToList(); + _excelTemplater.ApplyTemplate(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); @@ -2822,7 +2117,7 @@ public void Issue207() Assert.Equal("項目4", rows[15].A); Assert.Equal("[]內容1,[]內容2,[]內容3,[]內容4,[]內容5", rows[15].B); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C16", dimension); } @@ -2841,8 +2136,8 @@ public void Issue207() } }; - MiniExcel.SaveAsByTemplate(path, tempaltePath, value); - var rows = MiniExcel.Query(path).ToList(); + _excelTemplater.ApplyTemplate(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); @@ -2852,7 +2147,7 @@ public void Issue207() 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 = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:E15", dimension); } } @@ -2870,8 +2165,8 @@ public void Issue87() Tests = Enumerable.Range(1, 5).Select((_, i) => new { test1 = i, test2 = i }) }; - var rows = MiniExcel.Query(templatePath).ToList(); - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(templatePath).ToList(); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); } /// @@ -2881,7 +2176,7 @@ public void Issue87() public void Issue208() { const string path = "../../../../../samples/xlsx/TestIssue208.xlsx"; - var columns = MiniExcel.GetColumns(path).ToList(); + var columns = _excelImporter.GetColumnNames(path).ToList(); Assert.Equal(16384, columns.Count); Assert.Equal("XFD", columns[16383]); } @@ -2905,9 +2200,9 @@ public void Issue206() { ["employees"] = dt }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B2", dimension); } @@ -2921,9 +2216,9 @@ public void Issue206() dt.Rows.Add("Jack", "HR"); var value = new Dictionary { ["employees"] = dt }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B2", dimension); } } @@ -2957,11 +2252,11 @@ public void Issue193() new { name = "Keaton", department = "IT" } } }; - MiniExcel.SaveAsByTemplate(path, templatePath, value); + _excelTemplater.ApplyTemplate(path, templatePath, value); - foreach (var sheetName in MiniExcel.GetSheetNames(path)) + foreach (var sheetName in _excelImporter.GetSheetNames(path)) { - var rows = MiniExcel.Query(path, sheetName: sheetName).ToList(); + var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -2980,7 +2275,7 @@ public void Issue193() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); /*TODO:row can't contain xmlns*/ @@ -3008,8 +2303,8 @@ public void Issue193() new {name="Keaton",department="IT"} } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("FooCompany", rows[0].A); Assert.Equal("Jack", rows[2].B); @@ -3027,182 +2322,11 @@ public void Issue193() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } - [Fact] - public void Issue142() - { - { - using var file = AutoDeletingPath.Create(); - var path = file.ToString(); - Issue142VO[] values = - [ - new() - { - MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", - MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", - MyProperty7 = "MyProperty7" - } - ]; - var rowsWritten = MiniExcel.SaveAs(path, values); - Assert.Single(rowsWritten); - Assert.Equal(1, rowsWritten[0]); - - { - var rows = MiniExcel.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.Equal(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.Equal(null, rows[0].F); - Assert.Equal("MyProperty3", rows[0].G); - } - - { - var rows = MiniExcel.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); - } - } - - { - using var file = AutoDeletingPath.Create(ExcelType.CSV); - var path = file.ToString(); - Issue142VO[] values = - [ - new() - { - MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", - MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", - MyProperty7 = "MyProperty7" - } - ]; - var rowsWritten = MiniExcel.SaveAs(path, values); - Assert.Single(rowsWritten); - Assert.Equal(1, rowsWritten[0]); - - const string expected = - """ - MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 - MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 - - """; - - Assert.Equal(expected, File.ReadAllText(path)); - - { - var rows = MiniExcel.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); - } - } - - { - using var path = AutoDeletingPath.Create(ExcelType.CSV); - Issue142VoDuplicateColumnName[] input = - [ - new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 } - ]; - Assert.Throws(() => MiniExcel.SaveAs(path.ToString(), input)); - } - } - - [Fact] - public void Issue142_Query() - { - const string path = "../../../../../samples/xlsx/TestIssue142.xlsx"; - const string csvPath = "../../../../../samples/csv/TestIssue142.csv"; - { - var rows = MiniExcel.Query(path).ToList(); - Assert.Equal(0, rows[0].MyProperty1); - } - - Assert.Throws(() => MiniExcel.Query(path).ToList()); - - var rowsXlsx = MiniExcel.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 = MiniExcel.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 Issue142VO - { - [ExcelColumnName("CustomColumnName")] - public string MyProperty1 { get; set; } //index = 1 - [ExcelIgnore] - public string MyProperty7 { get; set; } //index = null - public string MyProperty2 { get; set; } //index = 3 - [ExcelColumnIndex(6)] - public string MyProperty3 { get; set; } //index = 6 - [ExcelColumnIndex("A")] // equal column index 0 - public string MyProperty4 { get; set; } - [ExcelColumnIndex(2)] - public string MyProperty5 { get; set; } //index = 2 - public string MyProperty6 { get; set; } //index = 4 - } - - private class Issue142VoDuplicateColumnName - { - [ExcelColumnIndex("A")] - public int MyProperty1 { get; set; } - [ExcelColumnIndex("A")] - public int MyProperty2 { get; set; } - - public int MyProperty3 { get; set; } - [ExcelColumnIndex("B")] - public int MyProperty4 { get; set; } - } - - private class Issue142VoOverIndex - { - [ExcelColumnIndex("Z")] - public int MyProperty1 { get; set; } - } - - private class Issue142VoExcelColumnNameNotFound - { - [ExcelColumnIndex("B")] - public int MyProperty1 { get; set; } - } - /// /// https://github.com/mini-software/MiniExcel/issues/150 /// @@ -3211,19 +2335,19 @@ public void Issue150() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { 1, 2 })); + Assert.Throws(() => _excelExporter.Export(path, new[] { 1, 2 })); File.Delete(path); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { "1", "2" })); + Assert.Throws(() => _excelExporter.Export(path, new[] { "1", "2" })); File.Delete(path); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { '1', '2' })); + Assert.Throws(() => _excelExporter.Export(path, new[] { '1', '2' })); File.Delete(path); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { DateTime.Now })); + Assert.Throws(() => _excelExporter.Export(path, new[] { DateTime.Now })); File.Delete(path); - Assert.Throws(() => MiniExcel.SaveAs(path, new[] { Guid.NewGuid() })); + Assert.Throws(() => _excelExporter.Export(path, new[] { Guid.NewGuid() })); File.Delete(path); } @@ -3238,7 +2362,7 @@ public void Issue157() var path = file.ToString(); _output.WriteLine("==== SaveAs by strongly type ===="); - var input = JsonConvert.DeserializeObject>( + var input = JsonConvert.DeserializeObject>( """ [ { @@ -3273,11 +2397,11 @@ public void Issue157() } ] """); - MiniExcel.SaveAs(path, input); + _excelExporter.Export(path, input); - var rows = MiniExcel.Query(path, sheetName: "Sheet1").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First()); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); using var p = new ExcelPackage(new FileInfo(path)); var ws = p.Workbook.Worksheets.First(); @@ -3288,9 +2412,9 @@ public void Issue157() const string path = "../../../../../samples/xlsx/TestIssue157.xlsx"; { - var rows = MiniExcel.Query(path, sheetName: "Sheet1").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); Assert.Equal(6, rows.Count); - Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First()); + Assert.Equal("Sheet1", _excelImporter.GetSheetNames(path).First()); } using (var p = new ExcelPackage(new FileInfo(path))) { @@ -3300,7 +2424,7 @@ public void Issue157() } { - var rows = MiniExcel.Query(path, sheetName: "Sheet1").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet1").ToList(); Assert.Equal(5, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); @@ -3333,7 +2457,7 @@ public void Issue149() { const string path = "../../../../../samples/xlsx/TestIssue149.xlsx"; - var rows = MiniExcel.Query(path).Select(s => (string)s.A).ToList(); + 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]}"); @@ -3347,9 +2471,9 @@ public void Issue149() { using var path = AutoDeletingPath.Create(); var input = chars.Select(s => new { Test = s.ToString() }); - MiniExcel.SaveAs(path.ToString(), input); + _excelExporter.Export(path.ToString(), input); - var rows = MiniExcel.Query(path.ToString(), true).Select(s => (string)s.Test).ToList(); + 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]}"); @@ -3363,9 +2487,9 @@ public void Issue149() { using var path = AutoDeletingPath.Create(); var input = chars.Select(s => new { Test = s.ToString() }); - MiniExcel.SaveAs(path.ToString(), input); + _excelExporter.Export(path.ToString(), input); - var rows = MiniExcel.Query(path.ToString()).Select(s => s.Test).ToList(); + 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]}"); @@ -3389,7 +2513,7 @@ private class Issue149VO public void Issue153() { const string path = "../../../../../samples/xlsx/TestIssue153.xlsx"; - var rows = MiniExcel.Query(path, true).First() as IDictionary; + var rows = _excelImporter.Query(path, true).First() as IDictionary; Assert.Equal( [ "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", @@ -3406,7 +2530,7 @@ public void Issue137() const string path = "../../../../../samples/xlsx/TestIssue137.xlsx"; { - var rows = MiniExcel.Query(path).ToList(); + 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); @@ -3440,7 +2564,7 @@ public void Issue137() // dynamic query with head { - var rows = MiniExcel.Query(path, true).ToList(); + 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); @@ -3460,7 +2584,7 @@ public void Issue137() } { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(10, rows.Count); { var row = rows[0]; @@ -3492,7 +2616,7 @@ public void Issue138() { const string path = "../../../../../samples/xlsx/TestIssue138.xlsx"; { - var rows = MiniExcel.Query(path, true).ToList(); + var rows = _excelImporter.Query(path, true).ToList(); Assert.Equal(6, rows.Count); foreach (var index in new[] { 0, 2, 5 }) @@ -3517,7 +2641,7 @@ public void Issue138() } { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(6, rows.Count); Assert.Equal(new DateTime(2021, 3, 1), rows[0].Date); @@ -3583,20 +2707,20 @@ public void IssueI50VD5() ["A"] = list1, ["B"] = list2, }; - MiniExcel.SaveAs(path, sheets); + _excelExporter.Export(path, sheets); { - Assert.Contains("/xl/media/", Helpers.GetZipFileContent(path, "xl/drawings/_rels/drawing1.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", Helpers.GetZipFileContent(path, "xl/drawings/drawing1.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", Helpers.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=\"drawing1\"", Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml")); - Assert.Contains("../drawings/drawing1.xml", Helpers.GetZipFileContent(path, "xl/worksheets/_rels/sheet1.xml.rels")); + 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/", Helpers.GetZipFileContent(path, "xl/drawings/_rels/drawing2.xml.rels")); - Assert.Contains("ext cx=\"609600\" cy=\"190500\"", Helpers.GetZipFileContent(path, "xl/drawings/drawing2.xml")); - Assert.Contains("/xl/drawings/drawing1.xml", Helpers.GetZipFileContent(path, "[Content_Types].xml")); - Assert.Contains("drawing r:id=\"drawing2\"", Helpers.GetZipFileContent(path, "xl/worksheets/sheet2.xml")); - Assert.Contains("../drawings/drawing2.xml", Helpers.GetZipFileContent(path, "xl/worksheets/_rels/sheet2.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")); } } @@ -3626,7 +2750,7 @@ public void Issue422() var enumerableWithCount = new Issue422Enumerable(items); using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.ToString(), enumerableWithCount); + _excelExporter.Export(path.ToString(), enumerableWithCount); Assert.Equal(1, enumerableWithCount.GetEnumeratorCount); } @@ -3652,7 +2776,7 @@ public void Issue459() } }; - ms.SaveAsByTemplate(template, values); + _excelTemplater.ApplyTemplate(ms, template, values); } [Fact] @@ -3668,9 +2792,9 @@ public void Issue527() var template = PathHelper.GetFile("xlsx/Issue527Template.xlsx"); using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAsByTemplate(path.FilePath, template, value); + _excelTemplater.ApplyTemplate(path.FilePath, template, value); - var rows = MiniExcel.Query(path.FilePath).ToList(); + var rows = _excelImporter.Query(path.FilePath).ToList(); Assert.Equal("General User", rows[1].B); Assert.Equal("General Administrator", rows[2].B); } @@ -3704,9 +2828,9 @@ WITH test('Id', 'Name') AS ( using var reader = cmd.ExecuteReader(); using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs(path.FilePath, reader, configuration: excelconfig, overwriteFile: true); + _excelExporter.Export(path.FilePath, reader, configuration: excelconfig, overwriteFile: true); - var rows = MiniExcel.Query(path.FilePath).ToList(); + var rows = _excelImporter.Query(path.FilePath).ToList(); Assert.All(rows, x => Assert.Single(x)); Assert.Equal("Name", rows[0].A); } @@ -3722,7 +2846,7 @@ private class Issue585VO2 { public string Col1 { get; set; } - [ExcelColumnName("Col2")] + [MiniExcelColumnName("Col2")] public string Prop2 { get; set; } public string Col3 { get; set; } @@ -3732,7 +2856,7 @@ private class Issue585VO3 { public string Col1 { get; set; } - [ExcelColumnIndex("B")] + [MiniExcelColumnIndex("B")] public string Prop2 { get; set; } public string Col3 { get; set; } @@ -3743,20 +2867,20 @@ public void Issue585() { const string path = "../../../../../samples/xlsx/TestIssue585.xlsx"; - var items1 = MiniExcel.Query(path); + var items1 = _excelImporter.Query(path); Assert.Equal(2, items1.Count()); - var items2 = MiniExcel.Query(path); + var items2 = _excelImporter.Query(path); Assert.Equal(2, items2.Count()); - var items3 = MiniExcel.Query(path); + var items3 = _excelImporter.Query(path); Assert.Equal(2, items3.Count()); } private class Issue542 { - [ExcelColumnIndex(0)] public Guid ID { get; set; } - [ExcelColumnIndex(1)] public string Name { get; set; } + [MiniExcelColumnIndex(0)] public Guid ID { get; set; } + [MiniExcelColumnIndex(1)] public string Name { get; set; } } [Fact] @@ -3764,8 +2888,8 @@ public void Issue_542() { const string path = "../../../../../samples/xlsx/TestIssue542.xlsx"; - var resultWithoutFirstRow = MiniExcel.Query(path).ToList(); - var resultWithFirstRow = MiniExcel.Query(path, hasHeader: false).ToList(); + var resultWithoutFirstRow = _excelImporter.Query(path).ToList(); + var resultWithFirstRow = _excelImporter.Query(path, treatHeaderAsData: true).ToList(); Assert.Equal(15, resultWithoutFirstRow.Count); Assert.Equal(16, resultWithFirstRow.Count); @@ -3774,122 +2898,6 @@ public void Issue_542() Assert.Equal("Wade", resultWithFirstRow[0].Name); } - private class Issue507V01 - { - public string A { get; set; } - public DateTime B { get; set; } - public string C { get; set; } - public int D { get; set; } - } - - private class Issue507V02 - { - public DateTime B { get; set; } - public int D { get; set; } - } - - [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(MiniExcelIssueTests), "_", nameof(Issue507_1), ".csv")); - var values = new Issue507V01[] - { - new() { A = "Github", B = DateTime.Parse("2021-01-01"), C = "abcd", D = 123 }, - new() { A = "Microsoft \nTest 1", B = DateTime.Parse("2021-02-01"), C = "efgh", D = 123 }, - new() { A = "Microsoft \rTest 2", B = DateTime.Parse("2021-02-01"), C = "ab\nc\nd", D = 123 }, - new() { A = "Microsoft\"\" \r\nTest\n3", B = DateTime.Parse("2021-02-01"), C = "a\"\"\nb\n\nc", D = 123 }, - }; - - var config = new CsvConfiguration - { - //AlwaysQuote = true, - ReadLineBreaksWithinQuotes = true, - }; - - // create - using (var stream = File.Create(path)) - { - stream.SaveAs(values, excelType: ExcelType.CSV, configuration: config); - } - - // read - var getRowsInfo = MiniExcel.Query(path, excelType: ExcelType.CSV, configuration: config).ToArray(); - - Assert.Equal(values.Length, getRowsInfo.Length); - - Assert.Equal("Github", getRowsInfo[0].A); - Assert.Equal("abcd", getRowsInfo[0].C); - - Assert.Equal($"Microsoft {config.NewLine}Test 1", getRowsInfo[1].A); - Assert.Equal("efgh", getRowsInfo[1].C); - - Assert.Equal($"Microsoft {config.NewLine}Test 2", getRowsInfo[2].A); - Assert.Equal($"ab{config.NewLine}c{config.NewLine}d", getRowsInfo[2].C); - - Assert.Equal($"""Microsoft"" {config.NewLine}Test{config.NewLine}3""", getRowsInfo[3].A); - Assert.Equal($"""a""{config.NewLine}b{config.NewLine}{config.NewLine}c""", getRowsInfo[3].C); - - File.Delete(path); - } - - [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(MiniExcelIssueTests), "_", nameof(Issue507_2), ".csv")); - var values = new Issue507V02[] - { - new() { B = DateTime.Parse("2021-01-01"), D = 123 }, - new() { B = DateTime.Parse("2021-02-01"), D = 123 }, - new() { B = DateTime.Parse("2021-02-01"), D = 123 }, - new() { B = DateTime.Parse("2021-02-01"), D = 123 }, - }; - - var config = new CsvConfiguration - { - //AlwaysQuote = true, - ReadLineBreaksWithinQuotes = true, - }; - - // create - using (var stream = File.Create(path)) - { - stream.SaveAs(values, excelType: ExcelType.CSV, configuration: config); - } - - // read - var getRowsInfo = MiniExcel.Query(path, excelType: ExcelType.CSV, configuration: config).ToArray(); - Assert.Equal(values.Length, getRowsInfo.Length); - - File.Delete(path); - } - - [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, - ReadLineBreaksWithinQuotes = true, - }; - - // create - using var stream = new MemoryStream(Encoding.UTF8.GetBytes("A,B,C\n\"r1a: no end quote,r1b,r1c")); - - // read - var getRowsInfo = stream.Query(excelType: ExcelType.CSV, configuration: config).ToArray(); - Assert.Equal(2, getRowsInfo.Length); - } - - [Fact] public void Issue606_1() { @@ -3933,7 +2941,7 @@ public void Issue606_1() ); const string templateFileName = "../../../../../samples/xlsx/TestIssue606_Template.xlsx"; - MiniExcel.SaveAsByTemplate(path, Path.GetFullPath(templateFileName), value); + _excelTemplater.ApplyTemplate(path, Path.GetFullPath(templateFileName), value); File.Delete(path); } @@ -3954,7 +2962,7 @@ public void TestIssue627() using var path = AutoDeletingPath.Create(); var value = new[] { new { long2 = "1550432695793487872" } }; - var rowsWritten = MiniExcel.SaveAs(path.ToString(), value, configuration: config); + var rowsWritten = _excelExporter.Export(path.ToString(), value, configuration: config); Assert.Single(rowsWritten); Assert.Equal(1, rowsWritten[0]); @@ -3990,7 +2998,7 @@ public void Issue632_1() string.Concat(nameof(MiniExcelIssueTests), "_", nameof(Issue632_1), ".xlsx") ); - MiniExcel.SaveAs(path, values, excelType: ExcelType.XLSX, configuration: config, overwriteFile: true); + _excelExporter.Export(path, values, configuration: config, overwriteFile: true); File.Delete(path); } @@ -4015,7 +3023,7 @@ static IEnumerable GetTestData() using var memoryStream = new MemoryStream(); var testData = GetTestData(); - var rowsWritten = memoryStream.SaveAs(testData, configuration: new OpenXmlConfiguration + var rowsWritten = _excelExporter.Export(memoryStream, testData, configuration: new OpenXmlConfiguration { FastMode = true }); @@ -4024,7 +3032,7 @@ static IEnumerable GetTestData() memoryStream.Position = 0; - var queryData = memoryStream.Query().ToList(); + var queryData = _excelImporter.Query(memoryStream).ToList(); Assert.Equal(testData.Count(), queryData.Count); @@ -4053,14 +3061,14 @@ static IEnumerable GetTestData() using var memoryStream = new MemoryStream(); var testData = GetTestData(); - await memoryStream.SaveAsAsync(testData, configuration: new OpenXmlConfiguration + await _excelExporter.ExportAsync(memoryStream, testData, configuration: new OpenXmlConfiguration { FastMode = true, }); memoryStream.Position = 0; - var queryData = (memoryStream.QueryAsync().ToBlockingEnumerable()).ToList(); + var queryData = _excelImporter.QueryAsync(memoryStream).ToBlockingEnumerable().ToList(); Assert.Equal(testData.Count(), queryData.Count); @@ -4078,10 +3086,10 @@ public void Issue_686() { var path = PathHelper.GetFile("xlsx/TestIssue686.xlsx"); Assert.Throws(() => - MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF10", endCell: "ZZFF11").First()); + _excelImporter.QueryRange(path, useHeaderRow: false, startCell: "ZZFF10", endCell: "ZZFF11").First()); Assert.Throws(() => - MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF@@10", endCell: "ZZFF@@11").First()); + _excelImporter.QueryRange(path, useHeaderRow: false, startCell: "ZZFF@@10", endCell: "ZZFF@@11").First()); } [Fact] @@ -4091,9 +3099,9 @@ public void Test_Issue_693_SaveSheetWithLongName() using var path2 = AutoDeletingPath.Create(); List> data = [new() { ["First"] = 1, ["Second"] = 2 }]; - Assert.Throws(() => MiniExcel.SaveAs(path1.ToString(), data, sheetName: "Some Really Looooooooooong Sheet Name")); - MiniExcel.SaveAs(path2.ToString(), new List>()); - Assert.Throws(() => MiniExcel.Insert(path2.ToString(), data, sheetName: "Some Other Very Looooooong Sheet Name")); + Assert.Throws(() => _excelExporter.Export(path1.ToString(), data, sheetName: "Some Really Looooooooooong Sheet Name")); + _excelExporter.Export(path2.ToString(), new List>()); + Assert.Throws(() => _excelExporter.InsertSheet(path2.ToString(), data, sheetName: "Some Other Very Looooooong Sheet Name")); } private class Issue697 @@ -4107,8 +3115,8 @@ private class Issue697 public void Test_Issue_697_EmptyRowsStronglyTypedQuery() { const string path = "../../../../../samples/xlsx/TestIssue697.xlsx"; - var rowsIgnoreEmpty = MiniExcel.Query(path, configuration: new OpenXmlConfiguration { IgnoreEmptyRows = true }).ToList(); - var rowsCountEmpty = MiniExcel.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); } @@ -4118,13 +3126,13 @@ public void Issue_710() { var values = new[] { new { Column1 = "MiniExcel", Column2 = 1, Column3 = "Test" } }; using var memoryStream = new MemoryStream(); - memoryStream.SaveAs(values, configuration: new OpenXmlConfiguration + _excelExporter.Export(memoryStream, values, configuration: new OpenXmlConfiguration { FastMode = true }); memoryStream.Position = 0; - using var dataReader = memoryStream.GetReader(useHeaderRow: false); + using var dataReader = _excelImporter.GetDataReader(memoryStream, useHeaderRow: false); dataReader.Read(); for (int i = 0; i < dataReader.FieldCount; i++) @@ -4143,9 +3151,9 @@ public void Issue_732_First_Sheet_Active() const string path2 = "../../../../../samples/xlsx/TestIssue732_2.xlsx"; const string path3 = "../../../../../samples/xlsx/TestIssue732_3.xlsx"; - var info1 = MiniExcel.GetSheetInformations(path1); - var info2 = MiniExcel.GetSheetInformations(path2); - var info3 = MiniExcel.GetSheetInformations(path3); + var info1 = _excelImporter.GetSheetInformations(path1); + var info2 = _excelImporter.GetSheetInformations(path2); + var info3 = _excelImporter.GetSheetInformations(path3); Assert.Equal(0u, info1.SingleOrDefault(x => x.Active)?.Index); // first sheet is active Assert.Equal(1u, info2.SingleOrDefault(x => x.Active)?.Index); // second sheet is active @@ -4164,9 +3172,9 @@ public void TestIssue750() ["list"] = Enumerable.Range(0, 10_000) .Select(_ => new { value1 = Guid.NewGuid(), value2 = Guid.NewGuid(), }) }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, data); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, data); - var rows = MiniExcel.Query(path.ToString()) + var rows = _excelImporter.Query(path.ToString()) .Skip(1453) .Take(2) .ToList(); @@ -4196,7 +3204,7 @@ public void TestIssue751() { ["list"] = list }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, data); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, data); using var stream = File.OpenRead(path.ToString()); using var workbook = new XSSFWorkbook(stream); @@ -4225,7 +3233,7 @@ public void TestIssue751() public void TestIssue763() { var path = PathHelper.GetFile("xlsx/TestIssue763.xlsx"); - var rows = MiniExcel.QueryRange(path, startCell: "A3", endCell: "J3").ToArray(); + var rows = _excelImporter.QueryRange(path, startCell: "A3", endCell: "J3").ToArray(); Assert.Equal("A3", rows[0].A); Assert.Equal(null, rows[0].J); } @@ -4254,8 +3262,8 @@ public void TestIssue768() ["list"] = list }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, data); - var rows = MiniExcel.Query(path.ToString(), startCell: "A16").ToList(); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, data); + var rows = _excelImporter.Query(path.ToString(), startCell: "A16").ToList(); Assert.Equal(list[0].value1.ToString(), rows[0].A.ToString()); Assert.Equal(list[1].value1.ToString(), rows[1].A.ToString()); @@ -4290,7 +3298,7 @@ public void TestIssue186() } ]; - MiniExcel.AddPicture(path.FilePath, images); + _excelExporter.AddPicture(path.FilePath, images); } /// @@ -4318,8 +3326,8 @@ public void TestIssue771() list12 = GetEnumerable() }; - MiniExcel.SaveAsByTemplate(path.FilePath, template, value); - var rows = MiniExcel.Query(path.FilePath).ToList(); + _excelTemplater.ApplyTemplate(path.FilePath, template, value); + var rows = _excelImporter.Query(path.FilePath).ToList(); Assert.Equal("2025-1", rows[2].B); Assert.Equal(null, rows[3].B); @@ -4337,7 +3345,7 @@ public void TestIssue771() public void TestIssue772() { var path = PathHelper.GetFile("xlsx/TestIssue772.xlsx"); - var rows = MiniExcel.Query(path, sheetName: "Supply plan(daily)", startCell: "A1") + var rows = _excelImporter.Query(path, sheetName: "Supply plan(daily)", startCell: "A1") .Cast>() .ToArray(); @@ -4361,8 +3369,8 @@ public void TestIssue773() var fill = new { t = a }; using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAsByTemplate(path.FilePath, templatePath, fill); - var rows = MiniExcel.Query(path.FilePath).ToList(); + _excelTemplater.ApplyTemplate(path.FilePath, templatePath, fill); + var rows = _excelImporter.Query(path.FilePath).ToList(); Assert.Equal("H1", rows[4].AF); Assert.Equal("c3", rows[6].AA); @@ -4381,9 +3389,9 @@ public void TestIssue789() new Dictionary { {"no","2"} }, new Dictionary { {"no","3"} }, }; - MiniExcel.SaveAs(path, value); + _excelExporter.Export(path, value); - var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); + var xml = SheetHelper.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); Assert.Contains("", xml); } @@ -4426,11 +3434,12 @@ public void TestIssue814() } ]; - MiniExcel.AddPicture(path.FilePath, images); + _excelExporter.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]; + + // 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); @@ -4478,10 +3487,9 @@ public void TestIssue815() } ]; - MiniExcel.AddPicture(path.FilePath, images); - //ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + _excelExporter.AddPicture(path.FilePath, images); - using (var package = new ExcelPackage(new FileInfo(path.FilePath))) + using (var package = new ExcelPackage(new FileInfo(path.FilePath))) { // Check picture in the first sheet (C3) var firstSheet = package.Workbook.Worksheets[0]; @@ -4538,10 +3546,9 @@ public void TestIssue816() } ]; - MiniExcel.AddPicture(path.FilePath, images); - //ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + _excelExporter.AddPicture(path.FilePath, images); - using var package = new ExcelPackage(new FileInfo(path.FilePath)); + using var package = new ExcelPackage(new FileInfo(path.FilePath)); // Check picture in the first sheet (C3) var firstSheet = package.Workbook.Worksheets[0]; @@ -4591,7 +3598,7 @@ public void TestIssue816() } ]; - MiniExcel.AddPicture(path.FilePath, images); + _excelExporter.AddPicture(path.FilePath, images); using var package = new ExcelPackage(new FileInfo(path.FilePath)); // Check picture in the first sheet (C3) @@ -4640,7 +3647,7 @@ public void TestIssue816() public void TestIssue809() { var path = PathHelper.GetFile("xlsx/TestIssue809.xlsx"); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(3, rows.Count); Assert.Equal(null, rows[0].A); diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs similarity index 75% rename from tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs index abf31b64..d0aef62f 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs @@ -1,21 +1,14 @@ using ClosedXML.Excel; -using Dapper; using ExcelDataReader; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using OfficeOpenXml; -using System.Data; -using System.Data.SQLite; -using System.Globalization; -using System.IO.Packaging; -using System.Text; -using Xunit; - -namespace MiniExcelLibs.Tests; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.Tests; public class MiniExcelOpenXmlAsyncTests { + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + [Fact] public async Task SaveAsControlChracter() { @@ -33,10 +26,10 @@ public async Task SaveAsControlChracter() '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' ]; var input = chars.Select(s => new { Test = s.ToString() }); - await MiniExcel.SaveAsAsync(path, input); + await _excelExporter.ExportAsync(path, input); - var rows2 = MiniExcel.QueryAsync(path, true).ToBlockingEnumerable().ToArray(); - var rows1 = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToArray(); + var rows2 = _excelImporter.QueryAsync(path, true).ToBlockingEnumerable().ToArray(); + var rows1 = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToArray(); } private class SaveAsControlChracterVO @@ -46,33 +39,33 @@ private class SaveAsControlChracterVO private class ExcelAttributeDemo { - [ExcelColumnName("Column1")] + [MiniExcelColumnName("Column1")] public string Test1 { get; set; } - [ExcelColumnName("Column2")] + [MiniExcelColumnName("Column2")] public string Test2 { get; set; } - [ExcelIgnore] + [MiniExcelIgnore] public string Test3 { get; set; } - [ExcelColumnIndex("I")] // system will convert "I" to 8 index + [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 - [ExcelColumnIndex(3)] // start with 0 + [MiniExcelColumnIndex(3)] // start with 0 public string Test7 { get; set; } } private class ExcelAttributeDemo2 { - [ExcelColumn(Name = "Column1")] + [MiniExcelColumn(Name = "Column1")] public string Test1 { get; set; } - [ExcelColumn(Name = "Column2")] + [MiniExcelColumn(Name = "Column2")] public string Test2 { get; set; } - [ExcelColumn(Ignore = true)] + [MiniExcelColumn(Ignore = true)] public string Test3 { get; set; } - [ExcelColumn(IndexName = "I")] // system will convert "I" to 8 index + [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 - [ExcelColumn(Index = 3)] // start with 0 + [MiniExcelColumn(Index = 3)] // start with 0 public string Test7 { get; set; } } @@ -82,7 +75,7 @@ public async Task CustomAttributeWihoutVaildPropertiesTest() const string path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; await Assert.ThrowsAsync(async () => { - _ = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList(); + _ = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); }); } @@ -90,7 +83,7 @@ await Assert.ThrowsAsync(async () => public async Task QueryCustomAttributesTest() { const string path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; - var rows = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); Assert.Equal("Column1", rows[0].Test1); Assert.Equal("Column2", rows[0].Test2); @@ -105,7 +98,7 @@ public async Task QueryCustomAttributesTest() public async Task QueryCustomAttributes2Test() { const string path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; - var rows = MiniExcel.QueryAsync(path).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path).ToBlockingEnumerable().ToList(); Assert.Equal("Column1", rows[0].Test1); Assert.Equal("Column2", rows[0].Test2); @@ -129,8 +122,8 @@ public async Task SaveAsCustomAttributesTest() Test4 = "Test4", }); - await MiniExcel.SaveAsAsync(path.ToString(), input); - var d = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + await _excelExporter.ExportAsync(path.ToString(), input); + var d = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var rows = d.ToList(); var first = rows[0] as IDictionary; @@ -156,8 +149,8 @@ public async Task SaveAsCustomAttributes2Test() Test4 = "Test4", }); - await MiniExcel.SaveAsAsync(path.ToString(), input); - var d = MiniExcel.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + await _excelExporter.ExportAsync(path.ToString(), input); + var d = _excelImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); var rows = d.ToList(); var first = rows[0] as IDictionary; @@ -172,7 +165,7 @@ public async Task SaveAsCustomAttributes2Test() private class CustomAttributesWihoutVaildPropertiesTestPoco { - [ExcelIgnore] + [MiniExcelIgnore] public string Test3 { get; set; } public string Test5 { get; } public string Test6 { get; private set; } @@ -182,7 +175,7 @@ private class CustomAttributesWihoutVaildPropertiesTestPoco public async Task QueryCastToIDictionary() { const string path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; - foreach (IDictionary row in MiniExcel.QueryAsync(path).ToBlockingEnumerable()) + foreach (IDictionary row in _excelImporter.QueryAsync(path).ToBlockingEnumerable()) { _ = row; } @@ -194,7 +187,7 @@ public async Task CenterEmptyRowsQueryTest() const string path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal("a", rows[0]["A"]); Assert.Equal("b", rows[0]["B"]); @@ -230,7 +223,7 @@ public async Task CenterEmptyRowsQueryTest() await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(1d, rows[0]["a"]); Assert.Null(rows[0]["b"]); @@ -264,7 +257,7 @@ public async Task TestDynamicQueryBasic_WithoutHead() { const string path = "../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; await using var stream = File.OpenRead(path); - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal("MiniExcel", rows[0]["A"]); @@ -279,7 +272,7 @@ public async Task TestDynamicQueryBasic_useHeaderRow() const string path = "../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); @@ -288,7 +281,7 @@ public async Task TestDynamicQueryBasic_useHeaderRow() } { - var d = MiniExcel.QueryAsync(path, useHeaderRow: true).ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(path, useHeaderRow: true).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1d, rows[0].Column2); @@ -319,7 +312,7 @@ public async Task QueryStrongTypeMapping_Test() const string path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; await using (var stream = File.OpenRead(path)) { - var d = stream.QueryAsync(); + var d = _excelImporter.QueryAsync(stream); var rows = d.ToBlockingEnumerable().ToList(); Assert.Equal(100, rows.Count); @@ -333,16 +326,16 @@ public async Task QueryStrongTypeMapping_Test() } { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: true).ToList(); Assert.Equal(100, rows.Count); - Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); + Assert.Equal("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("27/09/2020", 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); + Assert.Equal(bool.FalseString, rows[0].VIP); + Assert.Equal(5019.12d, rows[0].Points); + Assert.Null(rows[0].IgnoredProperty); } } @@ -360,7 +353,7 @@ public async Task AutoCheckTypeTest() { const string path = "../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; await using var stream = FileHelper.OpenRead(path); - _ = stream.QueryAsync().ToBlockingEnumerable().ToList(); + _ = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().ToList(); } [Fact] @@ -369,7 +362,7 @@ public async Task TestDatetimeSpanFormat_ClosedXml() const string path = "../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; await using var stream = FileHelper.OpenRead(path); - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var row = d.First(); var a = row["A"]; var b = row["B"]; @@ -384,13 +377,13 @@ public async Task LargeFileQueryStrongTypeMapping_Test() const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; await using (var stream = File.OpenRead(path)) { - var d = stream.QueryAsync().ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); var rows = d.Take(2).ToList(); Assert.Equal("HelloWorld2", rows[0].HelloWorld1); Assert.Equal("HelloWorld3", rows[1].HelloWorld1); } { - var d = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(path).ToBlockingEnumerable(); var rows = d.Take(2).ToList(); Assert.Equal("HelloWorld2", rows[0].HelloWorld1); Assert.Equal("HelloWorld3", rows[1].HelloWorld1); @@ -407,11 +400,11 @@ public async Task QueryExcelDataReaderCheckTest(string path) #endif await using var fs = File.OpenRead(path); - using var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(fs); + using var reader = ExcelReaderFactory.CreateReader(fs); var exceldatareaderResult = reader.AsDataSet(); await using var stream = File.OpenRead(path); - var d = stream.QueryAsync().ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); @@ -420,7 +413,7 @@ public async Task QueryExcelDataReaderCheckTest(string path) var rowIndex = rows.IndexOf(row); foreach (var (key, value) in row) { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][Helpers.GetColumnIndex(key)]; + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; var v = value ?? DBNull.Value; Assert.Equal(eV, v); } @@ -433,7 +426,7 @@ public async Task QuerySheetWithoutRAttribute() const string path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; await using var stream = File.OpenRead(path); - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); var keys = rows.First().Keys; @@ -456,7 +449,7 @@ public async Task FixDimensionJustOneColumnParsingError_Test() { const string path = "../../../../../samples/xlsx/TestDimensionC3.xlsx"; await using var stream = File.OpenRead(path); - var d = stream.QueryAsync().ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); var rows = d.ToList(); var keys = (rows.First() as IDictionary)?.Keys; Assert.Equal(3, keys?.Count); @@ -482,10 +475,10 @@ public async Task SaveAsFileWithDimensionByICollection() using (var file = AutoDeletingPath.Create()) { var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0]["A"]); @@ -495,19 +488,19 @@ public async Task SaveAsFileWithDimensionByICollection() await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0]["A"]); Assert.Equal("A", rows[1]["A"]); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } using var newPath = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(newPath.ToString(), values, false); - Assert.Equal("A1:B2", Helpers.GetFirstSheetDimensionRefValue(newPath.ToString())); + await _excelExporter.ExportAsync(newPath.ToString(), values, false); + Assert.Equal("A1:B2", SheetHelper.GetFirstSheetDimensionRefValue(newPath.ToString())); } //List empty @@ -516,28 +509,28 @@ public async Task SaveAsFileWithDimensionByICollection() using (var file = AutoDeletingPath.Create()) { var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, values, false); + await _excelExporter.ExportAsync(path, values, false); await using (var stream = File.OpenRead(path)) { - var d = stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Empty(rows); } - Assert.Equal("A1:B1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } using (var file = AutoDeletingPath.Create()) { var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); { await using var stream = File.OpenRead(path); - var d = stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Single(rows); } - Assert.Equal("A1:B1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } } @@ -552,11 +545,11 @@ public async Task SaveAsFileWithDimensionByICollection() using (var file = AutoDeletingPath.Create()) { var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0]["A"]); @@ -566,20 +559,20 @@ public async Task SaveAsFileWithDimensionByICollection() await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0]["A"]); Assert.Equal("A", rows[1]["A"]); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } using (var path = AutoDeletingPath.Create()) { - await MiniExcel.SaveAsAsync(path.ToString(), values, false); - Assert.Equal("A1:B2", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + await _excelExporter.ExportAsync(path.ToString(), values, false); + Assert.Equal("A1:B2", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } } @@ -587,7 +580,7 @@ public async Task SaveAsFileWithDimensionByICollection() { using var path = AutoDeletingPath.Create(); var values = new List(); - await Assert.ThrowsAsync(() => MiniExcel.SaveAsAsync(path.ToString(), values)); + await Assert.ThrowsAsync(() => _excelExporter.ExportAsync(path.ToString(), values)); } } @@ -599,16 +592,16 @@ public async Task SaveAsFileWithDimension() var path = file.ToString(); using var table = new DataTable(); - await MiniExcel.SaveAsAsync(path, table); - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + await _excelExporter.ExportAsync(path, table); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); { await using var stream = File.OpenRead(path); - var d = stream.QueryAsync().ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Single(rows); } - await MiniExcel.SaveAsAsync(path, table, printHeader: false, overwriteFile: true); - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + await _excelExporter.ExportAsync(path, table, printHeader: false, overwriteFile: true); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } { using var file = AutoDeletingPath.Create(); @@ -622,12 +615,12 @@ public async Task SaveAsFileWithDimension() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890); table.Rows.Add("Hello World", -1234567890, false, DateTime.Now); - await MiniExcel.SaveAsAsync(path, table); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(path)); + await _excelExporter.ExportAsync(path, table); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(path)); await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(2, rows.Count); Assert.Equal(@"""<>+-*//}{\\n", rows[0]["a"]); @@ -638,7 +631,7 @@ public async Task SaveAsFileWithDimension() await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(3, rows.Count); Assert.Equal("a", rows[0]["A"]); @@ -647,8 +640,8 @@ public async Task SaveAsFileWithDimension() Assert.Equal("d", rows[0]["D"]); } - await MiniExcel.SaveAsAsync(path, table, printHeader: false, overwriteFile: true); - Assert.Equal("A1:D2", Helpers.GetFirstSheetDimensionRefValue(path)); + await _excelExporter.ExportAsync(path, table, printHeader: false, overwriteFile: true); + Assert.Equal("A1:D2", SheetHelper.GetFirstSheetDimensionRefValue(path)); } { @@ -658,8 +651,8 @@ public async Task SaveAsFileWithDimension() table.Rows.Add("A"); table.Rows.Add("B"); - await MiniExcel.SaveAsAsync(path.ToString(), table); - Assert.Equal("A1:A3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + await _excelExporter.ExportAsync(path.ToString(), table); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } } @@ -680,7 +673,7 @@ public async Task SaveAsByDataTableTest() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); table.Rows.Add("Hello World", -1234567890, false, now.Date); - await MiniExcel.SaveAsAsync(path, table); + await _excelExporter.ExportAsync(path, table); using var p = new ExcelPackage(new FileInfo(path)); var ws = p.Workbook.Worksheets.First(); @@ -703,7 +696,7 @@ public async Task SaveAsByDataTableTest() table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); - await MiniExcel.SaveAsAsync(path.ToString(), table); + await _excelExporter.ExportAsync(path.ToString(), table); } } @@ -713,19 +706,19 @@ public async Task QueryByLINQExtensionsVoidTaskLargeFileOOMTest() const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; { - var row = (MiniExcel.QueryAsync(path).ToBlockingEnumerable()).First(); + var row = _excelImporter.QueryAsync(path).ToBlockingEnumerable().First(); Assert.Equal("HelloWorld1", row.A); } await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); var row = d.First(); Assert.Equal("HelloWorld1", row["A"]); } { - var d = (MiniExcel.QueryAsync(path).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(path).ToBlockingEnumerable().Cast>(); var rows = d.Take(10); Assert.Equal(10, rows.Count()); } @@ -739,12 +732,12 @@ public async Task EmptyTest() await using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = await connection.QueryAsync("with cte as (select 1 id,2 val) select * from cte where 1=2"); - await MiniExcel.SaveAsAsync(path.ToString(), rows); + await _excelExporter.ExportAsync(path.ToString(), rows); } await using (var stream = File.OpenRead(path.ToString())) { - var row = stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable(); + var row = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable(); Assert.Empty(row); } } @@ -761,11 +754,11 @@ public async Task SaveAsByIEnumerableIDictionary() new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, new() { { "Column1", "Github" }, { "Column2", 2 } } ]; - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal("Column1", rows[0]["A"]); Assert.Equal("Column2", rows[0]["B"]); @@ -777,7 +770,7 @@ public async Task SaveAsByIEnumerableIDictionary() await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(2, rows.Count); Assert.Equal("MiniExcel", rows[0]["Column1"]); @@ -786,7 +779,7 @@ public async Task SaveAsByIEnumerableIDictionary() Assert.Equal(2d, rows[1]["Column2"]); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } { @@ -798,15 +791,15 @@ public async Task SaveAsByIEnumerableIDictionary() new() { { 1, "MiniExcel" }, { 2, 1 } }, new() { { 1, "Github" }, { 2, 2 } } ]; - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using (var stream = File.OpenRead(path)) { - var d = stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable(); var rows = d.ToList(); Assert.Equal(3, rows.Count); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } } @@ -830,11 +823,11 @@ public async Task SaveAsByIEnumerableIDictionaryWithDynamicConfiguration() new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, new() { { "Column1", "Github" }, { "Column2", 2 } } ]; - await MiniExcel.SaveAsAsync(path, values, configuration: config); + await _excelExporter.ExportAsync(path, values, configuration: config); await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal(2, rows.Count); Assert.Equal("Name Column", rows[0].Keys.ElementAt(0)); @@ -844,7 +837,7 @@ public async Task SaveAsByIEnumerableIDictionaryWithDynamicConfiguration() Assert.Equal("Github", rows[1].Values.ElementAt(0)); Assert.Equal(2d, rows[1].Values.ElementAt(1)); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } @@ -861,7 +854,7 @@ public async Task SaveAsFrozenRowsAndColumnsTest() using var file = AutoDeletingPath.Create(); var path = file.ToString(); - await MiniExcel.SaveAsAsync( + await _excelExporter.ExportAsync( path, new[] { @@ -873,14 +866,14 @@ await MiniExcel.SaveAsAsync( await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); Assert.Equal("Github", rows[1].Column1); Assert.Equal(2, rows[1].Column2); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); // test table var table = new DataTable(); @@ -892,14 +885,14 @@ await MiniExcel.SaveAsAsync( table.Rows.Add("Hello World", -1234567890, false, DateTime.Now.Date); using var pathTable = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(pathTable.ToString(), table, configuration: config); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable.ToString())); + await _excelExporter.ExportAsync(pathTable.ToString(), table, configuration: config); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(pathTable.ToString())); // data reader await using var reader = table.CreateDataReader(); using var pathReader = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(pathReader.ToString(), reader, configuration: config); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable.ToString())); + await _excelExporter.ExportAsync(pathReader.ToString(), reader, configuration: config); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(pathTable.ToString())); } [Fact] @@ -912,13 +905,13 @@ public async Task SaveAsByDapperRows() await using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = await connection.QueryAsync("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); - await MiniExcel.SaveAsAsync(path, rows); + await _excelExporter.ExportAsync(path, rows); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); Assert.Equal("Github", rows[1]["Column1"]); @@ -929,33 +922,33 @@ public async Task SaveAsByDapperRows() await using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = (await connection.QueryAsync("with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2")).ToList(); - await MiniExcel.SaveAsAsync(path, rows, overwriteFile: true); + await _excelExporter.ExportAsync(path, rows, overwriteFile: true); } await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable()).ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable().ToList(); Assert.Empty(rows); } await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().ToList(); Assert.Empty(rows); } - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); // ToList await using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = (await connection.QueryAsync("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2")).ToList(); - await MiniExcel.SaveAsAsync(path, rows, overwriteFile: true); + await _excelExporter.ExportAsync(path, rows, overwriteFile: true); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: false).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: false).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("Column1", rows[0]["A"]); Assert.Equal("Column2", rows[0]["B"]); Assert.Equal("MiniExcel", rows[1]["A"]); @@ -966,7 +959,7 @@ public async Task SaveAsByDapperRows() await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); Assert.Equal("Github", rows[1]["Column1"]); @@ -990,10 +983,10 @@ public async Task QueryByStrongTypeParameterTest() new() { Column1 = "MiniExcel", Column2 = 1 }, new() { Column1 = "Github", Column2 = 2 } ]; - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using var stream = File.OpenRead(path); - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); @@ -1011,10 +1004,10 @@ public async Task QueryByDictionaryStringAndObjectParameterTest() new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, new() { { "Column1", "Github" }, { "Column2", 2 } } ]; - await MiniExcel.SaveAsAsync(path, values); + await _excelExporter.ExportAsync(path, values); await using var stream = File.OpenRead(path); - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); @@ -1041,7 +1034,7 @@ public async Task SQLiteInsertTest() await using (var transaction = connection.BeginTransaction()) await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync().ToBlockingEnumerable()).Cast>(); + var rows = _excelImporter.QueryAsync(stream).ToBlockingEnumerable().Cast>(); foreach (var row in rows) await connection.ExecuteAsync( "insert into T (A,B) values (@A,@B)", @@ -1066,7 +1059,7 @@ public async Task SaveAsBasicCreateTest() using var file = AutoDeletingPath.Create(); var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new[] + await _excelExporter.ExportAsync(path, new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} @@ -1074,14 +1067,14 @@ await MiniExcel.SaveAsAsync(path, new[] await using (var stream = File.OpenRead(path)) { - var d = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>(); + var d = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>(); var rows = d.ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); Assert.Equal("Github", rows[1]["Column1"]); Assert.Equal(2d, rows[1]["Column2"]); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } [Fact] @@ -1098,12 +1091,12 @@ public async Task SaveAsBasicStreamTest() }; await using (var stream = new FileStream(path, FileMode.CreateNew)) { - await stream.SaveAsAsync(values); + await _excelExporter.ExportAsync(stream, values); } await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); Assert.Equal("Github", rows[1]["Column1"]); @@ -1121,14 +1114,14 @@ public async Task SaveAsBasicStreamTest() await using (var stream = new MemoryStream()) await using (var fileStream = new FileStream(path, FileMode.Create)) { - await stream.SaveAsAsync(values); + await _excelExporter.ExportAsync(stream, values); stream.Seek(0, SeekOrigin.Begin); await stream.CopyToAsync(fileStream); } await using (var stream = File.OpenRead(path)) { - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()).Cast>().ToList(); + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().Cast>().ToList(); Assert.Equal("MiniExcel", rows[0]["Column1"]); Assert.Equal(1d, rows[0]["Column2"]); Assert.Equal("Github", rows[1]["Column1"]); @@ -1141,7 +1134,7 @@ public async Task SaveAsBasicStreamTest() public async Task SaveAsSpecialAndTypeCreateTest() { using var path = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(path.ToString(), new[] + await _excelExporter.ExportAsync(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = DateTime.Now }, new { a = "Hello World", b = -1234567890, c = false, d = DateTime.Now.Date} @@ -1156,7 +1149,7 @@ public async Task SaveAsFileEpplusCanReadTest() using var path = AutoDeletingPath.Create(); var now = DateTime.Now; - await MiniExcel.SaveAsAsync(path.ToString(), new[] + await _excelExporter.ExportAsync(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now}, new { a = "Hello World", b = -1234567890, c = false, d = now.Date} @@ -1182,7 +1175,7 @@ public async Task SavaAsClosedXmlCanReadTest() var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(path.ToString(), new[] + await _excelExporter.ExportAsync(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now}, new { a = "Hello World", b = -1234567890, c = false, d = now.Date} @@ -1207,7 +1200,7 @@ public async Task ContentTypeUriContentTypeReadCheckTest() var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(path.ToString(), new[] + await _excelExporter.ExportAsync(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now}, new { a = "Hello World", b = -1234567890, c = false, d = now.Date} @@ -1235,7 +1228,7 @@ await Assert.ThrowsAsync(async () => await cts.CancelAsync(); await using var stream = FileHelper.OpenRead(path); - var rows = stream.QueryAsync(cancellationToken: cts.Token).ToBlockingEnumerable(cts.Token).ToList(); + var rows = _excelImporter.QueryAsync(stream, cancellationToken: cts.Token).ToBlockingEnumerable(cts.Token).ToList(); }); } @@ -1255,7 +1248,7 @@ await Assert.ThrowsAsync(async () => }); await using var stream = FileHelper.OpenRead(path); - var d = stream.QueryAsync(cancellationToken: cts.Token).ToBlockingEnumerable(cts.Token); + var d = _excelImporter.QueryAsync(stream, cancellationToken: cts.Token).ToBlockingEnumerable(cts.Token); await cancelTask; _ = d.ToList(); }); @@ -1303,10 +1296,10 @@ public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataRe ] }; await using var reader = table.CreateDataReader(); - await MiniExcel.SaveAsAsync(path.ToString(), reader, configuration: configuration); + await _excelExporter.ExportAsync(path.ToString(), reader, configuration: configuration); await using var stream = File.OpenRead(path.ToString()); - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()) + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable() .Cast>() .ToList(); @@ -1369,10 +1362,10 @@ public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTab } ] }; - await MiniExcel.SaveAsAsync(path.ToString(), table, configuration: configuration); + await _excelExporter.ExportAsync(path.ToString(), table, configuration: configuration); await using var stream = File.OpenRead(path.ToString()); - var rows = (stream.QueryAsync(useHeaderRow: true).ToBlockingEnumerable()) + var rows = _excelImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable() .Cast>() .ToList(); @@ -1405,20 +1398,19 @@ public async Task SaveAsByMiniExcelDataReader() new() { Column1= "MiniExcel" ,Column2 = 1 }, new() { Column1 = "Github", Column2 = 2 } }; - await MiniExcel.SaveAsAsync(path1.ToString(), values); + await _excelExporter.ExportAsync(path1.ToString(), values); - await using (IMiniExcelDataReader? reader = MiniExcel.GetReader(path1.ToString(), true)) - { - using var path2 = AutoDeletingPath.Create(); - await MiniExcel.SaveAsAsync(path2.ToString(), reader); - var results = MiniExcel.QueryAsync(path2.ToString()).ToBlockingEnumerable().ToList(); - - Assert.True(results.Count == 2); - Assert.True(results.First().Column1 == "MiniExcel"); - Assert.True(results.First().Column2 == 1); - Assert.True(results.Last().Column1 == "Github"); - Assert.True(results.Last().Column2 == 2); - } + using var path2 = AutoDeletingPath.Create(); + await using IMiniExcelDataReader? reader = _excelImporter.GetDataReader(path1.ToString(), true); + + await _excelExporter.ExportAsync(path2.ToString(), reader); + var results = _excelImporter.QueryAsync(path2.ToString()).ToBlockingEnumerable().ToList(); + + Assert.True(results.Count == 2); + Assert.True(results.First().Column1 == "MiniExcel"); + Assert.True(results.First().Column2 == 1); + Assert.True(results.Last().Column1 == "Github"); + Assert.True(results.Last().Column2 == 2); } [Fact] @@ -1437,7 +1429,7 @@ public async Task InsertSheetTest() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); table.Rows.Add("Hello World", -1234567890, false, now.Date); - await MiniExcel.InsertAsync(path, table, sheetName: "Sheet1"); + await _excelExporter.InsertSheetAsync(path, table, sheetName: "Sheet1"); using var p = new ExcelPackage(new FileInfo(path)); var sheet1 = p.Workbook.Worksheets[0]; @@ -1460,7 +1452,7 @@ public async Task InsertSheetTest() table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); - await MiniExcel.InsertAsync(path, table, sheetName: "Sheet2"); + await _excelExporter.InsertSheetAsync(path, table, sheetName: "Sheet2"); using var p = new ExcelPackage(new FileInfo(path)); var sheet2 = p.Workbook.Worksheets[1]; @@ -1481,7 +1473,7 @@ public async Task InsertSheetTest() table.Columns.Add("Column2", typeof(DateTime)); table.Rows.Add("Test", now); - await MiniExcel.InsertAsync(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration + await _excelExporter.InsertSheetAsync(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1512,7 +1504,7 @@ public async Task InsertSheetTest() table.Rows.Add("MiniExcel", now); table.Rows.Add("Github", now); - await MiniExcel.InsertAsync(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration + await _excelExporter.InsertSheetAsync(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1545,63 +1537,6 @@ public async Task InsertSheetTest() } } - [Fact] - public async Task InsertCsvTest() - { - 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)}, - }; - await MiniExcel.SaveAsAsync(path, value); - var content = await File.ReadAllTextAsync(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) }; - await MiniExcel.InsertAsync(path, value); - var content = await File.ReadAllTextAsync(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)}, - }; - - await MiniExcel.InsertAsync(path, value); - var content = await File.ReadAllTextAsync(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 async Task SaveAsByAsyncEnumerable() { @@ -1615,13 +1550,13 @@ static async IAsyncEnumerable GetValues() } #pragma warning restore CS1998 - await MiniExcel.SaveAsAsync(path.ToString(), GetValues()); - var results = MiniExcel.Query(path.ToString()).ToList(); + await _excelExporter.ExportAsync(path.ToString(), GetValues()); + var results = _excelImporter.Query(path.ToString(), useHeaderRow: true).ToList(); - Assert.True(results.Count == 2); - Assert.True(results.First().Column1 == "MiniExcel"); - Assert.True(results.First().Column2 == 1); - Assert.True(results.Last().Column1 == "Github"); - Assert.True(results.Last().Column2 == 2); + Assert.Equal(2, results.Count); + Assert.Equal("MiniExcel", results[0].Column1); + Assert.Equal(1, results[0].Column2); + Assert.Equal("Github", results[^1].Column1); + Assert.Equal(2, results[^1].Column2); } } \ No newline at end of file diff --git a/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlConfigurationTest.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlConfigurationTest.cs new file mode 100644 index 00000000..11ff90a9 --- /dev/null +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlConfigurationTest.cs @@ -0,0 +1,40 @@ +using MiniExcelLib.Core.Helpers; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.Tests; + +public class MiniExcelOpenXmlConfigurationTest +{ + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + [Fact] + public async Task EnableWriteFilePathTest() + { + var img = await new HttpClient().GetByteArrayAsync("https://user-images.githubusercontent.com/12729184/150462383-ad9931b3-ed8d-4221-a1d6-66f799743433.png"); + ImgExportTestDto[] value = + [ + new() { Name = "github", Img = await File.ReadAllBytesAsync(PathHelper.GetFile("images/github_logo.png")) }, + new() { Name = "google", Img = await File.ReadAllBytesAsync(PathHelper.GetFile("images/google_logo.png")) }, + new() { Name = "microsoft", Img = await File.ReadAllBytesAsync(PathHelper.GetFile("images/microsoft_logo.png")) }, + new() { Name = "reddit", Img = await File.ReadAllBytesAsync(PathHelper.GetFile("images/reddit_logo.png")) }, + new() { Name = "statck_overflow", Img = await File.ReadAllBytesAsync(PathHelper.GetFile("images/statck_overflow_logo.png")) }, + new() { Name = "statck_over", Img = img } + ]; + + var path = PathHelper.GetFile("xlsx/Test_EnableWriteFilePath.xlsx"); + await _excelExporter.ExportAsync(path, value, configuration: new OpenXmlConfiguration { EnableWriteFilePath = false }, overwriteFile: true); + Assert.True(File.Exists(path)); + + var rows = await _excelImporter.QueryAsync(path).CreateListAsync(); + 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/MiniExcelTests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs similarity index 57% rename from tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs index adc17b73..e204c564 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetAsyncTests.cs @@ -1,29 +1,29 @@ -using Xunit; - -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Tests; public class MiniExcelOpenXmlMultipleSheetAsyncTests { + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + [Fact] public async Task SpecifySheetNameQueryTest() { const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; { - var q = MiniExcel.QueryAsync(path, sheetName: "Sheet3").ToBlockingEnumerable(); + 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 = MiniExcel.QueryAsync(path, sheetName: "Sheet2").ToBlockingEnumerable(); + 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 = MiniExcel.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); + var q = _excelImporter.QueryAsync(path, sheetName: "Sheet1").ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal(12, rows.Count); Assert.Equal(2, rows[0].A); @@ -32,7 +32,7 @@ public async Task SpecifySheetNameQueryTest() { await Assert.ThrowsAsync(() => { - _ = MiniExcel.QueryAsync(path, sheetName: "xxxx").ToBlockingEnumerable().ToList(); + _ = _excelImporter.QueryAsync(path, sheetName: "xxxx").ToBlockingEnumerable().ToList(); return Task.CompletedTask; }); } @@ -40,25 +40,25 @@ await Assert.ThrowsAsync(() => await using var stream = File.OpenRead(path); { - var rows = (stream.QueryAsync(sheetName: "Sheet3").ToBlockingEnumerable()).Cast>().ToList(); + 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 = (stream.QueryAsync(sheetName: "Sheet2").ToBlockingEnumerable()).Cast>().ToList(); + 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 = (stream.QueryAsync(sheetName: "Sheet1").ToBlockingEnumerable()).Cast>().ToList(); + 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 = (stream.QueryAsync(sheetName: "Sheet1").ToBlockingEnumerable()).Cast>().ToList(); + 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"]); @@ -70,9 +70,9 @@ public async Task MultiSheetsQueryBasicTest() { const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; await using var stream = File.OpenRead(path); - _ = stream.QueryAsync(sheetName: "Sheet1").ToBlockingEnumerable(); - _ = stream.QueryAsync(sheetName: "Sheet2").ToBlockingEnumerable(); - _ = stream.QueryAsync(sheetName: "Sheet3").ToBlockingEnumerable(); + _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet1").ToBlockingEnumerable(); + _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet2").ToBlockingEnumerable(); + _ = _excelImporter.QueryAsync(stream, sheetName: "Sheet3").ToBlockingEnumerable(); } [Fact] @@ -80,21 +80,23 @@ public async Task MultiSheetsQueryTest() { const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; { - var sheetNames = (await MiniExcel.GetSheetNamesAsync(path)).ToList(); + var sheetNames = await _excelImporter.GetSheetNamesAsync(path); foreach (var sheetName in sheetNames) { - _ = MiniExcel.QueryAsync(path, sheetName: sheetName).ToBlockingEnumerable(); + _ = _excelImporter.QueryAsync(path, sheetName: sheetName).ToBlockingEnumerable(); } + Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); } { await using var stream = File.OpenRead(path); - var sheetNames = (await stream.GetSheetNamesAsync()).ToList(); + var sheetNames = await _excelImporter.GetSheetNamesAsync(stream); + Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); foreach (var sheetName in sheetNames) { - _ = stream.QueryAsync(sheetName: sheetName).ToBlockingEnumerable().ToList(); + _ = _excelImporter.QueryAsync(stream, sheetName: sheetName).ToBlockingEnumerable().ToList(); } } } diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetTests.cs similarity index 65% rename from tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetTests.cs index f957063b..9cd0870a 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlMultipleSheetTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlMultipleSheetTests.cs @@ -1,58 +1,58 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using Xunit; +using MiniExcelLib.Core.OpenXml.Models; +using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Tests; public class MiniExcelOpenXmlMultipleSheetTests { + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + [Fact] public void SpecifySheetNameQueryTest() { const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; { - var rows = MiniExcel.Query(path, sheetName: "Sheet3").ToList(); + 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 = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + 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 = MiniExcel.Query(path, sheetName: "Sheet1").ToList(); + 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(() => MiniExcel.Query(path, sheetName: "xxxx").ToList()); + Assert.Throws(() => _excelImporter.Query(path, sheetName: "xxxx").ToList()); using var stream = File.OpenRead(path); { - var rows = stream.Query(sheetName: "Sheet3").ToList(); + 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 = stream.Query(sheetName: "Sheet2").ToList(); + 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 = stream.Query(sheetName: "Sheet1").ToList(); + 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 = stream.Query(sheetName: "Sheet1").ToList(); + 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); @@ -65,9 +65,9 @@ public void MultiSheetsQueryBasicTest() const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; using var stream = File.OpenRead(path); - _ = stream.Query(sheetName: "Sheet1"); - _ = stream.Query(sheetName: "Sheet2"); - _ = stream.Query(sheetName: "Sheet3"); + _ = _excelImporter.Query(stream, sheetName: "Sheet1"); + _ = _excelImporter.Query(stream, sheetName: "Sheet2"); + _ = _excelImporter.Query(stream, sheetName: "Sheet3"); } [Fact] @@ -75,10 +75,10 @@ public void MultiSheetsQueryTest() { const string path = "../../../../../samples/xlsx/TestMultiSheet.xlsx"; { - var sheetNames = MiniExcel.GetSheetNames(path).ToList(); + var sheetNames = _excelImporter.GetSheetNames(path).ToList(); foreach (var sheetName in sheetNames) { - var rows = MiniExcel.Query(path, sheetName: sheetName).ToList(); + var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); } Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); @@ -86,23 +86,24 @@ public void MultiSheetsQueryTest() { using var stream = File.OpenRead(path); - var sheetNames = stream.GetSheetNames().ToList(); + var sheetNames = _excelImporter.GetSheetNames(stream).ToList(); + Assert.Equal(new[] { "Sheet1", "Sheet2", "Sheet3" }, sheetNames); foreach (var sheetName in sheetNames) { - var rows = stream.Query(sheetName: sheetName).ToList(); + var rows = _excelImporter.Query(stream, sheetName: sheetName).ToList(); } } } - [ExcelSheet(Name = "Users")] + [MiniExcelSheet(Name = "Users")] private class UserDto { public string Name { get; set; } public int Age { get; set; } } - [ExcelSheet(Name = "Departments", State = SheetState.Hidden)] + [MiniExcelSheet(Name = "Departments", State = SheetState.Hidden)] private class DepartmentDto { public string ID { get; set; } @@ -115,21 +116,21 @@ public void ExcelSheetAttributeIsUsedWhenReadExcel() const string path = "../../../../../samples/xlsx/TestDynamicSheet.xlsx"; using (var stream = File.OpenRead(path)) { - var users = stream.Query().ToList(); + var users = _excelImporter.Query(stream).ToList(); Assert.Equal(2, users.Count); Assert.Equal("Jack", users[0].Name); - var departments = stream.Query().ToList(); + var departments = _excelImporter.Query(stream).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); } { - var users = MiniExcel.Query(path).ToList(); + var users = _excelImporter.Query(path).ToList(); Assert.Equal(2, users.Count); Assert.Equal("Jack", users[0].Name); - var departments = MiniExcel.Query(path).ToList(); + var departments = _excelImporter.Query(path).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); } @@ -142,8 +143,8 @@ public void DynamicSheetConfigurationIsUsedWhenReadExcel() { DynamicSheets = [ - new DynamicExcelSheet("usersSheet") { Name = "Users" }, - new DynamicExcelSheet("departmentSheet") { Name = "Departments" } + new DynamicExcelSheetAttribute("usersSheet") { Name = "Users" }, + new DynamicExcelSheetAttribute("departmentSheet") { Name = "Departments" } ] }; @@ -151,34 +152,34 @@ public void DynamicSheetConfigurationIsUsedWhenReadExcel() using (var stream = File.OpenRead(path)) { // take first sheet as default - var users = stream.Query(configuration: configuration, useHeaderRow: true).ToList(); + var users = _excelImporter.Query(stream, configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, users.Count); Assert.Equal("Jack", users[0].Name); // take second sheet by sheet name - var departments = stream.Query(sheetName: "Departments", configuration: configuration, useHeaderRow: true).ToList(); + var departments = _excelImporter.Query(stream, sheetName: "Departments", configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); // take second sheet by sheet key - departments = stream.Query(sheetName: "departmentSheet", configuration: configuration, useHeaderRow: true).ToList(); + departments = _excelImporter.Query(stream, sheetName: "departmentSheet", configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); } { // take first sheet as default - var users = MiniExcel.Query(path, configuration: configuration, useHeaderRow: true).ToList(); + var users = _excelImporter.Query(path, configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, users.Count); Assert.Equal("Jack", users[0].Name); // take second sheet by sheet name - var departments = MiniExcel.Query(path, sheetName: "Departments", configuration: configuration, useHeaderRow: true).ToList(); + var departments = _excelImporter.Query(path, sheetName: "Departments", configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); // take second sheet by sheet key - departments = MiniExcel.Query(path, sheetName: "departmentSheet", configuration: configuration, useHeaderRow: true).ToList(); + departments = _excelImporter.Query(path, sheetName: "departmentSheet", configuration: configuration, useHeaderRow: true).ToList(); Assert.Equal(2, departments.Count); Assert.Equal("HR", departments[0].Name); } @@ -189,7 +190,7 @@ public void ReadSheetVisibilityStateTest() { const string path = "../../../../../samples/xlsx/TestMultiSheetWithHiddenSheet.xlsx"; { - var sheetInfos = MiniExcel.GetSheetInformations(path).ToList(); + var sheetInfos = _excelImporter.GetSheetInformations(path).ToList(); Assert.Collection(sheetInfos, i => { @@ -229,12 +230,12 @@ public void WriteHiddenSheetTest() { DynamicSheets = [ - new DynamicExcelSheet("usersSheet") + new DynamicExcelSheetAttribute("usersSheet") { Name = "Users", State = SheetState.Visible }, - new DynamicExcelSheet("departmentSheet") + new DynamicExcelSheetAttribute("departmentSheet") { Name = "Departments", State = SheetState.Hidden @@ -253,12 +254,12 @@ public void WriteHiddenSheetTest() using var file = AutoDeletingPath.Create(); var path = file.ToString(); - var rowsWritten = MiniExcel.SaveAs(path, sheets, configuration: configuration); + var rowsWritten = MiniExcel.Exporters.GetOpenXmlExporter().Export(path, sheets, configuration: configuration); Assert.Equal(2, rowsWritten.Length); Assert.Equal(2, rowsWritten[0]); - var sheetInfos = MiniExcel.GetSheetInformations(path).ToList(); + var sheetInfos = _excelImporter.GetSheetInformations(path).ToList(); Assert.Collection(sheetInfos, i => { @@ -277,7 +278,7 @@ public void WriteHiddenSheetTest() foreach (var sheetName in sheetInfos.Select(s => s.Name)) { - var rows = MiniExcel.Query(path, sheetName: sheetName).ToList(); + var rows = _excelImporter.Query(path, sheetName: sheetName).ToList(); } } } \ No newline at end of file diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlTests.cs similarity index 79% rename from tests/MiniExcelTests/MiniExcelOpenXmlTests.cs rename to tests/MiniExcel.Core.Tests/MiniExcelOpenXmlTests.cs index f43d3520..64bd1e82 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlTests.cs @@ -1,42 +1,34 @@ using ClosedXML.Excel; -using Dapper; using ExcelDataReader; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using OfficeOpenXml; -using System.Data; -using System.Data.SQLite; -using System.Diagnostics; -using System.Globalization; -using System.IO.Packaging; -using System.Text; -using Xunit; -using Xunit.Abstractions; - -namespace MiniExcelLibs.Tests; +using MiniExcelLib.Core.OpenXml.Utils; +using MiniExcelLib.Tests.Common.Utils; + +namespace MiniExcelLib.Tests; public class MiniExcelOpenXmlTests(ITestOutputHelper output) { private readonly ITestOutputHelper _output = output; - + + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + [Fact] public void GetColumnsTest() { const string tmPath = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; const string tePath = "../../../../../samples/xlsx/TestEmpty.xlsx"; { - var columns = MiniExcel.GetColumns(tmPath); + var columns = _excelImporter.GetColumnNames (tmPath); Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columns); } { - var columns = MiniExcel.GetColumns(tmPath); + var columns = _excelImporter.GetColumnNames (tmPath); Assert.Equal(8, columns.Count); } { - var columns = MiniExcel.GetColumns(tePath); + var columns = _excelImporter.GetColumnNames (tePath); Assert.Empty(columns); } } @@ -56,10 +48,10 @@ public void SaveAsControlChracter() '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' ]; var input = chars.Select(s => new { Test = s.ToString() }); - MiniExcel.SaveAs(path.ToString(), input); + _excelExporter.Export(path.ToString(), input); - var rows2 = MiniExcel.Query(path.ToString(), true).Select(s => s.Test).ToArray(); - var rows1 = MiniExcel.Query(path.ToString()).Select(s => s.Test).ToArray(); + var rows2 = _excelImporter.Query(path.ToString(), true).Select(s => s.Test).ToArray(); + var rows1 = _excelImporter.Query(path.ToString()).Select(s => s.Test).ToArray(); } private class SaveAsControlChracterVO @@ -69,17 +61,17 @@ private class SaveAsControlChracterVO private class ExcelAttributeDemo { - [ExcelColumnName("Column1")] + [MiniExcelColumnName("Column1")] public string Test1 { get; set; } - [ExcelColumnName("Column2")] + [MiniExcelColumnName("Column2")] public string Test2 { get; set; } - [ExcelIgnore] + [MiniExcelIgnore] public string Test3 { get; set; } - [ExcelColumnIndex("I")] // system will convert "I" to 8 index + [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 - [ExcelColumnIndex(3)] // start with 0 + [MiniExcelColumnIndex(3)] // start with 0 public string Test7 { get; set; } } @@ -87,14 +79,14 @@ private class ExcelAttributeDemo public void CustomAttributeWihoutVaildPropertiesTest() { const string path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; - Assert.Throws(() => MiniExcel.Query(path).ToList()); + Assert.Throws(() => _excelImporter.Query(path).ToList()); } [Fact] public void QueryCustomAttributesTest() { const string path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal("Column1", rows[0].Test1); Assert.Equal("Column2", rows[0].Test2); @@ -118,8 +110,8 @@ public void SaveAsCustomAttributesTest() Test4 = "Test4", }); - MiniExcel.SaveAs(path.ToString(), input); - var rows = MiniExcel.Query(path.ToString(), true).ToList(); + _excelExporter.Export(path.ToString(), input); + var rows = _excelImporter.Query(path.ToString(), true).ToList(); var first = rows[0] as IDictionary; Assert.Equal(3, rows.Count); @@ -133,7 +125,7 @@ public void SaveAsCustomAttributesTest() private class CustomAttributesWihoutVaildPropertiesTestPoco { - [ExcelIgnore] + [MiniExcelIgnore] public string Test3 { get; set; } public string Test5 { get; } public string Test6 { get; private set; } @@ -145,7 +137,7 @@ private class CustomAttributesWihoutVaildPropertiesTestPoco public void QueryCastToIDictionary() { const string path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; - foreach (IDictionary row in MiniExcel.Query(path)) + foreach (IDictionary row in _excelImporter.Query(path)) { _ = row; } @@ -156,7 +148,7 @@ public void QueryRangeToIDictionary() { const string path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; // tips:Only uppercase letters are effective - var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C7") + var rows = _excelImporter.QueryRange(path, startCell: "A2", endCell: "C7") .Cast>() .ToList(); @@ -165,9 +157,9 @@ public void QueryRangeToIDictionary() Assert.Equal(2d, rows[1]["B"]); Assert.Equal(null!, rows[2]["A"]); - var startCellXY = MiniExcelLibs.Utils.ReferenceHelper.ConvertCellToXY("A2"); - var endCellXY = MiniExcelLibs.Utils.ReferenceHelper.ConvertCellToXY("C7"); - rows = MiniExcel.QueryRange(path, startRowIndex: startCellXY.Item2, startColumnIndex: startCellXY.Item1, endRowIndex: endCellXY.Item2, endColumnIndex: endCellXY.Item1) + var startCellXY = ReferenceHelper.ConvertCellToCoordinates("A2"); + var endCellXY = ReferenceHelper.ConvertCellToCoordinates("C7"); + rows = _excelImporter.QueryRange(path, startRowIndex: startCellXY.Item2, startColumnIndex: startCellXY.Item1, endRowIndex: endCellXY.Item2, endColumnIndex: endCellXY.Item1) .Cast>() .ToList(); Assert.Equal(5, rows.Count); @@ -175,14 +167,14 @@ public void QueryRangeToIDictionary() Assert.Equal(2d, rows[1]["B"]); Assert.Equal(null!, rows[2]["A"]); - rows = MiniExcel.QueryRange(path, startRowIndex:2, startColumnIndex: 1, endRowIndex: 3) + rows = _excelImporter.QueryRange(path, startRowIndex:2, startColumnIndex: 1, endRowIndex: 3) .Cast>() .ToList(); Assert.Equal(2, rows.Count); Assert.Equal(4, rows[0].Count); Assert.Equal(4d, rows[1]["D"]); - rows = MiniExcel.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endColumnIndex: 3) + rows = _excelImporter.QueryRange(path, startRowIndex: 2, startColumnIndex: 1, endColumnIndex: 3) .Cast>() .ToList(); Assert.Equal(5, rows.Count); @@ -196,7 +188,7 @@ public void CenterEmptyRowsQueryTest() const string path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; using (var stream = File.OpenRead(path)) { - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal("a", rows[0].A); Assert.Equal("b", rows[0].B); @@ -231,7 +223,7 @@ public void CenterEmptyRowsQueryTest() using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal(1, rows[0].a); Assert.Equal(null, rows[0].b); @@ -265,7 +257,7 @@ public void TestEmptyRowsQuerySelfClosingTag() { const string path = "../../../../../samples/xlsx/TestEmptySelfClosingRow.xlsx"; using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal(null, rows[0].A); Assert.Equal(1, rows[1].A); @@ -284,7 +276,7 @@ public void TestDynamicQueryBasic_WithoutHead() { const string path = "../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal("MiniExcel", rows[0].A); Assert.Equal(1, rows[0].B); @@ -298,7 +290,7 @@ public void TestDynamicQueryBasic_useHeaderRow() const string path = "../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -307,7 +299,7 @@ public void TestDynamicQueryBasic_useHeaderRow() } { - var rows = MiniExcel.Query(path, useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(path, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -338,7 +330,7 @@ public void QueryStrongTypeMapping_Test() const string path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; using (var stream = File.OpenRead(path)) { - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal(100, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); @@ -351,7 +343,7 @@ public void QueryStrongTypeMapping_Test() } { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(100, rows.Count); Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID); @@ -377,7 +369,7 @@ public void AutoCheckTypeTest() { const string path = "../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; using var stream = FileHelper.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); } private class ExcelUriDemo @@ -392,7 +384,7 @@ public void UriMappingTest() { const string path = "../../../../../samples/xlsx/TestUriMapping.xlsx"; using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal("Felix", rows[1].Name); Assert.Equal(44, rows[1].Age); @@ -410,7 +402,7 @@ private class SimpleAccount public void TrimColumnNamesTest() { const string path = "../../../../../samples/xlsx/TestTrimColumnNames.xlsx"; - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal("Raymond", rows[4].Name); Assert.Equal(18, rows[4].Age); @@ -424,7 +416,7 @@ public void TestDatetimeSpanFormat_ClosedXml() const string path = "../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; using var stream = FileHelper.OpenRead(path); - var row = stream.Query().First(); + var row = _excelImporter.Query(stream).First(); var a = row.A; var b = row.B; Assert.Equal(DateTime.Parse("2021-03-20T23:39:42.3130000"), (DateTime)a); @@ -437,13 +429,13 @@ public void LargeFileQueryStrongTypeMapping_Test() const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; using (var stream = File.OpenRead(path)) { - var rows = stream.Query().Take(2).ToList(); + var rows = _excelImporter.Query(stream).Take(2).ToList(); Assert.Equal("HelloWorld2", rows[0].HelloWorld1); Assert.Equal("HelloWorld3", rows[1].HelloWorld1); } { - var rows = MiniExcel.Query(path).Take(2).ToList(); + var rows = _excelImporter.Query(path).Take(2).ToList(); Assert.Equal("HelloWorld2", rows[0].HelloWorld1); Assert.Equal("HelloWorld3", rows[1].HelloWorld1); @@ -453,17 +445,17 @@ public void LargeFileQueryStrongTypeMapping_Test() [Theory] [InlineData("../../../../../samples/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] [InlineData("../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] - public void QueryExcelDataReaderCheckTest(string path) + public void QueryDataReaderCheckTest(string path) { #if NETCOREAPP3_1_OR_GREATER Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); #endif using var fs = File.OpenRead(path); - using var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(fs); + using var reader = ExcelReaderFactory.CreateReader(fs); var exceldatareaderResult = reader.AsDataSet(); using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal(exceldatareaderResult.Tables[0].Rows.Count, rows.Count); foreach (IDictionary row in rows) @@ -471,7 +463,7 @@ public void QueryExcelDataReaderCheckTest(string path) var rowIndex = rows.IndexOf(row); foreach (var (key, value) in row) { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][Helpers.GetColumnIndex(key)]; + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][SheetHelper.GetColumnIndex(key)]; var v = value ?? DBNull.Value; Assert.Equal(eV, v); } @@ -493,7 +485,7 @@ public void QuerySheetWithoutRAttribute() { const string path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); var keys = (rows.First() as IDictionary)!.Keys; Assert.Equal(2, rows.Count); @@ -515,7 +507,7 @@ public void FixDimensionJustOneColumnParsingError_Test() { const string path = "../../../../../samples/xlsx/TestDimensionC3.xlsx"; using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); var keys = ((IDictionary)rows.First()).Keys; Assert.Equal(3, keys.Count); Assert.Equal(2, rows.Count); @@ -539,11 +531,11 @@ public void SaveAsFileWithDimensionByICollection() new() { A = "A", B = "B" }, new() { A = "A", B = "B" } ]; - MiniExcel.SaveAs(path, values); + _excelExporter.Export(path, values); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -551,15 +543,15 @@ public void SaveAsFileWithDimensionByICollection() } using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); - MiniExcel.SaveAs(path, values, false, overwriteFile: true); - Assert.Equal("A1:B2", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, values, false, overwriteFile: true); + Assert.Equal("A1:B2", SheetHelper.GetFirstSheetDimensionRefValue(path)); } //List empty @@ -568,23 +560,23 @@ public void SaveAsFileWithDimensionByICollection() var path = file.ToString(); List values = []; - MiniExcel.SaveAs(path, values, false); + _excelExporter.Export(path, values, false); { using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Empty(rows); } - Assert.Equal("A1:B1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } - MiniExcel.SaveAs(path, values, overwriteFile: true); + _excelExporter.Export(path, values, overwriteFile: true); { using var stream = File.OpenRead(path); - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Single(rows); } - Assert.Equal("A1:B1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } //Array @@ -596,11 +588,11 @@ public void SaveAsFileWithDimensionByICollection() new {A="A",B="B"}, new {A="A",B="B"}, }; - MiniExcel.SaveAs(path, values); + _excelExporter.Export(path, values); { using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Equal(3, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); @@ -608,23 +600,23 @@ public void SaveAsFileWithDimensionByICollection() } using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal("A", rows[0].A); Assert.Equal("A", rows[1].A); } } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); - MiniExcel.SaveAs(path, values, false, overwriteFile: true); - Assert.Equal("A1:B2", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, values, false, overwriteFile: true); + Assert.Equal("A1:B2", SheetHelper.GetFirstSheetDimensionRefValue(path)); } // without properties { using var path = AutoDeletingPath.Create(); var values = new List(); - Assert.Throws(() => MiniExcel.SaveAs(path.ToString(), values)); + Assert.Throws(() => _excelExporter.Export(path.ToString(), values)); } } @@ -636,16 +628,16 @@ public void SaveAsFileWithDimension() var path = file.ToString(); var table = new DataTable(); - MiniExcel.SaveAs(path, table); - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, table); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); { using var stream = File.OpenRead(path); - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Single(rows); } - MiniExcel.SaveAs(path, table, printHeader: false, overwriteFile: true); - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, table, printHeader: false, overwriteFile: true); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); } { @@ -660,12 +652,12 @@ public void SaveAsFileWithDimension() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890); table.Rows.Add("Hello World", -1234567890, false, DateTime.Now); - MiniExcel.SaveAs(path, table); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, table); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(path)); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal(@"""<>+-*//}{\\n", rows[0].a); Assert.Equal(1234567890, rows[0].b); @@ -675,7 +667,7 @@ public void SaveAsFileWithDimension() using (var stream = File.OpenRead(path)) { - var rows = stream.Query().ToList(); + var rows = _excelImporter.Query(stream).ToList(); Assert.Equal(3, rows.Count); Assert.Equal("a", rows[0].A); Assert.Equal("b", rows[0].B); @@ -683,8 +675,8 @@ public void SaveAsFileWithDimension() Assert.Equal("d", rows[0].D); } - MiniExcel.SaveAs(path, table, printHeader: false, overwriteFile: true); - Assert.Equal("A1:D2", Helpers.GetFirstSheetDimensionRefValue(path)); + _excelExporter.Export(path, table, printHeader: false, overwriteFile: true); + Assert.Equal("A1:D2", SheetHelper.GetFirstSheetDimensionRefValue(path)); } //TODO:StartCell @@ -696,8 +688,8 @@ public void SaveAsFileWithDimension() table.Rows.Add("A"); table.Rows.Add("B"); - MiniExcel.SaveAs(path.ToString(), table); - Assert.Equal("A1:A3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + _excelExporter.Export(path.ToString(), table); + Assert.Equal("A1:A3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } } @@ -717,7 +709,7 @@ public void SaveAsByDataTableTest() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); table.Rows.Add("Hello World", -1234567890, false, now.Date); - MiniExcel.SaveAs(path, table, sheetName: "R&D"); + _excelExporter.Export(path, table, sheetName: "R&D"); using var p = new ExcelPackage(new FileInfo(path)); var ws = p.Workbook.Worksheets.First(); @@ -742,7 +734,7 @@ public void SaveAsByDataTableTest() table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); - MiniExcel.SaveAs(path.ToString(), table); + _excelExporter.Export(path.ToString(), table); } } @@ -751,16 +743,16 @@ public void QueryByLINQExtensionsAvoidLargeFileOOMTest() { const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; - var query1 = MiniExcel.Query(path).First(); + var query1 = _excelImporter.Query(path).First(); Assert.Equal("HelloWorld1", query1.A); using (var stream = File.OpenRead(path)) { - var query2 = stream.Query().First(); + var query2 = _excelImporter.Query(stream).First(); Assert.Equal("HelloWorld1", query2.A); } - var query3 = MiniExcel.Query(path).Take(10); + var query3 = _excelImporter.Query(path).Take(10); Assert.Equal(10, query3.Count()); } @@ -771,11 +763,11 @@ public void EmptyTest() using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = connection.Query("with cte as (select 1 id,2 val) select * from cte where 1=2"); - MiniExcel.SaveAs(path.ToString(), rows); + _excelExporter.Export(path.ToString(), rows); } using (var stream = File.OpenRead(path.ToString())) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Empty(rows); } } @@ -797,11 +789,11 @@ public void SaveAsByIEnumerableIDictionary() ["R&D"] = values, ["success!"] = values }; - MiniExcel.SaveAs(path, sheets); + _excelExporter.Export(path, sheets); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Equal("Column1", rows[0].A); Assert.Equal("Column2", rows[0].B); Assert.Equal("MiniExcel", rows[1].A); @@ -809,12 +801,12 @@ public void SaveAsByIEnumerableIDictionary() Assert.Equal("Github", rows[2].A); Assert.Equal(2, rows[2].B); - Assert.Equal("R&D", stream.GetSheetNames()[0]); + Assert.Equal("R&D", _excelImporter.GetSheetNames(stream)[0]); } using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal(2, rows.Count); Assert.Equal("MiniExcel", rows[0].Column1); @@ -822,10 +814,10 @@ public void SaveAsByIEnumerableIDictionary() Assert.Equal("Github", rows[1].Column1); Assert.Equal(2, rows[1].Column2); - Assert.Equal("success!", stream.GetSheetNames()[1]); + Assert.Equal("success!", _excelImporter.GetSheetNames(stream)[1]); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } { @@ -834,15 +826,15 @@ public void SaveAsByIEnumerableIDictionary() new() { { 1, "MiniExcel"}, { 2, 1 } }, new() { { 1, "Github" }, { 2, 2 } }, }; - MiniExcel.SaveAs(path, values, overwriteFile: true); + _excelExporter.Export(path, values, overwriteFile: true); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Equal(3, rows.Count); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); } } @@ -857,7 +849,7 @@ public void SaveAsFrozenRowsAndColumnsTest() // Test enumerable using var path = AutoDeletingPath.Create(); - MiniExcel.SaveAs( + _excelExporter.Export( path.ToString(), new[] { @@ -869,7 +861,7 @@ public void SaveAsFrozenRowsAndColumnsTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -877,7 +869,7 @@ public void SaveAsFrozenRowsAndColumnsTest() Assert.Equal(2, rows[1].Column2); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); // test table var table = new DataTable(); @@ -889,15 +881,15 @@ public void SaveAsFrozenRowsAndColumnsTest() table.Rows.Add("Hello World", -1234567890, false, DateTime.Now.Date); using var pathTable = AutoDeletingPath.Create(); - MiniExcel.SaveAs(pathTable.ToString(), table, configuration: config); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable.ToString())); + _excelExporter.Export(pathTable.ToString(), table, configuration: config); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(pathTable.ToString())); // data reader var reader = table.CreateDataReader(); using var pathReader = AutoDeletingPath.Create(); - MiniExcel.SaveAs(pathReader.ToString(), reader, configuration: config, overwriteFile: true); - Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable.ToString())); //TODO: fix datareader not writing ref dimension (also in async version) + _excelExporter.Export(pathReader.ToString(), reader, configuration: config, overwriteFile: true); + Assert.Equal("A1:D3", SheetHelper.GetFirstSheetDimensionRefValue(pathTable.ToString())); //TODO: fix datareader not writing ref dimension (also in async version) } [Fact] @@ -910,14 +902,14 @@ public void SaveAsByDapperRows() using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); - MiniExcel.SaveAs(path, rows); + _excelExporter.Export(path, rows); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -929,35 +921,35 @@ public void SaveAsByDapperRows() using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = connection.Query("with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2").ToList(); - MiniExcel.SaveAs(path, rows, overwriteFile: true); + _excelExporter.Export(path, rows, overwriteFile: true); } using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Empty(rows); } using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Empty(rows); } - Assert.Equal("A1", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1", SheetHelper.GetFirstSheetDimensionRefValue(path)); // ToList using (var connection = Db.GetConnection("Data Source=:memory:")) { var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2").ToList(); - MiniExcel.SaveAs(path, rows, overwriteFile: true); + _excelExporter.Export(path, rows, overwriteFile: true); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path)); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path)); using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: false).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: false).ToList(); Assert.Equal("Column1", rows[0].A); Assert.Equal("Column2", rows[0].B); @@ -969,7 +961,7 @@ public void SaveAsByDapperRows() using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -992,10 +984,10 @@ public void QueryByStrongTypeParameterTest() new() { Column1 = "MiniExcel", Column2 = 1 }, new() { Column1 = "Github", Column2 = 2 } ]; - MiniExcel.SaveAs(path.ToString(), values); + _excelExporter.Export(path.ToString(), values); using var stream = File.OpenRead(path.ToString()); - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1012,10 +1004,10 @@ public void QueryByDictionaryStringAndObjectParameterTest() new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, new() { { "Column1", "Github" }, { "Column2", 2 } } ]; - MiniExcel.SaveAs(path.ToString(), values); + _excelExporter.Export(path.ToString(), values); using var stream = File.OpenRead(path.ToString()); - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1042,7 +1034,7 @@ public void SQLiteInsertTest() using (var transaction = connection.BeginTransaction()) using (var stream = File.OpenRead(path)) { - var rows = stream.Query(); + 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); @@ -1064,7 +1056,7 @@ public void SaveAsBasicCreateTest() { using var path = AutoDeletingPath.Create(); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { Column1 = "MiniExcel", Column2 = 1 }, new { Column1 = "Github", Column2 = 2} @@ -1075,7 +1067,7 @@ public void SaveAsBasicCreateTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1083,7 +1075,7 @@ public void SaveAsBasicCreateTest() Assert.Equal(2, rows[1].Column2); } - Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path.ToString())); + Assert.Equal("A1:B3", SheetHelper.GetFirstSheetDimensionRefValue(path.ToString())); } [Fact] @@ -1098,14 +1090,14 @@ public void SaveAsBasicStreamTest() }; using (var stream = new FileStream(path.ToString(), FileMode.CreateNew)) { - var rowsWritten = stream.SaveAs(values); + var rowsWritten = _excelExporter.Export(stream, values); Assert.Single(rowsWritten); Assert.Equal(2, rowsWritten[0]); } using (var stream = File.OpenRead(path.ToString())) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1123,7 +1115,7 @@ public void SaveAsBasicStreamTest() using (var stream = new MemoryStream()) using (var fileStream = new FileStream(path.ToString(), FileMode.Create)) { - var rowsWritten = stream.SaveAs(values); + var rowsWritten = _excelExporter.Export(stream, values); stream.Seek(0, SeekOrigin.Begin); stream.CopyTo(fileStream); Assert.Single(rowsWritten); @@ -1132,7 +1124,7 @@ public void SaveAsBasicStreamTest() using (var stream = File.OpenRead(path.ToString())) { - var rows = stream.Query(useHeaderRow: true).ToList(); + var rows = _excelImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("MiniExcel", rows[0].Column1); Assert.Equal(1, rows[0].Column2); @@ -1146,7 +1138,7 @@ public void SaveAsBasicStreamTest() public void SaveAsSpecialAndTypeCreateTest() { using var path = AutoDeletingPath.Create(); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = DateTime.Now }, new { a = "Hello World", b = -1234567890, c = false, d = DateTime.Now.Date } @@ -1163,7 +1155,7 @@ public void SaveAsFileEpplusCanReadTest() { var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now}, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1190,7 +1182,7 @@ public void SavaAsClosedXmlCanReadTest() { var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d = now }, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1220,7 +1212,7 @@ public void ContentTypeUriContentTypeReadCheckTest() { var now = DateTime.Now; using var path = AutoDeletingPath.Create(); - var rowsWritten = MiniExcel.SaveAs(path.ToString(), new[] + var rowsWritten = _excelExporter.Export(path.ToString(), new[] { new { a = @"""<>+-*//}{\\n", b = 1234567890, c = true, d= now }, new { a = "Hello World", b = -1234567890, c = false, d = now.Date } @@ -1244,10 +1236,10 @@ public void ContentTypeUriContentTypeReadCheckTest() public void TestStirctOpenXml() { const string path = "../../../../../samples/xlsx/TestStrictOpenXml.xlsx"; - var columns = MiniExcel.GetColumns(path); + var columns = _excelImporter.GetColumnNames (path); Assert.Equal(["A", "B", "C"], columns); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(rows[0].A, "title1"); Assert.Equal(rows[0].B, "title2"); Assert.Equal(rows[0].C, "title3"); @@ -1262,7 +1254,7 @@ public void SharedStringCacheTest() const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; var ts = Stopwatch.GetTimestamp(); - _ = MiniExcel.Query(path, configuration: new OpenXmlConfiguration { EnableSharedStringCache = true }).First(); + _ = _excelImporter.Query(path, configuration: new OpenXmlConfiguration { EnableSharedStringCache = true }).First(); using var currentProcess = Process.GetCurrentProcess(); var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; @@ -1276,7 +1268,7 @@ public void SharedStringNoCacheTest() const string path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10_SharingStrings.xlsx"; var ts = Stopwatch.GetTimestamp(); - _ = MiniExcel.Query(path).First(); + _ = _excelImporter.Query(path).First(); using var currentProcess = Process.GetCurrentProcess(); var totalBytesOfMemoryUsed = currentProcess.WorkingSet64; _output.WriteLine("totalBytesOfMemoryUsed: " + totalBytesOfMemoryUsed); @@ -1324,10 +1316,10 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() ] }; var reader = table.CreateDataReader(); - MiniExcel.SaveAs(path.ToString(), reader, configuration: configuration); + _excelExporter.Export(path.ToString(), reader, configuration: configuration); using var stream = File.OpenRead(path.ToString()); - var rows = stream.Query(useHeaderRow: true) + var rows = _excelImporter.Query(stream, useHeaderRow: true) .Select(x => (IDictionary)x) .ToList(); @@ -1390,10 +1382,10 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() } ] }; - MiniExcel.SaveAs(path.ToString(), table, configuration: configuration); + _excelExporter.Export(path.ToString(), table, configuration: configuration); using var stream = File.OpenRead(path.ToString()); - var rows = stream.Query(useHeaderRow: true) + var rows = _excelImporter.Query(stream, useHeaderRow: true) .Select(x => (IDictionary)x) .Select(x => (IDictionary)x) .ToList(); @@ -1434,7 +1426,7 @@ public void InsertSheetTest() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); table.Rows.Add("Hello World", -1234567890, false, now.Date); - var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet1"); + var rowsWritten = _excelExporter.InsertSheet(path, table, sheetName: "Sheet1"); Assert.Equal(2, rowsWritten); using var p = new ExcelPackage(new FileInfo(path)); @@ -1459,7 +1451,7 @@ public void InsertSheetTest() table.Rows.Add("MiniExcel", 1); table.Rows.Add("Github", 2); - var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet2"); + var rowsWritten = _excelExporter.InsertSheet(path, table, sheetName: "Sheet2"); Assert.Equal(2, rowsWritten); using var p = new ExcelPackage(new FileInfo(path)); @@ -1482,7 +1474,7 @@ public void InsertSheetTest() table.Columns.Add("Column2", typeof(DateTime)); table.Rows.Add("Test", now); - var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration + var rowsWritten = _excelExporter.InsertSheet(path, table, sheetName: "Sheet2", printHeader: false, configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1515,7 +1507,7 @@ public void InsertSheetTest() table.Rows.Add("MiniExcel", now); table.Rows.Add("Github", now); - var rowsWritten = MiniExcel.Insert(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration + var rowsWritten = _excelExporter.InsertSheet(path, table, sheetName: "Sheet3", configuration: new OpenXmlConfiguration { FastMode = true, AutoFilter = false, @@ -1552,13 +1544,13 @@ public void InsertSheetTest() private class DateOnlyTest { public DateOnly Date { get; set; } - [ExcelFormat("d.M.yyyy")] public DateOnly DateWithFormat { get; set; } + [MiniExcelFormat("d.M.yyyy")] public DateOnly DateWithFormat { get; set; } } [Fact] public void DateOnlySupportTest() { - var query = MiniExcel.Query(PathHelper.GetFile("xlsx/TestDateOnlyMapping.xlsx")).ToList(); + var query = _excelImporter.Query(PathHelper.GetFile("xlsx/TestDateOnlyMapping.xlsx")).ToList(); Assert.Equal(new DateOnly(2020, 9, 27), query[0].Date); Assert.Equal(new DateOnly(2020, 10, 25), query[1].Date); @@ -1573,7 +1565,7 @@ public void DateOnlySupportTest() public void SheetDimensionsTest() { var path1 = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - var dim1 = MiniExcel.GetSheetDimensions(path1); + var dim1 = _excelImporter.GetSheetDimensions(path1); Assert.Equal("A1", dim1[0].StartCell); Assert.Equal("H101", dim1[0].EndCell); Assert.Equal(101, dim1[0].Rows.Count); @@ -1584,7 +1576,7 @@ public void SheetDimensionsTest() Assert.Equal(8, dim1[0].Columns.EndIndex); var path2 = PathHelper.GetFile("xlsx/TestNoDimension.xlsx"); - var dim2 = MiniExcel.GetSheetDimensions(path2); + var dim2 = _excelImporter.GetSheetDimensions(path2); Assert.Equal(101, dim2[0].Rows.Count); Assert.Equal(7, dim2[0].Columns.Count); Assert.Equal(1, dim2[0].Rows.StartIndex); @@ -1597,7 +1589,7 @@ public void SheetDimensionsTest() public void SheetDimensionsTest_MultiSheet() { var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - var dim = MiniExcel.GetSheetDimensions(path); + var dim = _excelImporter.GetSheetDimensions(path); Assert.Equal("A1", dim[0].StartCell); Assert.Equal("D12", dim[0].EndCell); diff --git a/tests/MiniExcelTests/SaveByTemplate/InputValueExtractorTests.cs b/tests/MiniExcel.Core.Tests/SaveByTemplate/InputValueExtractorTests.cs similarity index 92% rename from tests/MiniExcelTests/SaveByTemplate/InputValueExtractorTests.cs rename to tests/MiniExcel.Core.Tests/SaveByTemplate/InputValueExtractorTests.cs index 7c39b890..e5df32a9 100644 --- a/tests/MiniExcelTests/SaveByTemplate/InputValueExtractorTests.cs +++ b/tests/MiniExcel.Core.Tests/SaveByTemplate/InputValueExtractorTests.cs @@ -1,8 +1,6 @@ -using System.Data; -using MiniExcelLibs.SaveByTemplate; -using Xunit; +using MiniExcelLib.Core.OpenXml.Templates; -namespace MiniExcelLibs.Tests.SaveByTemplate; +namespace MiniExcelLib.Tests.SaveByTemplate; public class InputValueExtractorTests { @@ -16,7 +14,7 @@ public void ToValueDictionary_Given_InputIsDictionaryWithoutDataReader_Then_Outp ["Fruits"] = new List { "Apples, Oranges" }, }; - var sut = new InputValueExtractor(); + var sut = new OpenXmlValueExtractor(); var result = sut.ToValueDictionary(valueDictionary); Assert.Equal(result.Count, valueDictionary.Count); @@ -47,7 +45,7 @@ public void ToValueDictionary_Given_InputIsDictionaryWithDataReader_Then_DataRea ["DataReader"] = dataTable.CreateDataReader() }; - var sut = new InputValueExtractor(); + var sut = new OpenXmlValueExtractor(); var extracted = sut.ToValueDictionary(valueDictionary); var result = (List>)extracted["DataReader"]; @@ -76,7 +74,7 @@ public void ToValueDictionary_Given_InputIsPocoRecord_Then_Output_IsAnEquivalent ["Fruits"] = new List { "Apples, Oranges" } }; - var sut = new InputValueExtractor(); + var sut = new OpenXmlValueExtractor(); var result = sut.ToValueDictionary(valueObject); Assert.Equal(result.Count, expectedOutput.Count); @@ -103,7 +101,7 @@ public void ToValueDictionary_Given_InputIsPocoClass_Then_Output_IsAnEquivalentD ["Fruits"] = new List { "Apples, Oranges" } }; - var sut = new InputValueExtractor(); + var sut = new OpenXmlValueExtractor(); var result = sut.ToValueDictionary(valueObject); Assert.Equal(result.Count, expectedOutput.Count); diff --git a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs similarity index 78% rename from tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs rename to tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs index a48ca63e..90f894bb 100644 --- a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateAsyncTests.cs @@ -1,12 +1,12 @@ -using System.Data; -using Dapper; -using MiniExcelLibs.Tests.Utils; -using Xunit; +using MiniExcelLib.Tests.Common.Utils; -namespace MiniExcelLibs.Tests.SaveByTemplate; +namespace MiniExcelLib.Tests.SaveByTemplate; public class MiniExcelTemplateAsyncTests { + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + [Fact] public async Task DatatableTemptyRowTest() { @@ -29,10 +29,10 @@ public async Task DatatableTemptyRowTest() ["managers"] = managers, ["employees"] = employees }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C5", dimension); } { @@ -54,10 +54,10 @@ public async Task DatatableTemptyRowTest() ["employees"] = employees }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C5", dimension); } } @@ -88,10 +88,10 @@ public async Task DatatableTest() ["managers"] = managers, ["employees"] = employees }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); Assert.Equal(9, rows.Count); @@ -112,7 +112,7 @@ public async Task DatatableTest() Assert.Equal("IT", rows[8].C); { - rows = MiniExcel.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); + rows = _excelImporter.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -131,7 +131,7 @@ public async Task DatatableTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -149,10 +149,10 @@ public async Task DapperTemplateTest() ["managers"] = connection.Query("select 'Jack' name,'HR' department union all select 'Loan','IT'"), ["employees"] = connection.Query(@"select 'Wade' name,'HR' department union all select 'Felix','HR' union all select 'Eric','IT' union all select 'Keaton','IT'") }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); { - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal(9, rows.Count); @@ -172,12 +172,12 @@ public async Task DapperTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -196,7 +196,7 @@ public async Task DapperTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -223,10 +223,10 @@ public async Task DictionaryTemplateTest() new Dictionary{["name"]="Keaton",["department"]="IT"} } }; - await MiniExcel.SaveAsByTemplateAsync(path, templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path, templatePath, value); { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(9, rows.Count); @@ -246,12 +246,12 @@ public async Task DictionaryTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); Assert.Equal(9, rows.Count); @@ -271,7 +271,7 @@ public async Task DictionaryTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } } @@ -294,13 +294,13 @@ public async Task TestGithubProject() Projects = projects, TotalStar = projects.Sum(s => s.Star) }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal("ITWeiHan Github Projects", rows[0].B); Assert.Equal("Total Star : 178", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:D9", dimension); } @@ -342,9 +342,9 @@ public async Task TestIEnumerableType() poco } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(poco.@string, rows[0].@string); Assert.Equal(poco.@int, rows[0].@int); Assert.Equal(poco.@double, rows[0].@double); @@ -386,7 +386,7 @@ public async Task TestIEnumerableType() Assert.Equal(poco.datetime, rows[4].datetime); Assert.Equal(poco.Guid, rows[4].Guid); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:G6", dimension); } } @@ -407,9 +407,9 @@ public async Task TestTemplateTypeMapping() @bool = true, Guid = Guid.NewGuid() }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(value.@string, rows[0].@string); Assert.Equal(value.@int, rows[0].@int); Assert.Equal(value.@double, rows[0].@double); @@ -418,7 +418,7 @@ public async Task TestTemplateTypeMapping() Assert.Equal(value.datetime, rows[0].datetime); Assert.Equal(value.Guid, rows[0].Guid); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:G2", dimension); } @@ -431,7 +431,7 @@ public async Task TemplateCenterEmptyTest() { Tests = Enumerable.Range(1, 5).Select(i => new { test1 = i, test2 = i }) }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); } [Fact] @@ -449,16 +449,16 @@ public async Task TemplateAsyncBasiTest() VIP = true, Points = 123 }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } @@ -473,16 +473,16 @@ public async Task TemplateAsyncBasiTest() VIP = true, Points = 123 }; - await MiniExcel.SaveAsByTemplateAsync(path, templateBytes, value); + await _excelTemplater.ApplyTemplateAsync(path, templateBytes, value); - var rows = (MiniExcel.QueryAsync(path).ToBlockingEnumerable()).ToList(); + var rows = (_excelImporter.QueryAsync(path).ToBlockingEnumerable()).ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:E2", dimension); } @@ -500,17 +500,17 @@ public async Task TemplateAsyncBasiTest() }; await using (var stream = File.Create(path.ToString())) { - await stream.SaveAsByTemplateAsync(templateBytes, value); + await _excelTemplater.ApplyTemplateAsync(stream, templateBytes, value); } - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } @@ -525,16 +525,16 @@ public async Task TemplateAsyncBasiTest() ["VIP"] = true, ["Points"] = 123 }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } } @@ -559,9 +559,9 @@ public async Task TestIEnumerable() new { name = "Loan", department = "IT "} } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } @@ -581,9 +581,9 @@ public async Task TestIEnumerable() new { name = "Loan", department = "IT "} } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } @@ -606,9 +606,9 @@ public async Task TestIEnumerable() { ["employees"] = dt }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } } @@ -633,9 +633,9 @@ public async Task TestIEnumerableGrouped() new { name = "Loan", department = "IT" } } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } @@ -655,9 +655,9 @@ public async Task TestIEnumerableGrouped() new { name = "Loan", department = "IT "} } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } @@ -680,9 +680,9 @@ public async Task TestIEnumerableGrouped() { ["employees"] = dt }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } } @@ -707,9 +707,9 @@ public async Task TestIEnumerableConditional() new { name = "Loan", department = "IT "} } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } @@ -729,9 +729,9 @@ public async Task TestIEnumerableConditional() new { name = "Loan", department = "IT" } } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } @@ -754,9 +754,9 @@ public async Task TestIEnumerableConditional() { ["employees"] = dt }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B18", dimension); } } @@ -785,10 +785,10 @@ public async Task TemplateTest() new { name = "Keaton", department = "IT" } } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); { - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -807,12 +807,12 @@ public async Task TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString(), sheetName: "Sheet2").ToBlockingEnumerable().ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -831,7 +831,7 @@ public async Task TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -856,9 +856,9 @@ public async Task TemplateTest() new { name = "Keaton", department = "IT" } } }; - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value); - var rows = MiniExcel.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); + var rows = _excelImporter.QueryAsync(path.ToString()).ToBlockingEnumerable().ToList(); Assert.Equal("FooCompany", rows[0].A); Assert.Equal("Jack", rows[2].B); Assert.Equal("HR", rows[2].C); @@ -875,7 +875,7 @@ public async Task TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -898,7 +898,7 @@ await Assert.ThrowsAsync(async () => }; await cts.CancelAsync(); - await MiniExcel.SaveAsByTemplateAsync(path.ToString(), templatePath, value, cancellationToken: cts.Token); + await _excelTemplater.ApplyTemplateAsync(path.ToString(), templatePath, value, cancellationToken: cts.Token); }); } } \ No newline at end of file diff --git a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs similarity index 81% rename from tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs rename to tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs index cadd66be..47f7275e 100644 --- a/tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateTests.cs +++ b/tests/MiniExcel.Core.Tests/SaveByTemplate/MiniExcelTemplateTests.cs @@ -1,18 +1,16 @@ -using Dapper; -using DocumentFormat.OpenXml.Office2013.ExcelAc; -using MiniExcelLibs.Enums; -using MiniExcelLibs.Picture; -using MiniExcelLibs.Tests.Utils; -using OfficeOpenXml; +using MiniExcelLib.Core.Enums; +using MiniExcelLib.Core.OpenXml.Picture; +using MiniExcelLib.Tests.Common.Utils; using OfficeOpenXml.Drawing; -using System.Data; -using System.IO.Compression; -using Xunit; -namespace MiniExcelLibs.Tests.SaveByTemplate; +namespace MiniExcelLib.Tests.SaveByTemplate; public class MiniExcelTemplateTests { + private readonly OpenXmlImporter _excelImporter = MiniExcel.Importers.GetOpenXmlImporter(); + private readonly OpenXmlExporter _excelExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlTemplater _excelTemplater = MiniExcel.Templaters.GetOpenXmlTemplater(); + [Fact] public void TestImageType() { @@ -60,7 +58,7 @@ public void TestImageType() }; // Act - MiniExcel.AddPicture(path.ToString(), pictures); + _excelExporter.AddPicture(path.ToString(), pictures); // Assert using var zip = ZipFile.OpenRead(path.FilePath); @@ -95,7 +93,8 @@ public void TestImageType() } } } - [Fact] + + [Fact] public void DatatableTemptyRowTest() { const string templatePath = "../../../../../samples/xlsx/TestTemplateComplex.xlsx"; @@ -116,10 +115,10 @@ public void DatatableTemptyRowTest() ["managers"] = managers, ["employees"] = employees }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var rows = _excelImporter.Query(path.ToString()).ToList(); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C5", dimension); } { @@ -141,10 +140,10 @@ public void DatatableTemptyRowTest() ["managers"] = managers, ["employees"] = employees }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var rows = _excelImporter.Query(path.ToString()).ToList(); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C5", dimension); } } @@ -176,12 +175,12 @@ public void DatatableTest() ["managers"] = managers, ["employees"] = employees }; - MiniExcel.SaveAsByTemplate(path, templatePath, value); + _excelTemplater.ApplyTemplate(path, templatePath, value); { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); Assert.Equal(9, rows.Count); @@ -204,7 +203,7 @@ public void DatatableTest() } { - var rows = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -223,7 +222,7 @@ public void DatatableTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } } @@ -243,10 +242,10 @@ public void DapperTemplateTest() ["managers"] = connection.Query("select 'Jack' name,'HR' department union all select 'Loan','IT'"), ["employees"] = connection.Query("select 'Wade' name,'HR' department union all select 'Felix','HR' union all select 'Eric','IT' union all select 'Keaton','IT'") }; - MiniExcel.SaveAsByTemplate(path, templatePath, value); + _excelTemplater.ApplyTemplate(path, templatePath, value); { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -265,12 +264,12 @@ public void DapperTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -289,7 +288,7 @@ public void DapperTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } } @@ -318,10 +317,10 @@ public void DictionaryTemplateTest() new Dictionary { ["name"] = "Keaton", ["department"] = "IT" } } }; - MiniExcel.SaveAsByTemplate(path, templatePath, value); + _excelTemplater.ApplyTemplate(path, templatePath, value); { - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -340,12 +339,12 @@ public void DictionaryTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.Query(path, sheetName: "Sheet2").ToList(); + var rows = _excelImporter.Query(path, sheetName: "Sheet2").ToList(); Assert.Equal(9, rows.Count); Assert.Equal("FooCompany", rows[0].A); @@ -364,7 +363,7 @@ public void DictionaryTemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:C9", dimension); } } @@ -395,9 +394,9 @@ public void GroupTemplateTest() new() { name = "Felix", department = "HR" } } }; - MiniExcel.SaveAsByTemplate(path, templatePath, value); + _excelTemplater.ApplyTemplate(path, templatePath, value); - var rows = MiniExcel.Query(path).ToList(); + var rows = _excelImporter.Query(path).ToList(); Assert.Equal(16, rows.Count); Assert.Equal("Jack", rows[1].A); @@ -424,7 +423,7 @@ public void GroupTemplateTest() Assert.Equal("Felix", rows[12].A); Assert.Equal("HR", rows[12].B); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path); Assert.Equal("A1:B20", dimension); } @@ -464,13 +463,13 @@ public void TestGithubProject() Projects = projects, TotalStar = projects.Sum(s => s.Star) }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("ITWeiHan Github Projects", rows[0].B); Assert.Equal("Total Star : 178", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:D9", dimension); } @@ -489,7 +488,7 @@ private class TestIEnumerableTypePoco public void TestIEnumerableType() { { - const string templatePath = @"../../../../../samples/xlsx/TestIEnumerableType.xlsx"; + const string templatePath = "../../../../../samples/xlsx/TestIEnumerableType.xlsx"; using var path = AutoDeletingPath.Create(); //1. By POCO @@ -515,9 +514,9 @@ public void TestIEnumerableType() poco } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(poco.@string, rows[0].@string); Assert.Equal(poco.@int, rows[0].@int); Assert.Equal(poco.@double, rows[0].@double); @@ -560,7 +559,7 @@ public void TestIEnumerableType() Assert.Equal(poco.datetime, rows[4].datetime); Assert.Equal(poco.Guid, rows[4].Guid); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:G6", dimension); } } @@ -583,9 +582,9 @@ public void TestTemplateTypeMapping() @bool = true, Guid = Guid.NewGuid() }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(value.@string, rows[0].@string); Assert.Equal(value.@int, rows[0].@int); Assert.Equal(value.@double, rows[0].@double); @@ -594,7 +593,7 @@ public void TestTemplateTypeMapping() Assert.Equal(value.datetime, rows[0].datetime); Assert.Equal(value.Guid, rows[0].Guid); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:G2", dimension); } } @@ -608,7 +607,7 @@ public void TemplateCenterEmptyTest() { Tests = Enumerable.Range(1, 5).Select(i => new { test1 = i, test2 = i }) }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); } [Fact] @@ -626,16 +625,16 @@ public void TemplateBasicTest() VIP = true, Points = 123 }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } @@ -650,16 +649,16 @@ public void TemplateBasicTest() VIP = true, Points = 123 }; - MiniExcel.SaveAsByTemplate(path.ToString(), templateBytes, value); + _excelTemplater.ApplyTemplate(path.ToString(), templateBytes, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } @@ -677,17 +676,17 @@ public void TemplateBasicTest() }; using (var stream = File.Create(path.ToString())) { - stream.SaveAsByTemplate(templateBytes, value); + _excelTemplater.ApplyTemplate(stream, templateBytes, value); } - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } @@ -702,16 +701,16 @@ public void TemplateBasicTest() ["VIP"] = true, ["Points"] = 123 }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal("Jack", rows[1].A); Assert.Equal("2021-01-01 00:00:00", rows[1].B); Assert.Equal(true, rows[1].C); Assert.Equal(123, rows[1].D); Assert.Equal("Jack has 123 points", rows[1].E); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:E2", dimension); } } @@ -736,9 +735,9 @@ public void TestIEnumerable() new { name = "Loan", department = "IT" } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } @@ -758,9 +757,9 @@ public void TestIEnumerable() new { name = "Loan", department = "IT" } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } @@ -783,9 +782,9 @@ public void TestIEnumerable() { ["employees"] = dt }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:B7", dimension); } } @@ -808,9 +807,9 @@ public void TestIEnumerableWithFormulas() new { name = "Joan", department = "IT", salary = 120000 } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C13", dimension); } @@ -838,10 +837,10 @@ public void TemplateTest() new { name = "Keaton", department = "IT" } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); { - var rows = MiniExcel.Query(path.ToString()).ToList(); + var rows = _excelImporter.Query(path.ToString()).ToList(); Assert.Equal(9, rows.Count); @@ -861,12 +860,12 @@ public void TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } { - var rows = MiniExcel.Query(path.ToString(), sheetName: "Sheet2").ToList(); + var rows = _excelImporter.Query(path.ToString(), sheetName: "Sheet2").ToList(); Assert.Equal(9, rows.Count); @@ -886,7 +885,7 @@ public void TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -912,9 +911,9 @@ public void TemplateTest() new { name = "Keaton", department = "IT" } } }; - MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); + _excelTemplater.ApplyTemplate(path.ToString(), templatePath, value); - var rows = MiniExcel.Query(path.ToString()).ToList(); + 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); @@ -931,7 +930,7 @@ public void TemplateTest() Assert.Equal("Keaton", rows[8].B); Assert.Equal("IT", rows[8].C); - var dimension = Helpers.GetFirstSheetDimensionRefValue(path.ToString()); + var dimension = SheetHelper.GetFirstSheetDimensionRefValue(path.ToString()); Assert.Equal("A1:C9", dimension); } } @@ -942,8 +941,8 @@ public void MergeSameCellsWithTagTest() const string path = "../../../../../samples/xlsx/TestMergeWithTag.xlsx"; using var mergedFilePath = AutoDeletingPath.Create(); - MiniExcel.MergeSameCells(mergedFilePath.ToString(), path); - var mergedCells = Helpers.GetFirstSheetMergedCells(mergedFilePath.ToString()); + _excelTemplater.MergeSameCells(mergedFilePath.ToString(), path); + var mergedCells = SheetHelper.GetFirstSheetMergedCells(mergedFilePath.ToString()); Assert.Equal("A2:A4", mergedCells[0]); Assert.Equal("C3:C4", mergedCells[1]); @@ -956,8 +955,8 @@ public void MergeSameCellsWithLimitTagTest() const string path = "../../../../../samples/xlsx/TestMergeWithLimitTag.xlsx"; using var mergedFilePath = AutoDeletingPath.Create(); - MiniExcel.MergeSameCells(mergedFilePath.ToString(), path); - var mergedCells = Helpers.GetFirstSheetMergedCells(mergedFilePath.ToString()); + _excelTemplater.MergeSameCells(mergedFilePath.ToString(), path); + var mergedCells = SheetHelper.GetFirstSheetMergedCells(mergedFilePath.ToString()); Assert.Equal("A3:A4", mergedCells[0]); Assert.Equal("C3:C6", mergedCells[1]); diff --git a/tests/MiniExcelTests/Utils/EpplusLicense.cs b/tests/MiniExcel.Core.Tests/Utils/EpplusLicense.cs similarity index 68% rename from tests/MiniExcelTests/Utils/EpplusLicense.cs rename to tests/MiniExcel.Core.Tests/Utils/EpplusLicense.cs index fe0374e6..6b9f4c81 100644 --- a/tests/MiniExcelTests/Utils/EpplusLicense.cs +++ b/tests/MiniExcel.Core.Tests/Utils/EpplusLicense.cs @@ -1,6 +1,4 @@ -using OfficeOpenXml; - -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Utils; internal static class EpplusLicence { diff --git a/tests/MiniExcelTests/Utils/FileHelper.cs b/tests/MiniExcel.Core.Tests/Utils/FileHelper.cs similarity index 87% rename from tests/MiniExcelTests/Utils/FileHelper.cs rename to tests/MiniExcel.Core.Tests/Utils/FileHelper.cs index 32fcd9eb..b0a7b122 100644 --- a/tests/MiniExcelTests/Utils/FileHelper.cs +++ b/tests/MiniExcel.Core.Tests/Utils/FileHelper.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Utils; public static class FileHelper { diff --git a/tests/MiniExcelTests/Utils/Helpers.cs b/tests/MiniExcel.Core.Tests/Utils/SheetHelper.cs similarity index 80% rename from tests/MiniExcelTests/Utils/Helpers.cs rename to tests/MiniExcel.Core.Tests/Utils/SheetHelper.cs index 40695ac7..740179ad 100644 --- a/tests/MiniExcelTests/Utils/Helpers.cs +++ b/tests/MiniExcel.Core.Tests/Utils/SheetHelper.cs @@ -1,12 +1,10 @@ -using System.IO.Compression; -using System.Text; -using System.Xml; +using System.Xml; using System.Xml.Linq; using System.Xml.XPath; -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Utils; -internal static class Helpers +internal static class SheetHelper { private const int GeneralColumnIndex = 255; private const int MaxColumnIndex = 16383; @@ -18,7 +16,6 @@ internal static class Helpers private static readonly Dictionary AlphabetMappingInt = Enumerable .Range(0, MaxColumnIndex) .ToDictionary(IntToLetters, i => i); - public static string GetAlphabetColumnName(int columnIndex) { @@ -83,9 +80,9 @@ internal static string GetZipFileContent(string zipPath, string filePath) using var stream = File.OpenRead(path); using var archive = new ZipArchive(stream, ZipArchiveMode.Read, false, Encoding.UTF8); - var sheet = archive.Entries - .Single(w => w.FullName.StartsWith("xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase) || - w.FullName.StartsWith("/xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase)); + var sheet = archive.Entries.Single(w => + w.FullName.StartsWith("xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase) || + w.FullName.StartsWith("/xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase)); using var sheetStream = sheet.Open(); var doc = XDocument.Load(sheetStream); @@ -103,14 +100,14 @@ internal static Dictionary GetFirstSheetMergedCells(string path) using var stream = File.OpenRead(path); using var archive = new ZipArchive(stream, ZipArchiveMode.Read, false, Encoding.UTF8); - var sheet = archive.Entries - .Single(w => w.FullName.StartsWith("xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase) || - w.FullName.StartsWith("/xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase)); + var sheet = archive.Entries.Single(w => + w.FullName.StartsWith("xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase) || + w.FullName.StartsWith("/xl/worksheets/sheet1", StringComparison.OrdinalIgnoreCase)); using var sheetStream = sheet.Open(); var doc = new XmlDocument(); doc.Load(sheetStream); - var mergeCells = doc.SelectSingleNode($"/x:worksheet/x:mergeCells", ns)?.Cast().ToList(); + var mergeCells = doc.SelectSingleNode("/x:worksheet/x:mergeCells", ns)?.Cast().ToList(); if (mergeCells is { Count: > 0 }) { diff --git a/tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs b/tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs new file mode 100644 index 00000000..258654bc --- /dev/null +++ b/tests/MiniExcel.Csv.Tests/AsyncIssueTests.cs @@ -0,0 +1,397 @@ +namespace MiniExcelLib.Csv.Tests; + +public class AsyncIssueTests +{ + private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); + private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); + + private readonly OpenXmlExporter _openXmlExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlImporter _openXmlImporter = MiniExcel.Importers.GetOpenXmlImporter(); + /// + /// 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.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + const string expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 + + """"; + + Assert.Equal(expected, await File.ReadAllTextAsync(path.ToString())); + } + + private class Issue89VO + { + public WorkState State { get; set; } + + public enum WorkState + { + OnDuty, + Leave, + Fired + } + } + + /// + /// Support Enum Mapping + /// https://github.com/mini-software/MiniExcel/issues/89 + /// + [Fact] + public async Task Issue89() + { + { + const string text = + """ + State + OnDuty + Fired + Leave + """; + await using var stream = new MemoryStream(); + await using var writer = new StreamWriter(stream); + + await writer.WriteAsync(text); + await writer.FlushAsync(); + + stream.Position = 0; + var q = _csvImporter.QueryAsync(stream).ToBlockingEnumerable(); + var rows = q.ToList(); + + Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); + Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); + + var outputPath = PathHelper.GetTempPath("xlsx"); + var rowsWritten = await _openXmlExporter.ExportAsync(outputPath, rows); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); + + var q2 = _openXmlImporter.QueryAsync(outputPath).ToBlockingEnumerable(); + var rows2 = q2.ToList(); + Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); + Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); + } + + //xlsx + { + var path = PathHelper.GetFile("xlsx/TestIssue89.xlsx"); + var q = _openXmlImporter.QueryAsync(path).ToBlockingEnumerable(); + var rows = q.ToList(); + Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); + Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); + + var outputPath = PathHelper.GetTempPath(); + var rowsWritten = await _openXmlExporter.ExportAsync(outputPath, rows); + Assert.Single(rowsWritten); + Assert.Equal(3, rowsWritten[0]); + + var q1 = _openXmlImporter.QueryAsync(outputPath).ToBlockingEnumerable(); + var rows2 = q1.ToList(); + Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); + Assert.Equal(Issue89VO.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() + { + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + await _openXmlExporter.ExportAsync(path, new[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); + + { + var q = _openXmlImporter.QueryAsync(path).ToBlockingEnumerable(); + var rows = q.ToList(); + Assert.Equal("MyProperty4", rows[0].A); + Assert.Equal("CustomColumnName", rows[0].B); //note + Assert.Equal("MyProperty5", rows[0].C); + Assert.Equal("MyProperty2", rows[0].D); + Assert.Equal("MyProperty6", rows[0].E); + Assert.Equal(null, rows[0].F); + Assert.Equal("MyProperty3", rows[0].G); + + Assert.Equal("MyProperty4", rows[0].A); + Assert.Equal("CustomColumnName", rows[0].B); //note + Assert.Equal("MyProperty5", rows[0].C); + Assert.Equal("MyProperty2", rows[0].D); + Assert.Equal("MyProperty6", rows[0].E); + Assert.Equal(null, rows[0].F); + Assert.Equal("MyProperty3", rows[0].G); + } + + { + var q = _openXmlImporter.QueryAsync(path).ToBlockingEnumerable(); + var rows = q.ToList(); + + Assert.Equal("MyProperty4", rows[0].MyProperty4); + Assert.Equal("MyProperty1", rows[0].MyProperty1); //note + 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); + } + } + + { + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + await _csvExporter.ExportAsync(path, new[] { new Issue142VO { MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", MyProperty7 = "MyProperty7" } }); + const string expected = + """ + MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + + """; + Assert.Equal(expected, await File.ReadAllTextAsync(path)); + + { + var q = _csvImporter.QueryAsync(path).ToBlockingEnumerable(); + var rows = q.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); + } + } + + { + using var path = AutoDeletingPath.Create(); + Issue142VoDuplicateColumnName[] input = [new() { MyProperty1 = 0, MyProperty2 = 0, MyProperty3 = 0, MyProperty4 = 0 }]; + Assert.Throws(() => _openXmlExporter.Export(path.ToString(), input)); + } + } + + /// + /// 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(ExcelType.Csv); + await _csvExporter.ExportAsync(path.ToString(), table); + + var q = _csvImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); + var rows = q.ToList(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); + } + + + /// + /// Csv QueryAsync split comma not correct #237 + /// https://github.com/mini-software/MiniExcel/issues/237 + /// + [Fact] + public async Task Issue237() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + var value = new[] + { + new{ id = "\"\"1,2,3\"\"" }, + new{ id = "1,2,3" } + }; + await _csvExporter.ExportAsync(path.ToString(), value); + + var q = _csvImporter.QueryAsync(path.ToString(), true).ToBlockingEnumerable(); + var rows = q.ToList(); + + Assert.Equal("\"\"1,2,3\"\"", rows[0].id); + Assert.Equal("1,2,3", rows[1].id); + } + + + /// + /// Support Custom Datetime format #241 + /// + [Fact] + public async Task Issue241() + { + Issue241Dto[] value = + [ + new() { Name = "Jack", InDate = new DateTime(2021, 01, 04) }, + new() { Name = "Henry", InDate = new DateTime(2020, 04, 05) } + ]; + + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + var rowsWritten = await _csvExporter.ExportAsync(path, value); + + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + var q1 = _csvImporter.QueryAsync(path, true).ToBlockingEnumerable(); + var rows1 = q1.ToList(); + Assert.Equal(rows1[0].InDate, "01 04, 2021"); + Assert.Equal(rows1[1].InDate, "04 05, 2020"); + + var q2 = _csvImporter.QueryAsync(path).ToBlockingEnumerable(); + var rows2 = q2.ToList(); + Assert.Equal(rows2[0].InDate, new DateTime(2021, 01, 04)); + Assert.Equal(rows2[1].InDate, new DateTime(2020, 04, 05)); + } + + + /// + /// Csv type mapping QueryAsync error "cannot be converted to xxx type" #243 + /// + [Fact] + public async Task Issue243() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + 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 rowsWritten = await _csvExporter.ExportAsync(path.ToString(), value); + Assert.Single(rowsWritten); + Assert.Equal(2, rowsWritten[0]); + + var q = _csvImporter.QueryAsync(path.ToString()).ToBlockingEnumerable(); + var rows = q.ToList(); + + Assert.Equal("Jack", rows[0].Name); + Assert.Equal(25, rows[0].Age); + Assert.Equal(new DateTime(2021, 01, 03), rows[0].InDate); + + Assert.Equal("Henry", rows[1].Name); + Assert.Equal(36, rows[1].Age); + Assert.Equal(new DateTime(2020, 05, 03), rows[1].InDate); + } + + #region Duplicated + private class Issue142VO + { + [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 + { + public string Name { get; set; } + + [MiniExcelFormat("MM dd, yyyy")] + public DateTime InDate { get; set; } + } + + private class Issue243Dto + { + public string Name { get; set; } + public int Age { get; set; } + public DateTime InDate { get; set; } + } + #endregion + +} diff --git a/tests/MiniExcel.Csv.Tests/IssueTests.cs b/tests/MiniExcel.Csv.Tests/IssueTests.cs new file mode 100644 index 00000000..4df8c500 --- /dev/null +++ b/tests/MiniExcel.Csv.Tests/IssueTests.cs @@ -0,0 +1,1011 @@ +namespace MiniExcelLib.Csv.Tests; + +public class IssueTests +{ + private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); + private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); + + private readonly OpenXmlExporter _openXmlExporter = MiniExcel.Exporters.GetOpenXmlExporter(); + private readonly OpenXmlImporter _openXmlImporter = MiniExcel.Importers.GetOpenXmlImporter(); + + [Fact] + public void TestPR10() + { + 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(); + } + + /// + /// https://gitee.com/dotnetchina/MiniExcel/issues/I49RZH + /// https://github.com/mini-software/MiniExcel/issues/305 + /// + [Fact] + public void TestIssueI49RZH() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + var value = new[] + { + new TestIssueI49RZHDto{ dd = DateTimeOffset.Parse("2022-01-22")}, + new TestIssueI49RZHDto{ dd = null} + }; + _csvExporter.Export(path.ToString(), value); + + var rows = _csvImporter.Query(path.ToString()).ToList(); + Assert.Equal("2022-01-22", rows[1].A); + } + + private class TestIssueI49RZHDto + { + [MiniExcelFormat("yyyy-MM-dd")] + public DateTimeOffset? dd { get; set; } + } + + /// + /// 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); + } + } + + private class TestIssue316Dto + { + public decimal Amount { get; set; } + public DateTime CreateTime { get; set; } + } + + /// + /// Using stream.SaveAs will close the Stream automatically when Specifying excelType + /// https://gitee.com/dotnetchina/MiniExcel/issues/I57WMM + /// + [Fact] + public void TestIssueI57WMM() + { + 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); + } + + [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(ExcelType.Csv); + _csvExporter.Export(path.ToString(), table); + + var rows = _csvImporter.Query(path.ToString()).ToList(); + Assert.Equal("Name", rows[0].B); + Assert.Equal("Limit", rows[0].C); + } + + /// + /// Csv Query split comma not correct #237 + /// https://github.com/mini-software/MiniExcel/issues/237 + /// + [Fact] + public void Issue237() + { + var value = new[] + { + new{ id = "\"\"1,2,3\"\"" }, + new{ id = "1,2,3" } + }; + + using var path = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.Export(path.ToString(), value); + + var rows = _csvImporter.Query(path.ToString(), true).ToList(); + + Assert.Equal("\"\"1,2,3\"\"", rows[0].id); + Assert.Equal("1,2,3", rows[1].id); + } + + /// + /// Support Custom Datetime format #241 + /// + [Fact] + public void Issue241() + { + Issue241Dto[] value = + [ + new() { Name = "Jack", InDate = new DateTime(2021, 01, 04) }, + new() { Name = "Henry", InDate = new DateTime(2020, 04, 05) } + ]; + + using var path = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.Export(path.ToString(), value); + + var rows1 = _csvImporter.Query(path.ToString(), true).ToList(); + Assert.Equal(rows1[0].InDate, "01 04, 2021"); + Assert.Equal(rows1[1].InDate, "04 05, 2020"); + + var rows2 = _csvImporter.Query(path.ToString()).ToList(); + 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 + /// + [Fact] + public void Issue243() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + 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) }, + }; + _csvExporter.Export(path.ToString(), value); + + var rows = _csvImporter.Query(path.ToString()).ToList(); + Assert.Equal("Jack", rows[0].Name); + Assert.Equal(25, rows[0].Age); + Assert.Equal(new DateTime(2021, 01, 03), rows[0].InDate); + + Assert.Equal("Henry", rows[1].Name); + Assert.Equal(36, rows[1].Age); + 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 + /// + [Fact] + public void TestIssue312() + { + using var path = AutoDeletingPath.Create(ExcelType.Csv); + TestIssue312Dto[] value = + [ + new() { Value = 12345.6789}, + new() { Value = null} + ]; + _csvExporter.Export(path.ToString(), value); + + 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; } + } + + [Fact] + public async Task TestIssue338() + { + 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 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())); + } + + [Fact] + public void TestIssue316() + { + // XLSX + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + var value = new[] + { + new{ Amount=123_456.789M, CreateTime=DateTime.Parse("2018-01-31",CultureInfo.InvariantCulture)} + }; + var config = new OpenXmlConfiguration + { + Culture = new CultureInfo("fr-FR"), + }; + _openXmlExporter.Export(path, value, configuration: config); + + //Datetime error + Assert.Throws(() => + { + var conf = new OpenXmlConfiguration + { + Culture = new CultureInfo("en-US"), + }; + _ = _openXmlImporter.Query(path, configuration: conf).ToList(); + }); + + // dynamic + var rows = _openXmlImporter.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(); + var path = file.ToString(); + var value = new[] + { + new { Amount = 123_456.789M, CreateTime = new DateTime(2018, 5, 12) } + }; + { + var config = new OpenXmlConfiguration + { + Culture = new CultureInfo("fr-FR"), + }; + _openXmlExporter.Export(path, value, configuration: config); + } + + { + var rows = _openXmlImporter.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 OpenXmlConfiguration + { + Culture = new CultureInfo("en-US"), + }; + var rows = _openXmlImporter.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 OpenXmlConfiguration + { + Culture = new CultureInfo("fr-FR"), + }; + var rows = _openXmlImporter.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); + } + } + + // 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); + } + } + } + + /// + /// Column '' does not belong to table when csv convert to datatable #298 + /// https://github.com/mini-software/MiniExcel/issues/298 + /// + [Fact] + public void TestIssue298() + { + var path = PathHelper.GetFile("/csv/TestIssue298.csv"); +#pragma warning disable CS0618 // Type or member is obsolete + var dt = _csvImporter.QueryAsDataTable(path); +#pragma warning restore CS0618 + Assert.Equal(["ID", "Name", "Age"], dt.Columns.Cast().Select(x => x.ColumnName)); + } + /// + /// [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) + /// + [Fact] + public void TestIssue292() + { + { + var xlsxPath = PathHelper.GetFile("/xlsx/TestIssue292.xlsx"); + using var csvPath = AutoDeletingPath.Create(ExcelType.Csv); + _csvExporter.ConvertXlsxToCsv(xlsxPath, csvPath.ToString(), false); + + var actualCotent = File.ReadAllText(csvPath.ToString()); + Assert.Equal( + """ + Name,Age,Name,Age + Jack,22,Mike,25 + Henry,44,Jerry,44 + + """, + actualCotent); + } + + { + var csvPath = PathHelper.GetFile("/csv/TestIssue292.csv"); + using var path = AutoDeletingPath.Create(); + _csvExporter.ConvertCsvToXlsx(csvPath, path.ToString()); + + var rows = _openXmlImporter.Query(path.ToString()).ToList(); + Assert.Equal(3, rows.Count); + Assert.Equal("Name", rows[0].A); + Assert.Equal("Age", rows[0].B); + Assert.Equal("Name", rows[0].C); + Assert.Equal("Age", rows[0].D); + Assert.Equal("Jack", rows[1].A); + Assert.Equal("22", rows[1].B); + Assert.Equal("Mike", rows[1].C); + Assert.Equal("25", rows[1].D); + } + } + + /// + /// [Csv Query then SaveAs will throw "Stream was not readable." exception · Issue #293 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/293) + /// + [Fact] + public void TestIssue293() + { + var path = PathHelper.GetFile("/csv/Test5x2.csv"); + using var tempPath = AutoDeletingPath.Create(); + using var csv = File.OpenRead(path); + var value = _csvImporter.Query(csv, useHeaderRow: false); + _openXmlExporter.Export(tempPath.ToString(), value, printHeader: false); + } + + /// + /// Csv not support QueryAsDataTable #279 https://github.com/mini-software/MiniExcel/issues/279 + /// + [Fact] + public void TestIssue279() + { + var path = PathHelper.GetFile("/csv/TestHeader.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(); + + _csvExporter.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())); + } + } + + /// + /// [CSV SaveAs support datareader · Issue #251 · mini-software/MiniExcel](https://github.com/mini-software/MiniExcel/issues/251) + /// + [Fact] + 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); + _csvExporter.Export(path.ToString(), reader); + const string expected = + """" + a,b + """<>+-*//}{\\n",1234567890 + "Hello World",-1234567890 + + """"; + + Assert.Equal(expected, File.ReadAllText(path.ToString())); + } + + public class Issue89VO + { + public WorkState State { get; set; } + + public enum WorkState + { + OnDuty, + Leave, + Fired + } + } + + /// + /// Support Enum Mapping + /// https://github.com/mini-software/MiniExcel/issues/89 + /// + [Fact] + public void Issue89() + { + //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, useHeaderRow: true).ToList(); + + Assert.Equal(nameof(Issue89VO.WorkState.OnDuty), rows[0].State); + Assert.Equal(nameof(Issue89VO.WorkState.Fired), rows[1].State); + Assert.Equal(nameof(Issue89VO.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(Issue89VO.WorkState.OnDuty, rows2[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); + Assert.Equal(Issue89VO.WorkState.Leave, rows2[2].State); + } + + //xlsx + { + var path = PathHelper.GetFile("xlsx/TestIssue89.xlsx"); + var rows = _openXmlImporter.Query(path).ToList(); + + Assert.Equal(Issue89VO.WorkState.OnDuty, rows[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows[1].State); + Assert.Equal(Issue89VO.WorkState.Leave, rows[2].State); + + using var xlsxPath = AutoDeletingPath.Create(); + _openXmlExporter.Export(xlsxPath.ToString(), rows); + var rows2 = _openXmlImporter.Query(xlsxPath.ToString()).ToList(); + + Assert.Equal(Issue89VO.WorkState.OnDuty, rows2[0].State); + Assert.Equal(Issue89VO.WorkState.Fired, rows2[1].State); + Assert.Equal(Issue89VO.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; } + } + + private class Issue142VO + { + [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 + } + + [Fact] + public void Issue142() + { + { + using var file = AutoDeletingPath.Create(); + var path = file.ToString(); + Issue142VO[] 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 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.Equal(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.Equal(null, rows[0].F); + Assert.Equal("MyProperty3", rows[0].G); + } + + { + 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); + } + } + + { + using var file = AutoDeletingPath.Create(ExcelType.Csv); + var path = file.ToString(); + Issue142VO[] values = + [ + new() + { + MyProperty1 = "MyProperty1", MyProperty2 = "MyProperty2", MyProperty3 = "MyProperty3", + MyProperty4 = "MyProperty4", MyProperty5 = "MyProperty5", MyProperty6 = "MyProperty6", + MyProperty7 = "MyProperty7" + } + ]; + var rowsWritten = _csvExporter.Export(path, values); + Assert.Single(rowsWritten); + Assert.Equal(1, rowsWritten[0]); + + const string expected = + """ + MyProperty4,CustomColumnName,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3 + + """; + + Assert.Equal(expected, File.ReadAllText(path)); + + { + var rows = _csvImporter.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); + } + } + + { + 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() + { + const string path = "../../../../../samples/xlsx/TestIssue142.xlsx"; + const string csvPath = "../../../../../samples/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 string A { get; set; } + public DateTime B { get; set; } + public string C { get; set; } + public int D { get; set; } + } + + + [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 values = new Issue507V01[] + { + new() { A = "Github", B = DateTime.Parse("2021-01-01"), C = "abcd", D = 123 }, + new() { A = "Microsoft \nTest 1", B = DateTime.Parse("2021-02-01"), C = "efgh", D = 123 }, + new() { A = "Microsoft \rTest 2", B = DateTime.Parse("2021-02-01"), C = "ab\nc\nd", D = 123 }, + new() { A = "Microsoft\"\" \r\nTest\n3", B = DateTime.Parse("2021-02-01"), C = "a\"\"\nb\n\nc", D = 123 }, + }; + + var config = new CsvConfiguration + { + //AlwaysQuote = true, + ReadLineBreaksWithinQuotes = true, + }; + + // create + using (var stream = File.Create(path)) + { + _csvExporter.Export(stream, values, configuration: config); + } + + // read + var getRowsInfo = _csvImporter.Query(path, configuration: config).ToArray(); + + Assert.Equal(values.Length, getRowsInfo.Length); + + Assert.Equal("Github", getRowsInfo[0].A); + Assert.Equal("abcd", getRowsInfo[0].C); + + Assert.Equal($"Microsoft {config.NewLine}Test 1", getRowsInfo[1].A); + Assert.Equal("efgh", getRowsInfo[1].C); + + Assert.Equal($"Microsoft {config.NewLine}Test 2", getRowsInfo[2].A); + Assert.Equal($"ab{config.NewLine}c{config.NewLine}d", getRowsInfo[2].C); + + Assert.Equal($"""Microsoft"" {config.NewLine}Test{config.NewLine}3""", getRowsInfo[3].A); + Assert.Equal($"""a""{config.NewLine}b{config.NewLine}{config.NewLine}c""", getRowsInfo[3].C); + + 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 values = new Issue507V02[] + { + new() { B = DateTime.Parse("2021-01-01"), D = 123 }, + new() { B = DateTime.Parse("2021-02-01"), D = 123 }, + new() { B = DateTime.Parse("2021-02-01"), D = 123 }, + new() { B = DateTime.Parse("2021-02-01"), D = 123 }, + }; + + var config = new CsvConfiguration + { + //AlwaysQuote = true, + ReadLineBreaksWithinQuotes = true, + }; + + // create + using (var stream = File.Create(path)) + { + _csvExporter.Export(stream, values, true, config); + } + + // read + var getRowsInfo = _csvImporter.Query(path, configuration: config).ToArray(); + Assert.Equal(values.Length, getRowsInfo.Length); + + File.Delete(path); + } + + [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, + ReadLineBreaksWithinQuotes = true, + }; + + // create + using var stream = new MemoryStream(Encoding.UTF8.GetBytes("A,B,C\n\"r1a: no end quote,r1b,r1c")); + + // read + var getRowsInfo = _csvImporter.Query(stream, configuration: config).ToArray(); + Assert.Equal(2, getRowsInfo.Length); + } + +} diff --git a/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj new file mode 100644 index 00000000..84e3963f --- /dev/null +++ b/tests/MiniExcel.Csv.Tests/MiniExcel.Csv.Tests.csproj @@ -0,0 +1,45 @@ + + + + net8.0 + enable + enable + $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA1849;CA2000;CA2007;CA2208 + + false + true + true + ..\..\src\miniexcel.snk + MiniExcelLib.Csv.Tests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs b/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs similarity index 75% rename from tests/MiniExcelTests/MiniExcelCsvAsycTests.cs rename to tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs index 79aff1b2..0231160a 100644 --- a/tests/MiniExcelTests/MiniExcelCsvAsycTests.cs +++ b/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs @@ -1,24 +1,20 @@ -using CsvHelper; -using MiniExcelLibs.Tests.Utils; -using System.Data; -using System.Globalization; -using System.Text; -using Xunit; - -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Csv.Tests; public class MiniExcelCsvAsycTests { + private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); + private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); + [Fact] public async Task Gb2312_Encoding_Read_Test() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var path = PathHelper.GetFile("csv/gb2312_Encoding_Read_Test.csv"); - var config = new Csv.CsvConfiguration + var config = new CsvConfiguration { StreamReaderFunc = stream => new StreamReader(stream, encoding: Encoding.GetEncoding("gb2312")) }; - var q = MiniExcel.QueryAsync(path, true, excelType: ExcelType.CSV, configuration: config).ToBlockingEnumerable(); + var q = _csvImporter.QueryAsync(path, true, configuration: config).ToBlockingEnumerable(); var rows = q.ToList(); Assert.Equal("世界你好", rows[0].栏位1); } @@ -26,7 +22,7 @@ public async Task Gb2312_Encoding_Read_Test() [Fact] public async Task SeperatorTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -48,7 +44,7 @@ public async Task SeperatorTest() } ]; - var rowsWritten = await MiniExcel.SaveAsAsync(path, values, configuration: new Csv.CsvConfiguration { Seperator = ';' }); + var rowsWritten = await _csvExporter.ExportAsync(path, values, configuration: new CsvConfiguration { Seperator = ';' }); Assert.Equal(2, rowsWritten[0]); const string expected = @@ -66,24 +62,24 @@ public async Task SeperatorTest() public async Task SaveAsByDictionary() { { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); var table = new List>(); - await MiniExcel.SaveAsAsync(path, table); + await _csvExporter.ExportAsync(path, table); Assert.Equal("\r\n", await File.ReadAllTextAsync(path)); } { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); var table = new Dictionary(); //TODO - Assert.Throws(() => MiniExcel.SaveAs(path, table)); + Assert.Throws(() => _csvExporter.Export(path, table)); } { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -103,11 +99,11 @@ public async Task SaveAsByDictionary() { "d", new DateTime(2021, 1, 2) } } ]; - var rowsWritten = await MiniExcel.SaveAsAsync(path, values); + var rowsWritten = await _csvExporter.ExportAsync(path, values); Assert.Equal(2, rowsWritten[0]); using var reader = new StreamReader(path); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); @@ -122,7 +118,7 @@ public async Task SaveAsByDictionary() } { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -144,11 +140,11 @@ public async Task SaveAsByDictionary() } ]; - var rowsWritten = await MiniExcel.SaveAsAsync(path, values); + var rowsWritten = await _csvExporter.ExportAsync(path, values); Assert.Equal(2, rowsWritten[0]); using var reader = new StreamReader(path); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); var row1 = records[0] as IDictionary; @@ -168,17 +164,17 @@ public async Task SaveAsByDictionary() [Fact] public async Task SaveAsByDataTableTest() { - using var file1 = AutoDeletingPath.Create(ExcelType.CSV); + using var file1 = AutoDeletingPath.Create(ExcelType.Csv); var path1 = file1.ToString(); var emptyTable = new DataTable(); - await MiniExcel.SaveAsAsync(path1, emptyTable); + await _csvExporter.ExportAsync(path1, emptyTable); var text = await File.ReadAllTextAsync(path1); Assert.Equal("\r\n", text); - using var file2= AutoDeletingPath.Create(ExcelType.CSV); + using var file2= AutoDeletingPath.Create(ExcelType.Csv); var path2 = file2.ToString(); var table = new DataTable(); @@ -189,11 +185,11 @@ public async Task SaveAsByDataTableTest() table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, new DateTime(2021, 1, 1)); table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2)); - var rowsWritten = await MiniExcel.SaveAsAsync(path2, table); + var rowsWritten = await _csvExporter.ExportAsync(path2, table); Assert.Equal(2, rowsWritten[0]); using var reader = new StreamReader(path2); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); @@ -217,17 +213,17 @@ private class Test [Fact] public async Task CsvExcelTypeTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); var input = new[] { new { A = "Test1", B = "Test2" } }; - await MiniExcel.SaveAsAsync(path, input); + await _csvExporter.ExportAsync(path, input); var texts = await File.ReadAllLinesAsync(path); Assert.Equal("A,B", texts[0]); Assert.Equal("Test1,Test2", texts[1]); - var q = MiniExcel.QueryAsync(path).ToBlockingEnumerable(); + var q = _csvImporter.QueryAsync(path).ToBlockingEnumerable(); var rows1 = q.ToList(); Assert.Equal("A", rows1[0].A); @@ -236,7 +232,7 @@ public async Task CsvExcelTypeTest() Assert.Equal("Test2", rows1[1].B); using var reader = new StreamReader(path); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var rows2 = csv.GetRecords().ToList(); Assert.Equal("Test1", rows2[0].A); @@ -246,10 +242,10 @@ public async Task CsvExcelTypeTest() [Fact] public async Task Create2x2_Test() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new[] + await _csvExporter.ExportAsync(path, new[] { new { c1 = "A1", c2 = "B1"}, new { c1 = "A2", c2 = "B2"}, @@ -257,7 +253,7 @@ await MiniExcel.SaveAsAsync(path, new[] await using (var stream = File.OpenRead(path)) { - var rows = stream.QueryAsync(useHeaderRow: true, excelType: ExcelType.CSV).ToBlockingEnumerable().ToList(); + var rows = _csvImporter.QueryAsync(stream, useHeaderRow: true).ToBlockingEnumerable().ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -265,7 +261,7 @@ await MiniExcel.SaveAsAsync(path, new[] } { - var rows = MiniExcel.QueryAsync(path, useHeaderRow: true, excelType: ExcelType.CSV).ToBlockingEnumerable().ToList(); + var rows = _csvImporter.QueryAsync(path, useHeaderRow: true).ToBlockingEnumerable().ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -276,10 +272,10 @@ await MiniExcel.SaveAsAsync(path, new[] [Fact] public async Task CsvTypeMappingTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new[] + await _csvExporter.ExportAsync(path, new[] { new { c1 = "A1", c2 = "B1"}, new { c1 = "A2", c2 = "B2"} @@ -287,7 +283,7 @@ await MiniExcel.SaveAsAsync(path, new[] await using (var stream = File.OpenRead(path)) { - var rows = stream.Query(excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(stream).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -295,7 +291,7 @@ await MiniExcel.SaveAsAsync(path, new[] } { - var rows = MiniExcel.Query(path, excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(path).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -306,10 +302,10 @@ await MiniExcel.SaveAsAsync(path, new[] [Fact] public async Task CsvReadEmptyStringAsNullTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - await MiniExcel.SaveAsAsync(path, new[] + await _csvExporter.ExportAsync(path, new[] { new { c1 = (string?)"A1", c2 = (string?)null}, new { c1 = (string?)null, c2 = (string?)null} @@ -317,7 +313,7 @@ await MiniExcel.SaveAsAsync(path, new[] await using (var stream = File.OpenRead(path)) { - var rows = stream.Query(excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(stream).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal(string.Empty, rows[0].c2); Assert.Equal(string.Empty, rows[1].c1); @@ -325,17 +321,17 @@ await MiniExcel.SaveAsAsync(path, new[] } { - var rows = MiniExcel.Query(path, excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(path).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal(string.Empty, rows[0].c2); Assert.Equal(string.Empty, rows[1].c1); Assert.Equal(string.Empty, rows[1].c2); } - var config = new Csv.CsvConfiguration { ReadEmptyStringAsNull = true }; + var config = new CsvConfiguration { ReadEmptyStringAsNull = true }; await using (var stream = File.OpenRead(path)) { - var rows = stream.Query(excelType: ExcelType.CSV, configuration: config).ToList(); + var rows = _csvImporter.Query(stream, configuration: config).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Null(rows[0].c2); Assert.Null(rows[1].c1); @@ -343,7 +339,7 @@ await MiniExcel.SaveAsAsync(path, new[] } { - var rows = MiniExcel.Query(path, excelType: ExcelType.CSV, configuration: config).ToList(); + var rows = _csvImporter.Query(path, configuration: config).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Null(rows[0].c2); Assert.Null(rows[1].c1); @@ -365,10 +361,10 @@ static async IAsyncEnumerable GetValues() } #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously - var rowsWritten = await MiniExcel.SaveAsAsync(path, GetValues()); + var rowsWritten = await _csvExporter.ExportAsync(path, GetValues()); Assert.Equal(2, rowsWritten[0]); - var results = MiniExcel.Query(path).ToList(); + var results = _csvImporter.Query(path).ToList(); Assert.Equal(2, results.Count); Assert.Equal("A1", results[0].c1); Assert.Equal("B1", results[0].c2); diff --git a/tests/MiniExcelTests/MiniExcelCsvTests.cs b/tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs similarity index 69% rename from tests/MiniExcelTests/MiniExcelCsvTests.cs rename to tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs index ad1b8e70..4fe12e0b 100644 --- a/tests/MiniExcelTests/MiniExcelCsvTests.cs +++ b/tests/MiniExcel.Csv.Tests/MiniExcelCsvTests.cs @@ -1,33 +1,27 @@ -using CsvHelper; -using MiniExcelLibs.Attributes; -using MiniExcelLibs.Exceptions; -using MiniExcelLibs.Tests.Utils; -using System.Data; -using System.Globalization; -using System.Text; -using Xunit; - -namespace MiniExcelLibs.Tests; +namespace MiniExcelLib.Csv.Tests; public class MiniExcelCsvTests { + private readonly CsvExporter _csvExporter = MiniExcel.Exporters.GetCsvExporter(); + private readonly CsvImporter _csvImporter = MiniExcel.Importers.GetCsvImporter(); + [Fact] public void gb2312_Encoding_Read_Test() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var path = PathHelper.GetFile("csv/gb2312_Encoding_Read_Test.csv"); - var config = new Csv.CsvConfiguration + var config = new CsvConfiguration { StreamReaderFunc = stream => new StreamReader(stream, encoding: Encoding.GetEncoding("gb2312")) }; - var rows = MiniExcel.Query(path, true, excelType: ExcelType.CSV, configuration: config).ToList(); + var rows = _csvImporter.Query(path, true, configuration: config).ToList(); Assert.Equal("世界你好", rows[0].栏位1); } [Fact] public void SeperatorTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -48,7 +42,7 @@ public void SeperatorTest() { "d", new DateTime(2021, 1, 2) } } ]; - var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration { Seperator = ';' }); + var rowsWritten = _csvExporter.Export(path, values, configuration: new CsvConfiguration { Seperator = ';' }); Assert.Equal(2, rowsWritten[0]); const string expected = @@ -64,7 +58,7 @@ public void SeperatorTest() [Fact] public void DontQuoteWhitespacesTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -85,7 +79,7 @@ public void DontQuoteWhitespacesTest() { "d", new DateTime(2021, 1, 2) } } ]; - var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration { QuoteWhitespaces = false }); + var rowsWritten = _csvExporter.Export(path, values, configuration: new CsvConfiguration { QuoteWhitespaces = false }); Assert.Equal(2, rowsWritten[0]); const string expected = @@ -101,7 +95,7 @@ public void DontQuoteWhitespacesTest() [Fact] public void AlwaysQuoteTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -122,7 +116,7 @@ public void AlwaysQuoteTest() } ]; - MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration { AlwaysQuote = true }); + _csvExporter.Export(path, values, configuration: new CsvConfiguration { AlwaysQuote = true }); const string expected = """" "a","b","c","d" @@ -136,7 +130,7 @@ public void AlwaysQuoteTest() [Fact] public void QuoteSpecialCharacters() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); List> values = @@ -150,7 +144,7 @@ public void QuoteSpecialCharacters() } ]; - var rowsWritten = MiniExcel.SaveAs(path, values, configuration: new Csv.CsvConfiguration()); + var rowsWritten = _csvExporter.Export(path, values, configuration: new CsvConfiguration()); Assert.Equal(1, rowsWritten[0]); const string expected = "a,b,c,d\r\n\"potato,banana\",\"text\ntest\",\"text\rpotato\",\"2021-01-01 00:00:00\"\r\n"; @@ -161,21 +155,21 @@ public void QuoteSpecialCharacters() public void SaveAsByDictionary() { { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); var table = new List>(); - MiniExcel.SaveAs(path.ToString(), table); + _csvExporter.Export(path.ToString(), table); Assert.Equal("\r\n", File.ReadAllText(path.ToString())); } { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); var table = new Dictionary(); //TODO - Assert.Throws(() => MiniExcel.SaveAs(path.ToString(), table)); + Assert.Throws(() => _csvExporter.Export(path.ToString(), table)); } { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); List> values = [ new() @@ -195,11 +189,11 @@ public void SaveAsByDictionary() } ]; - var rowsWritten = MiniExcel.SaveAs(path.ToString(), values); + var rowsWritten = _csvExporter.Export(path.ToString(), values); Assert.Equal(2, rowsWritten[0]); using var reader = new StreamReader(path.ToString()); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); @@ -214,7 +208,7 @@ public void SaveAsByDictionary() } { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); List> values = [ new() @@ -232,10 +226,10 @@ public void SaveAsByDictionary() { 4, new DateTime(2021, 1, 2) } } ]; - MiniExcel.SaveAs(path.ToString(), values); + _csvExporter.Export(path.ToString(), values); using (var reader = new StreamReader(path.ToString())) - using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) + using (var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); { @@ -260,17 +254,17 @@ public void SaveAsByDictionary() public void SaveAsByDataTableTest() { { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); var table = new DataTable(); - MiniExcel.SaveAs(path.ToString(), table); + _csvExporter.Export(path.ToString(), table); var text = File.ReadAllText(path.ToString()); Assert.Equal("\r\n", text); } { - using var path = AutoDeletingPath.Create(ExcelType.CSV); + using var path = AutoDeletingPath.Create(ExcelType.Csv); var table = new DataTable(); { @@ -282,11 +276,11 @@ public void SaveAsByDataTableTest() table.Rows.Add("Hello World", -1234567890, false, new DateTime(2021, 1, 2)); } - var rowsWritten = MiniExcel.SaveAs(path.ToString(), table); + var rowsWritten = _csvExporter.Export(path.ToString(), table); Assert.Equal(2, rowsWritten[0]); using (var reader = new StreamReader(path.ToString())) - using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)) + using (var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csv.GetRecords().ToList(); Assert.Equal(@"""<>+-*//}{\\n", records[0].a); @@ -311,33 +305,33 @@ private class Test private class TestWithAlias { - [ExcelColumnName(excelColumnName: "c1", aliases: ["column1", "col1"])] + [MiniExcelColumnName(columnName: "c1", aliases: ["column1", "col1"])] public string c1 { get; set; } - [ExcelColumnName(excelColumnName: "c2", aliases: ["column2", "col2"])] + [MiniExcelColumnName(columnName: "c2", aliases: ["column2", "col2"])] public string c2 { get; set; } } [Fact] public void CsvExcelTypeTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); var input = new[] { new { A = "Test1", B = "Test2" } }; - MiniExcel.SaveAs(path, input); + _csvExporter.Export(path, input); var texts = File.ReadAllLines(path); Assert.Equal("A,B", texts[0]); Assert.Equal("Test1,Test2", texts[1]); - var rows = MiniExcel.Query(path).ToList(); + var rows = _csvImporter.Query(path).ToList(); Assert.Equal("A", rows[0].A); Assert.Equal("B", rows[0].B); Assert.Equal("Test1", rows[1].A); Assert.Equal("Test2", rows[1].B); using var reader = new StreamReader(path); - using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); + using var csv = new global::CsvHelper.CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); Assert.Equal("Test1", records[0].A); Assert.Equal("Test2", records[0].B); @@ -346,18 +340,20 @@ public void CsvExcelTypeTest() [Fact] public void Create2x2_Test() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - MiniExcel.SaveAs(path, new[] + _csvExporter.Export(path, new[] { new { c1 = "A1", c2 = "B1"}, new { c1 = "A2", c2 = "B2"}, }); + var txt = File.ReadAllText(path); + using (var stream = File.OpenRead(path)) { - var rows = stream.Query(useHeaderRow: true, excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(stream, useHeaderRow: true).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -365,7 +361,7 @@ public void Create2x2_Test() } { - var rows = MiniExcel.Query(path, useHeaderRow: true, excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(path, useHeaderRow: true).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -376,10 +372,10 @@ public void Create2x2_Test() [Fact] public void CsvTypeMappingTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); - MiniExcel.SaveAs(path, new[] + _csvExporter.Export(path, new[] { new { c1 = "A1", c2 = "B1"}, new { c1 = "A2", c2 = "B2"}, @@ -387,7 +383,7 @@ public void CsvTypeMappingTest() using (var stream = File.OpenRead(path)) { - var rows = stream.Query(excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(stream).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -395,7 +391,7 @@ public void CsvTypeMappingTest() } { - var rows = MiniExcel.Query(path, excelType: ExcelType.CSV).ToList(); + var rows = _csvImporter.Query(path).ToList(); Assert.Equal("A1", rows[0].c1); Assert.Equal("B1", rows[0].c2); Assert.Equal("A2", rows[1].c1); @@ -406,14 +402,14 @@ public void CsvTypeMappingTest() [Fact] public void CsvColumnNotFoundTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); File.WriteAllLines(path, ["c1,c2", "v1"]); using (var stream = File.OpenRead(path)) { - var exception = Assert.Throws(() => stream.Query(excelType: ExcelType.CSV).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(stream).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -423,7 +419,7 @@ public void CsvColumnNotFoundTest() } { - var exception = Assert.Throws(() => MiniExcel.Query(path, excelType: ExcelType.CSV).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(path).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -436,13 +432,13 @@ public void CsvColumnNotFoundTest() [Fact] public void CsvColumnNotFoundWithAliasTest() { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); File.WriteAllLines(path, ["col1,col2", "v1"]); using (var stream = File.OpenRead(path)) { - var exception = Assert.Throws(() => stream.Query(excelType: ExcelType.CSV).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(stream).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -452,7 +448,7 @@ public void CsvColumnNotFoundWithAliasTest() } { - var exception = Assert.Throws(() => MiniExcel.Query(path, excelType: ExcelType.CSV).ToList()); + var exception = Assert.Throws(() => _csvImporter.Query(path).ToList()); Assert.Equal("c2", exception.ColumnName); Assert.Equal(2, exception.RowIndex); @@ -477,11 +473,11 @@ public void Delimiters_Test() private static string Generate(string value) { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); using (var writer = new StreamWriter(path)) - using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)) + using (var csv = new global::CsvHelper.CsvWriter(writer, CultureInfo.InvariantCulture)) { var records = Enumerable.Range(1, 1).Select(_ => new { v1 = value, v2 = value }); csv.WriteRecords(records); @@ -493,17 +489,76 @@ private static string Generate(string value) private static string MiniExcelGenerateCsv(string value) { - using var file = AutoDeletingPath.Create(ExcelType.CSV); + using var file = AutoDeletingPath.Create(ExcelType.Csv); var path = file.ToString(); using (var stream = File.Create(path)) { IEnumerable records = [new { v1 = value, v2 = value }]; - var rowsWritten = stream.SaveAs(records, excelType: ExcelType.CSV); + var rowsWritten = MiniExcel.Exporters.GetCsvExporter().Export(stream, records); Assert.Equal(1, rowsWritten[0]); } var content = File.ReadAllText(path); return content; } + + + [Fact] + public async Task InsertCsvTest() + { + 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)}, + }; + await _csvExporter.ExportAsync(path, value); + var content = await File.ReadAllTextAsync(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) }; + await _csvExporter.AppendAsync(path, value); + var content = await File.ReadAllTextAsync(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)}, + }; + + await _csvExporter.AppendAsync(path, value); + var content = await File.ReadAllTextAsync(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); + } + } + } \ No newline at end of file diff --git a/tests/MiniExcel.Tests.AspNetCore/MiniExcel.Tests.AspNetCore.csproj b/tests/MiniExcel.Tests.AspNetCore/MiniExcel.Tests.AspNetCore.csproj index 2575d01a..b6e23ffb 100644 --- a/tests/MiniExcel.Tests.AspNetCore/MiniExcel.Tests.AspNetCore.csproj +++ b/tests/MiniExcel.Tests.AspNetCore/MiniExcel.Tests.AspNetCore.csproj @@ -1,28 +1,29 @@ - - netcoreapp3.1 - + + net8.0 + enable + - - - - - - + + + + + + - - - + + + - - - + + + - - - PreserveNewest - - + + + PreserveNewest + + diff --git a/tests/MiniExcel.Tests.AspNetCore/Program.cs b/tests/MiniExcel.Tests.AspNetCore/Program.cs index 40e5271c..e2a88dd2 100644 --- a/tests/MiniExcel.Tests.AspNetCore/Program.cs +++ b/tests/MiniExcel.Tests.AspNetCore/Program.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Net; -using MiniExcelLibs; +using MiniExcelLib; using Microsoft.AspNetCore.Http; using System.Text.Json.Serialization; using Newtonsoft.Json; @@ -43,16 +43,24 @@ public IActionResult Index() { ContentType = "text/html", StatusCode = (int)HttpStatusCode.OK, - Content = @" -DownloadExcel
-DownloadExcelFromTemplatePath
-DownloadExcelFromTemplateBytes
-

Upload Excel

-
-
- -
-" + Content = + """ + + + DownloadExcel
+ DownloadExcelFromTemplatePath
+ DownloadExcelFromTemplateBytes
+ +

Upload Excel

+ +
+
+ + +
+ + + """ }; } diff --git a/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj new file mode 100644 index 00000000..5b371db1 --- /dev/null +++ b/tests/MiniExcel.Tests.Common/MiniExcel.Tests.Common.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + MiniExcelLib.Tests.Common + + + + + + + + diff --git a/tests/MiniExcelTests/Utils/AutoDeletingPath.cs b/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs similarity index 83% rename from tests/MiniExcelTests/Utils/AutoDeletingPath.cs rename to tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs index 139779bf..860e2d4b 100644 --- a/tests/MiniExcelTests/Utils/AutoDeletingPath.cs +++ b/tests/MiniExcel.Tests.Common/Utils/AutoDeletingPath.cs @@ -1,4 +1,4 @@ -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Common.Utils; public class AutoDeletingPath : IDisposable { @@ -11,7 +11,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( + public static AutoDeletingPath Create(ExcelType type = ExcelType.Xlsx) => Create( Path.GetTempPath(), $"{Guid.NewGuid()}.{type.ToString().ToLowerInvariant()}"); @@ -21,4 +21,6 @@ public void Dispose() } public override string ToString() => FilePath; -} \ No newline at end of file +} + +public enum ExcelType { Xlsx, Csv } \ No newline at end of file diff --git a/tests/MiniExcelTests/Utils/Db.cs b/tests/MiniExcel.Tests.Common/Utils/Db.cs similarity index 81% rename from tests/MiniExcelTests/Utils/Db.cs rename to tests/MiniExcel.Tests.Common/Utils/Db.cs index 38ab7b44..95402519 100644 --- a/tests/MiniExcelTests/Utils/Db.cs +++ b/tests/MiniExcel.Tests.Common/Utils/Db.cs @@ -1,13 +1,13 @@ using System.Data.SQLite; using System.Text; -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Common.Utils; -internal static class Db +public static class Db { - internal static SQLiteConnection GetConnection(string connectionString = "Data Source=:memory:") => new(connectionString); + public static SQLiteConnection GetConnection(string connectionString = "Data Source=:memory:") => new(connectionString); - internal static string GenerateDummyQuery(List> data) + public static string GenerateDummyQuery(List> data) { if (data is null or []) throw new ArgumentException("The data list cannot be null or empty."); diff --git a/tests/MiniExcelTests/Utils/PathHelper.cs b/tests/MiniExcel.Tests.Common/Utils/PathHelper.cs similarity index 87% rename from tests/MiniExcelTests/Utils/PathHelper.cs rename to tests/MiniExcel.Tests.Common/Utils/PathHelper.cs index d09df59e..cba011ad 100644 --- a/tests/MiniExcelTests/Utils/PathHelper.cs +++ b/tests/MiniExcel.Tests.Common/Utils/PathHelper.cs @@ -1,6 +1,6 @@ -namespace MiniExcelLibs.Tests.Utils; +namespace MiniExcelLib.Tests.Common.Utils; -internal static class PathHelper +public static class PathHelper { public static string GetFile(string fileName) => $"../../../../../samples/{fileName}"; diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlConfigurationTest.cs b/tests/MiniExcelTests/MiniExcelOpenXmlConfigurationTest.cs deleted file mode 100644 index 64030d00..00000000 --- a/tests/MiniExcelTests/MiniExcelOpenXmlConfigurationTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Tests.Utils; -using Xunit; - -namespace MiniExcelLibs.Tests; - -public class MiniExcelOpenXmlConfigurationTest -{ - [Fact] - public async Task EnableWriteFilePathTest() - { - var img = await new HttpClient().GetByteArrayAsync("https://user-images.githubusercontent.com/12729184/150462383-ad9931b3-ed8d-4221-a1d6-66f799743433.png"); - var value = new[] - { - new ImgExportTestDto { Name = "github", Img = File.ReadAllBytes(PathHelper.GetFile("images/github_logo.png")) }, - new ImgExportTestDto { Name = "google", Img = File.ReadAllBytes(PathHelper.GetFile("images/google_logo.png")) }, - new ImgExportTestDto { Name = "microsoft", Img = File.ReadAllBytes(PathHelper.GetFile("images/microsoft_logo.png")) }, - new ImgExportTestDto { Name = "reddit", Img = File.ReadAllBytes(PathHelper.GetFile("images/reddit_logo.png")) }, - new ImgExportTestDto { Name = "statck_overflow", Img = File.ReadAllBytes(PathHelper.GetFile("images/statck_overflow_logo.png")) }, - new ImgExportTestDto { Name = "statck_over", Img = img }, - }; - - var path = PathHelper.GetFile("xlsx/Test_EnableWriteFilePath.xlsx"); - MiniExcel.SaveAs(path, value, configuration: new OpenXmlConfiguration { EnableWriteFilePath = false }, overwriteFile: true); - Assert.True(File.Exists(path)); - - var rows = MiniExcel.Query(path).ToList(); - Assert.True(rows.All(x => x.Img is null || x.Img.Length < 1)); - } - - private class ImgExportTestDto - { - public string Name { get; set; } - - [ExcelColumn(Name = "图片", Width = 100)] - public byte[] Img { get; set; } - } -} -