diff --git a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj index 1e68a8bd..c4be3c22 100644 --- a/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj +++ b/benchmarks/MiniExcel.Benchmarks/MiniExcel.Benchmarks.csproj @@ -8,6 +8,7 @@ latest MiniExcelLib.Benchmarks $(NoWarn);CA2000;CA2007 + true diff --git a/src/MiniExcel.Core/Helpers/FileHelper.cs b/src/MiniExcel.Core/Helpers/FileHelper.cs index dbd1180c..16b95b04 100644 --- a/src/MiniExcel.Core/Helpers/FileHelper.cs +++ b/src/MiniExcel.Core/Helpers/FileHelper.cs @@ -2,5 +2,6 @@ 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 + public static FileStream OpenSharedRead(string path) + => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); +} diff --git a/src/MiniExcel.OpenXml/Picture/OpenXmlPictureImplement.cs b/src/MiniExcel.OpenXml/Picture/OpenXmlPictureImplement.cs index 0e658898..8c0a8604 100644 --- a/src/MiniExcel.OpenXml/Picture/OpenXmlPictureImplement.cs +++ b/src/MiniExcel.OpenXml/Picture/OpenXmlPictureImplement.cs @@ -20,7 +20,8 @@ public static async Task AddPictureAsync(Stream excelStream, CancellationToken c var excelArchive = await OpenXmlZip.CreateAsync(excelStream, cancellationToken: cancellationToken).ConfigureAwait(false); await using var disposableExcelArchive = excelArchive.ConfigureAwait(false); - using var reader = await OpenXmlReader.CreateAsync(excelStream, null, false, cancellationToken).ConfigureAwait(false); + var reader = await OpenXmlReader.CreateAsync(excelStream, null, false, cancellationToken).ConfigureAwait(false); + await using var dispoableReader = reader.ConfigureAwait(false); #if NET10_0_OR_GREATER var archive = await ZipArchive.CreateAsync(excelStream, ZipArchiveMode.Update, true, null, cancellationToken).ConfigureAwait(false); diff --git a/src/MiniExcel/MiniExcelConverter.cs b/src/MiniExcel/MiniExcelConverter.cs index 331f9d45..ddf9cdfa 100644 --- a/src/MiniExcel/MiniExcelConverter.cs +++ b/src/MiniExcel/MiniExcelConverter.cs @@ -5,51 +5,112 @@ namespace MiniExcelLib; +/// +/// Provides methods for converting between CSV and OpenXml (XLSX) documents. +/// public static partial class MiniExcelConverter { - /// The origin stream is left open. + /// + /// Converts a CSV file to an OpenXml (XLSX) file. + /// + /// The file path to the CSV file to be converted. + /// The file path where the resulting XLSX file will be created. + /// If true, the first row will be treated as headers and printed in the Excel file. Otherwise, all rows are treated as data. Default is false + /// A cancellation token to signal that the operation should be cancelled. + /// + /// A task that completes when the OpenXml file has been successfully created. + /// [CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, bool csvHasHeader = false, CancellationToken cancellationToken = default) + public static async Task ConvertCsvToXlsxAsync(string csvPath, string xlsxPath, bool csvHasHeader = false, CancellationToken cancellationToken = default) { - var value = MiniExcel.Importers - .GetCsvImporter() - .QueryAsync(csv, hasHeaderRow: csvHasHeader, leaveOpen: true, cancellationToken: cancellationToken); +#if SYNC_ONLY + using var csvStream = MiniExcelLib.Core.Helpers.FileHelper.OpenSharedRead(csvPath); + using var xlsxStream = new FileStream(xlsxPath, FileMode.CreateNew); +#else + var csvStream = FileHelper.OpenSharedRead(csvPath); + await using var disposableCsvStream = csvStream.ConfigureAwait(false); - await MiniExcel.Exporters - .GetOpenXmlExporter() - .ExportAsync(xlsx, value, printHeader: csvHasHeader, cancellationToken: cancellationToken) - .ConfigureAwait(false); + var xlsxStream = new FileStream(xlsxPath, FileMode.CreateNew); + await using var disposableXlsxStream = xlsxStream.ConfigureAwait(false); +#endif + + await ConvertCsvToXlsxAsync(csvStream, xlsxStream, csvHasHeader, cancellationToken).ConfigureAwait(false); } + /// + /// Converts CSV data from a stream to OpenXml (XLSX) format in another stream. + /// + /// A readable stream containing CSV data. + /// A writable stream where the Excel data will be written. + /// If true, the first row will be treated as headers and printed in the Excel file. Otherwise, all rows are treated as data. Default is false. + /// A cancellation token to signal that the operation should be cancelled. + /// + /// A task that completes when the OpenXml data has been successfully written to the output stream. + /// + /// + /// The streams will not be closed by this method; the caller is responsible for disposing them. + /// [CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(string csvPath, string xlsx, bool csvHasHeader = false, CancellationToken cancellationToken = default) + public static async Task ConvertCsvToXlsxAsync(Stream csvStream, Stream xlsxStream, bool csvHasHeader = false, CancellationToken cancellationToken = default) { - using var csvStream = FileHelper.OpenSharedRead(csvPath); - using var xlsxStream = new FileStream(xlsx, FileMode.CreateNew); + var value = MiniExcel.Importers.GetCsvImporter() + .QueryAsync(csvStream, hasHeaderRow: csvHasHeader, leaveOpen: true, cancellationToken: cancellationToken) + .ConfigureAwait(false); - await ConvertCsvToXlsxAsync(csvStream, xlsxStream, csvHasHeader, cancellationToken).ConfigureAwait(false); + await MiniExcel.Exporters.GetOpenXmlExporter() + .ExportAsync(xlsxStream, value, printHeader: csvHasHeader, cancellationToken: cancellationToken) + .ConfigureAwait(false); } + /// + /// Converts an OpenXml (XLSX) file to a CSV file. + /// + /// The file path to the XLSX file to be converted. + /// The file path where the resulting CSV file will be created. + /// If true, the first row will be treated as headers and printed in the CSV file. Otherwise, all rows are treated as data. Default is false. + /// A cancellation token to signal that the operation should be cancelled. + /// + /// A task that completes when the CSV file has been successfully created. + /// [CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(string xlsx, string csvPath, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) + public static async Task ConvertXlsxToCsvAsync(string xlsxPath, string csvPath, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) { - using var xlsxStream = FileHelper.OpenSharedRead(xlsx); +#if SYNC_ONLY + using var xlsxStream = MiniExcelLib.Core.Helpers.FileHelper.OpenSharedRead(xlsxPath); using var csvStream = new FileStream(csvPath, FileMode.CreateNew); +#else + var xlsxStream = FileHelper.OpenSharedRead(xlsxPath); + await using var disposableXlsxStream = xlsxStream.ConfigureAwait(false); + + var csvStream = new FileStream(csvPath, FileMode.CreateNew); + await using var disposableCsvStream = csvStream.ConfigureAwait(false); +#endif await ConvertXlsxToCsvAsync(xlsxStream, csvStream, xlsxHasHeader, cancellationToken).ConfigureAwait(false); } - /// The origin stream is left open. + /// + /// Converts OpenXml (XLSX) data from a stream to CSV format in another stream. + /// + /// A readable stream containing the OpenXml data. + /// A writable stream where the CSV data will be written. + /// If true, the first row will be treated as headers and printed in the Excel file. Otherwise, all rows are treated as data. Default is false. + /// A cancellation token to signal that the operation should be cancelled. + /// + /// A task that completes when the CSV data has been successfully written to the output stream. + /// + /// + /// The streams will not be closed by this method; the caller is responsible for disposing them. + /// [CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) + public static async Task ConvertXlsxToCsvAsync(Stream xlsxStream, Stream csvStream, bool xlsxHasHeader = true, CancellationToken cancellationToken = default) { - var value = MiniExcel.Importers - .GetOpenXmlImporter() - .QueryAsync(xlsx, hasHeaderRow: xlsxHasHeader, leaveOpen: true, cancellationToken: cancellationToken) + var value = MiniExcel.Importers.GetOpenXmlImporter() + .QueryAsync(xlsxStream, hasHeaderRow: xlsxHasHeader, leaveOpen: true, cancellationToken: cancellationToken) .ConfigureAwait(false); - await MiniExcel.Exporters - .GetCsvExporter() - .ExportAsync(csv, value, printHeader: xlsxHasHeader, cancellationToken: cancellationToken).ConfigureAwait(false); + await MiniExcel.Exporters.GetCsvExporter() + .ExportAsync(csvStream, value, printHeader: xlsxHasHeader, cancellationToken: cancellationToken) + .ConfigureAwait(false); } } diff --git a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs index 7242000d..443eb2ab 100644 --- a/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs +++ b/tests/MiniExcel.Csv.Tests/Main/MiniExcelCsvTests.cs @@ -604,7 +604,7 @@ public void ExportAndQueryMixedFieldAndPropertyTest() using var reader = new StreamReader(path); using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); - var first = records[0] as IDictionary; + var first = (IDictionary)records[0]; Assert.Contains("F1", first.Keys); Assert.Contains("P1", first.Keys); @@ -622,7 +622,7 @@ public void ExportAndQueryFieldsWithoutAttributeTest() using var reader = new StreamReader(path); using var csv = new CsvReader(reader, CultureInfo.InvariantCulture); var records = csv.GetRecords().ToList(); - var first = records[0] as IDictionary; + var first = (IDictionary)records[0]; Assert.Contains("Mapped", first.Keys); Assert.DoesNotContain("NotMappedField", first.Keys); diff --git a/tests/MiniExcel.Csv.Tests/Main/Models.cs b/tests/MiniExcel.Csv.Tests/Main/Models.cs index 46b4b425..e03f5150 100644 --- a/tests/MiniExcel.Csv.Tests/Main/Models.cs +++ b/tests/MiniExcel.Csv.Tests/Main/Models.cs @@ -9,7 +9,7 @@ internal class TestDto internal class CsvFieldMappingTest { [MiniExcelColumnName("Column1")] - public string Test1; + public string? Test1; [MiniExcelColumnName("Column2")] public int Test2; @@ -21,18 +21,18 @@ internal class CsvFieldMappingTest internal class MixedFieldPropertyTest { [MiniExcelColumnName("F1")] - public string Field1; + public string? Field1; [MiniExcelColumnName("P1")] - public string Prop1 { get; set; } + public string? Prop1 { get; set; } } internal class CsvFieldsWithoutAttributeDemo { - public string NotMappedField; + public string? NotMappedField; [MiniExcelColumnName("Mapped")] - public string MappedField; + public string? MappedField; } internal class TestWithAlias diff --git a/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTests.cs b/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTests.cs index 1ee34c57..038820cc 100644 --- a/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/FluentMapping/MiniExcelMappingTests.cs @@ -1251,7 +1251,7 @@ public void Multiple_Items_With_Collections_Should_Detect_Pattern() { var boundaries = mapping.OptimizedBoundaries; // Pattern detection for multiple items - Assert.True(boundaries.PatternHeight > 0 || !boundaries.IsMultiItemPattern); + Assert.True(boundaries?.PatternHeight > 0 || boundaries?.IsMultiItemPattern is false); } } diff --git a/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs b/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs index 54d73057..7bc9a1b2 100644 --- a/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/InputValueExtractorTests.cs @@ -47,10 +47,10 @@ public void ToValueDictionary_Given_InputIsDictionaryWithDataReader_Then_DataRea var sut = new OpenXmlValueExtractor(); var extracted = sut.ToValueDictionary(valueDictionary); - var result = (List>)extracted["DataReader"]; + var result = (List>?)extracted["DataReader"]; - Assert.Equal(result.Count, expectedOutput.Count); - for (int i = 0; i < result.Count; i++) + Assert.Equal(result?.Count, expectedOutput.Count); + for (int i = 0; i < result?.Count; i++) { var row = result[i]; var expected = expectedOutput[i]; @@ -114,8 +114,8 @@ public void ToValueDictionary_Given_InputIsPocoClass_Then_Output_IsAnEquivalentD private record PocoRecord(string Name, int Age, IEnumerable Fruits); private class PocoClass { - public string Name { get; set; } + public string? Name { get; set; } public int Age { get; set; } - public IEnumerable Fruits; // Field - }; -} \ No newline at end of file + public IEnumerable? Fruits; // Field + } +} diff --git a/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs b/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs index 24bb12dd..264424a2 100644 --- a/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs +++ b/tests/MiniExcel.OpenXml.Tests/Templates/Models.cs @@ -2,7 +2,7 @@ namespace MiniExcelLib.OpenXml.Tests.Templates; internal class TestIEnumerableTypePoco { - public string @string { get; set; } + public string? @string { get; set; } public int? @int { get; set; } public decimal? @decimal { get; set; } public double? @double { get; set; } @@ -13,8 +13,8 @@ internal class TestIEnumerableTypePoco internal class Employee { - public string name { get; set; } - public string department { get; set; } + public string? name { get; set; } + public string? department { get; set; } } internal record struct Identity(int Type, string Id);