diff --git a/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs b/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs index b80f88dd..a900b26f 100644 --- a/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs +++ b/src/MiniExcel.Core/Abstractions/IMiniExcelWriter.cs @@ -3,8 +3,8 @@ public partial interface IMiniExcelWriter { [CreateSyncVersion] - Task SaveAsAsync(CancellationToken cancellationToken = default); + Task SaveAsAsync(IProgress? progress = null, CancellationToken cancellationToken = default); [CreateSyncVersion] - Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default); + Task InsertAsync(bool overwriteSheet = false, IProgress? progress = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/MiniExcel.Core/Api/OpenXmlExporter.cs b/src/MiniExcel.Core/Api/OpenXmlExporter.cs index 409a96c7..84d34185 100644 --- a/src/MiniExcel.Core/Api/OpenXmlExporter.cs +++ b/src/MiniExcel.Core/Api/OpenXmlExporter.cs @@ -7,10 +7,12 @@ internal OpenXmlExporter() { } [CreateSyncVersion] - public async Task InsertSheetAsync(string path, object value, string? sheetName = "Sheet1", bool printHeader = true, bool overwriteSheet = false, OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + public async Task InsertSheetAsync(string path, object value, string? sheetName = "Sheet1", + bool printHeader = true, bool overwriteSheet = false, OpenXmlConfiguration? configuration = null, + IProgress? progress = null, CancellationToken cancellationToken = default) { if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) - throw new NotSupportedException("MiniExcel's InsertExcelSheet does not support the .xlsm format"); + throw new NotSupportedException("MiniExcel's InsertSheet does not support the .xlsm format"); if (!File.Exists(path)) { @@ -19,13 +21,13 @@ public async Task InsertSheetAsync(string path, object value, string? sheet } 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); + return await InsertSheetAsync(stream, value, sheetName, printHeader, overwriteSheet, configuration, progress, 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) + IProgress? progress = null, CancellationToken cancellationToken = default) { stream.Seek(0, SeekOrigin.End); configuration ??= new OpenXmlConfiguration { FastMode = true }; @@ -34,31 +36,31 @@ public async Task InsertSheetAsync(Stream stream, object value, string? she .CreateAsync(stream, value, sheetName, printHeader, configuration, cancellationToken) .ConfigureAwait(false); - return await writer.InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); + return await writer.InsertAsync(overwriteSheet, cancellationToken: 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) + IProgress? progress = null, CancellationToken cancellationToken = default) { if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) - throw new NotSupportedException("MiniExcel's ExportExcel does not support the .xlsm format"); + throw new NotSupportedException("MiniExcel's Export 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); + return await ExportAsync(stream, value, printHeader, sheetName, configuration, progress, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] public async Task ExportAsync(Stream stream, object value, bool printHeader = true, string? sheetName = "Sheet1", - OpenXmlConfiguration? configuration = null, CancellationToken cancellationToken = default) + OpenXmlConfiguration? configuration = null, IProgress? progress = null, CancellationToken cancellationToken = default) { var writer = await OpenXmlWriter .CreateAsync(stream, value, sheetName, printHeader, configuration, cancellationToken) .ConfigureAwait(false); - return await writer.SaveAsAsync(cancellationToken).ConfigureAwait(false); + return await writer.SaveAsAsync(progress, cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs index c1ee9b8f..f5b562e3 100644 --- a/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs +++ b/src/MiniExcel.Core/OpenXml/OpenXmlWriter.cs @@ -52,7 +52,7 @@ internal static Task CreateAsync(Stream stream, object? value, st } [CreateSyncVersion] - public async Task SaveAsAsync(CancellationToken cancellationToken = default) + public async Task SaveAsAsync(IProgress? progress = null, CancellationToken cancellationToken = default) { try { @@ -69,7 +69,7 @@ public async Task SaveAsAsync(CancellationToken cancellationToken = defau _sheets.Add(sheet.Item1); //TODO:remove _currentSheetIndex = sheet.Item1.SheetIdx; - var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken).ConfigureAwait(false); + var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, progress, cancellationToken).ConfigureAwait(false); rowsWritten.Add(rows); } @@ -87,7 +87,7 @@ public async Task SaveAsAsync(CancellationToken cancellationToken = defau } [CreateSyncVersion] - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + public async Task InsertAsync(bool overwriteSheet = false, IProgress? progress = null, CancellationToken cancellationToken = default) { try { @@ -123,13 +123,13 @@ public async Task InsertAsync(bool overwriteSheet = false, CancellationToke var insertSheetInfo = GetSheetInfos(_defaultSheetName); var insertSheetDto = insertSheetInfo.ToDto(_currentSheetIndex); _sheets.Add(insertSheetDto); - rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken).ConfigureAwait(false); + rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, progress, cancellationToken).ConfigureAwait(false); } else { _currentSheetIndex = existSheetDto.SheetIdx; _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken).ConfigureAwait(false); + rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, progress, cancellationToken).ConfigureAwait(false); } await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); @@ -177,7 +177,7 @@ internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationTo } [CreateSyncVersion] - private async Task CreateSheetXmlAsync(object? values, string sheetPath, CancellationToken cancellationToken) + private async Task CreateSheetXmlAsync(object? values, string sheetPath, IProgress? progress, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -197,7 +197,7 @@ private async Task CreateSheetXmlAsync(object? values, string sheetPath, Ca } else { - rowsWritten = await WriteValuesAsync(writer, values, cancellationToken).ConfigureAwait(false); + rowsWritten = await WriteValuesAsync(writer, values, cancellationToken, progress).ConfigureAwait(false); } _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); @@ -232,7 +232,7 @@ private static async Task WriteDimensionAsync(SafeStreamWriter writer, int maxRo } [CreateSyncVersion] - private async Task WriteValuesAsync(SafeStreamWriter writer, object values, CancellationToken cancellationToken) + private async Task WriteValuesAsync(SafeStreamWriter writer, object values, CancellationToken cancellationToken, IProgress? progress = null) { cancellationToken.ThrowIfCancellationRequested(); @@ -313,6 +313,7 @@ private async Task WriteValuesAsync(SafeStreamWriter writer, object values, { cancellationToken.ThrowIfCancellationRequested(); await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); + progress?.Report(1); } await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); } @@ -328,6 +329,7 @@ private async Task WriteValuesAsync(SafeStreamWriter writer, object values, await foreach (var cellValue in row.ConfigureAwait(false).WithCancellation(cancellationToken)) { await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); + progress?.Report(1); } await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); } diff --git a/src/MiniExcel.Csv/Api/CsvExporter.cs b/src/MiniExcel.Csv/Api/CsvExporter.cs index c4c71702..7ece7412 100644 --- a/src/MiniExcel.Csv/Api/CsvExporter.cs +++ b/src/MiniExcel.Csv/Api/CsvExporter.cs @@ -12,43 +12,43 @@ internal CsvExporter() { } [CreateSyncVersion] public async Task AppendAsync(string path, object value, bool printHeader = true, - CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + CsvConfiguration? configuration = null, IProgress? progress = null, CancellationToken cancellationToken = default) { if (!File.Exists(path)) { - var rowsWritten = await ExportAsync(path, value, printHeader, false, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + var rowsWritten = await ExportAsync(path, value, printHeader, false, configuration, progress, 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); + return await AppendAsync(stream, value, configuration, progress, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] - public async Task AppendAsync(Stream stream, object value, CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + public async Task AppendAsync(Stream stream, object value, CsvConfiguration? configuration = null, IProgress? progress = 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); + return await writer.InsertAsync(false, progress, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] - public async Task ExportAsync(string path, object value, bool printHeader = true, bool overwriteFile = false, - CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + public async Task ExportAsync(string path, object value, bool printHeader = true, bool overwriteFile = false, + CsvConfiguration? configuration = null, IProgress? progress = 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); + return await ExportAsync(stream, value, printHeader, configuration, progress, cancellationToken).ConfigureAwait(false); } [CreateSyncVersion] - public async Task ExportAsync(Stream stream, object value, bool printHeader = true, - CsvConfiguration? configuration = null, CancellationToken cancellationToken = default) + public async Task ExportAsync(Stream stream, object value, bool printHeader = true, + CsvConfiguration? configuration = null, IProgress? progress = null, CancellationToken cancellationToken = default) { using var writer = new CsvWriter(stream, value, printHeader, configuration); - return await writer.SaveAsAsync(cancellationToken).ConfigureAwait(false); + return await writer.SaveAsAsync(progress, cancellationToken).ConfigureAwait(false); } #endregion diff --git a/src/MiniExcel.Csv/CsvWriter.cs b/src/MiniExcel.Csv/CsvWriter.cs index bd66449b..49d5abe6 100644 --- a/src/MiniExcel.Csv/CsvWriter.cs +++ b/src/MiniExcel.Csv/CsvWriter.cs @@ -37,7 +37,8 @@ private static void RemoveTrailingSeparator(StringBuilder rowBuilder) } [CreateSyncVersion] - private async Task WriteValuesAsync(StreamWriter writer, object values, string separator, string newLine, CancellationToken cancellationToken = default) + private async Task WriteValuesAsync(StreamWriter writer, object values, string separator, string newLine, + IProgress? progress = null, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -96,6 +97,7 @@ await _writer.WriteAsync(newLine { cancellationToken.ThrowIfCancellationRequested(); AppendColumn(rowBuilder, column); + progress?.Report(1); } RemoveTrailingSeparator(rowBuilder); @@ -124,6 +126,7 @@ await _writer.WriteAsync(newLine await foreach (var column in row.WithCancellation(cancellationToken).ConfigureAwait(false)) { AppendColumn(rowBuilder, column); + progress?.Report(1); } RemoveTrailingSeparator(rowBuilder); @@ -146,7 +149,7 @@ await _writer.WriteAsync(newLine } [CreateSyncVersion] - public async Task SaveAsAsync(CancellationToken cancellationToken = default) + public async Task SaveAsAsync(IProgress? progress = null, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -168,7 +171,7 @@ await _writer.FlushAsync( return []; } - var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken).ConfigureAwait(false); + var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, progress, cancellationToken).ConfigureAwait(false); await _writer.FlushAsync( #if NET5_0_OR_GREATER cancellationToken @@ -179,9 +182,9 @@ await _writer.FlushAsync( } [CreateSyncVersion] - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + public async Task InsertAsync(bool overwriteSheet = false, IProgress? progress = null, CancellationToken cancellationToken = default) { - var rowsWritten = await SaveAsAsync(cancellationToken).ConfigureAwait(false); + var rowsWritten = await SaveAsAsync(progress, cancellationToken).ConfigureAwait(false); return rowsWritten.FirstOrDefault(); } diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index ae2aea8e..ebdef08b 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -39,7 +39,7 @@ public static MiniExcelDataReader GetReader(string path, bool useHeaderRow = fal { 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") + _ => throw new NotSupportedException($"Type {type} is not a valid Excel type") }; } @@ -50,55 +50,63 @@ public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRo { 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") + _ => throw new NotSupportedException($"Type {type} is not a valid Excel type") }; } [CreateSyncVersion] - 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) + 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, + IProgress? progress = null, 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") + ExcelType.XLSX => await ExcelExporter.InsertSheetAsync(path, value, sheetName, printHeader, overwriteSheet, configuration as OpenXmlConfiguration, progress, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.AppendAsync(path, value, printHeader, configuration as Csv.CsvConfiguration, progress, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } [CreateSyncVersion] - 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) + 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, + IProgress? progress = null, 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") + ExcelType.XLSX => await ExcelExporter.InsertSheetAsync(stream, value, sheetName, printHeader, overwriteSheet, configuration as OpenXmlConfiguration, progress, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.AppendAsync(stream, value, configuration as Csv.CsvConfiguration, progress, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } [CreateSyncVersion] - 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) + 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, IProgress? progress = null, 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") + ExcelType.XLSX => await ExcelExporter.ExportAsync(path, value, printHeader, sheetName, overwriteFile, configuration as OpenXmlConfiguration, progress, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.ExportAsync(path, value, printHeader, overwriteFile, configuration as Csv.CsvConfiguration, progress, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } [CreateSyncVersion] - 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) + public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, + string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration? configuration = null, + IProgress? progress = 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") + ExcelType.XLSX => await ExcelExporter.ExportAsync(stream, value, printHeader, sheetName, configuration as OpenXmlConfiguration, progress, cancellationToken).ConfigureAwait(false), + ExcelType.CSV => await CsvExporter.ExportAsync(stream, value, printHeader, configuration as Csv.CsvConfiguration, progress, cancellationToken).ConfigureAwait(false), + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -110,7 +118,7 @@ public static async Task SaveAsAsync(this Stream stream, object value, bo { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -122,7 +130,7 @@ public static async Task SaveAsAsync(this Stream stream, object value, bo { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -134,7 +142,7 @@ public static IAsyncEnumerable QueryAsync(string path, bool useHeaderRo { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -146,7 +154,7 @@ public static IAsyncEnumerable QueryAsync(this Stream stream, bool useH { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -167,7 +175,7 @@ public static IAsyncEnumerable QueryRangeAsync(string path, bool useHea { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -179,7 +187,7 @@ public static IAsyncEnumerable QueryRangeAsync(this Stream stream, bool { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -191,7 +199,7 @@ public static IAsyncEnumerable QueryRangeAsync(string path, bool useHea { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -203,7 +211,7 @@ public static IAsyncEnumerable QueryRangeAsync(this Stream stream, bool { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } @@ -273,7 +281,7 @@ public static async Task QueryAsDataTableAsync(string path, bool useH { 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") + _ => throw new InvalidDataException($"Type {type} is not a valid Excel type") }; } diff --git a/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs index d0aef62f..bf541d1e 100644 --- a/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcel.Core.Tests/MiniExcelOpenXmlAsyncTests.cs @@ -1559,4 +1559,41 @@ static async IAsyncEnumerable GetValues() Assert.Equal("Github", results[^1].Column1); Assert.Equal(2, results[^1].Column2); } + + [Fact] + public async Task ExportDataTableWithProgressTest() + { + var dataTable = new DataTable(); + dataTable.Columns.Add("Id", typeof(int)); + dataTable.Columns.Add("Name", typeof(string)); + dataTable.Columns.Add("Date", typeof(DateTime)); + dataTable.Rows.Add(1, "Alice", DateTime.Now); + dataTable.Rows.Add(2, DBNull.Value, DateTime.UtcNow); + dataTable.Rows.Add(3, "Alice", DateTime.Now.Date); + + var progress = new SimpleProgress(); + using var ms = new MemoryStream(); + var rowCounts = await _excelExporter.ExportAsync(ms, dataTable, progress: progress); + Assert.Single(rowCounts); + Assert.Equal(3, rowCounts.First()); + + //Confirm the progress report is correct + var cellCount = dataTable.Columns.Count * dataTable.Rows.Count; + Assert.Equal(cellCount, progress.Value); + + ms.Seek(0, SeekOrigin.Begin); + var resultDataTable = await _excelImporter.QueryAsDataTableAsync(ms); + + //Confirm the data is correct + Assert.Equal(dataTable.Rows.Count, resultDataTable.Rows.Count); + Assert.Equal(dataTable.Columns.Count, resultDataTable.Columns.Count); + for (var i = 0; i < dataTable.Rows.Count; i++) + { + for (var j = 0; j < dataTable.Columns.Count; j++) + { + //We compare string values because types change after writing and reading them back (e.g. int becomes double) + Assert.Equal(dataTable.Rows[i][j].ToString(), resultDataTable.Rows[i][j].ToString()); + } + } + } } \ No newline at end of file diff --git a/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs b/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs index 95471ece..b345233f 100644 --- a/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs +++ b/tests/MiniExcel.Csv.Tests/MiniExcelCsvAsycTests.cs @@ -370,4 +370,53 @@ static async IAsyncEnumerable GetValues() Assert.Equal("A2", results[1].C1); Assert.Equal("B2", results[1].C2); } + + [Fact] + public async Task ExportDataTableWithProgressTest() + { + var dataTable = new DataTable(); + dataTable.Columns.Add("Id", typeof(int)); + dataTable.Columns.Add("Name", typeof(string)); + dataTable.Columns.Add("Date", typeof(DateTime)); + dataTable.Rows.Add(1, "Alice", new DateTime(1900, 1, 1, 1, 0, 0)); + dataTable.Rows.Add(2, DBNull.Value, new DateTime(1901, 2, 2, 2, 0, 0)); + dataTable.Rows.Add(3, "Alice", DateTime.Now.Date); + + // We need to use the file system because the CsvExporter automatically disposes the stream + var tempFilePath = Path.GetTempFileName(); + File.Delete(tempFilePath); + + var progress = new SimpleProgress(); + var rowCounts = await _csvExporter.ExportAsync(tempFilePath, dataTable, progress: progress); + Assert.Single(rowCounts); + Assert.Equal(3, rowCounts.First()); + + //Confirm the progress report is correct + var cellCount = dataTable.Columns.Count * dataTable.Rows.Count; + Assert.Equal(cellCount, progress.Value); + + var resultDataTable = await _csvImporter.QueryAsDataTableAsync(tempFilePath); + + //Confirm the data is correct + Assert.Equal(dataTable.Rows.Count, resultDataTable.Rows.Count); + Assert.Equal(dataTable.Columns.Count, resultDataTable.Columns.Count); + for (var i = 0; i < dataTable.Rows.Count; i++) + { + for (var j = 0; j < dataTable.Columns.Count; j++) + { + if (dataTable.Columns[j].DataType == typeof(DateTime)) + { + //We need to compare Dates properly as they will be formatted differently in CSV + //Note: if dates have millisecond precision that will be lost when saving to CSV + DateTime.TryParse(resultDataTable.Rows[i][j].ToString(), out var resultDate); + Assert.Equal((DateTime)dataTable.Rows[i][j], resultDate); + } + else + { + //We compare string values because types change after writing and reading them back + Assert.Equal(dataTable.Rows[i][j].ToString(), resultDataTable.Rows[i][j].ToString()); + } + } + } + } } \ No newline at end of file diff --git a/tests/MiniExcel.Tests.Common/Utils/SimpleProgress.cs b/tests/MiniExcel.Tests.Common/Utils/SimpleProgress.cs new file mode 100644 index 00000000..16a27672 --- /dev/null +++ b/tests/MiniExcel.Tests.Common/Utils/SimpleProgress.cs @@ -0,0 +1,10 @@ +namespace MiniExcelLib.Tests.Common.Utils; + +public class SimpleProgress: IProgress +{ + public int Value { get; private set; } + public void Report(int value) + { + Value += value; + } +}