From 109df9fe78137bcc44a2e0d34e3dcc098fa1e9e5 Mon Sep 17 00:00:00 2001 From: Nikolai Norum Hansen Date: Fri, 23 Aug 2024 12:46:12 +0200 Subject: [PATCH 1/2] Add tests for fast mode --- tests/MiniExcelTests/MiniExcelIssueTests.cs | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index b3ab18ea..d8da929e 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3734,5 +3734,82 @@ public void Issue632_1() MiniExcel.SaveAs( path, values, excelType: ExcelType.XLSX, configuration: config, overwriteFile: true ); } + + private class Issue658TestData + { + public string FirstName { get; set; } + public string LastName { get; set; } + } + + /// + /// https://github.com/mini-software/MiniExcel/issues/658 + /// + [Fact] + public void Issue_658() + { + static IEnumerable GetTestData() + { + yield return new() { FirstName = "Unit", LastName = "Test" }; + yield return new() { FirstName = "Unit1", LastName = "Test1" }; + yield return new() { FirstName = "Unit2", LastName = "Test2" }; + } + + using var memoryStream = new MemoryStream(); + var testData = GetTestData(); + memoryStream.SaveAs(testData, configuration: new OpenXmlConfiguration + { + FastMode = true, + }); + + memoryStream.Position = 0; + + var queryData = memoryStream.Query().ToList(); + + Assert.Equal(testData.Count(), queryData.Count); + + var i = 0; + foreach (var data in testData) + { + Assert.Equal(data.FirstName, queryData[i].FirstName); + Assert.Equal(data.LastName, queryData[i].LastName); + i++; + } + } + + /// + /// https://github.com/mini-software/MiniExcel/issues/658 + /// + /// + [Fact] + public async Task Issue_658_async() + { + static IEnumerable GetTestData() + { + yield return new() { FirstName = "Unit", LastName = "Test" }; + yield return new() { FirstName = "Unit1", LastName = "Test1" }; + yield return new() { FirstName = "Unit2", LastName = "Test2" }; + } + + using var memoryStream = new MemoryStream(); + var testData = GetTestData(); + await memoryStream.SaveAsAsync(testData, configuration: new OpenXmlConfiguration + { + FastMode = true, + }); + + memoryStream.Position = 0; + + var queryData = (await memoryStream.QueryAsync()).ToList(); + + Assert.Equal(testData.Count(), queryData.Count); + + var i = 0; + foreach (var data in testData) + { + Assert.Equal(data.FirstName, queryData[i].FirstName); + Assert.Equal(data.LastName, queryData[i].LastName); + i++; + } + } } } \ No newline at end of file From ef612ec5d7636d7e256d263a95e813263a9eda2a Mon Sep 17 00:00:00 2001 From: Nikolai Norum Hansen Date: Fri, 23 Aug 2024 12:48:00 +0200 Subject: [PATCH 2/2] Fix dimension writing in FastMode --- .../OpenXml/Constants/WorksheetXml.cs | 5 +-- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 37 ++++++++++++------ .../OpenXml/ExcelOpenXmlSheetWriter.cs | 38 ++++++++++++------- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs index 1138fd32..0fb12908 100644 --- a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs +++ b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs @@ -8,10 +8,9 @@ internal class WorksheetXml internal const string StartWorksheetWithRelationship = @""; internal const string EndWorksheet = ""; - internal const string StartDimension = @" $"{StartDimension}{dimensionRef}\"/>"; + internal static string Dimension(string dimensionRef) => $"{StartDimension}{dimensionRef}\" />"; internal const string StartSheetViews = ""; internal const string EndSheetViews = ""; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index 80e85e38..87e08b53 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -77,9 +77,28 @@ private async Task WriteEmptySheetAsync(MiniExcelAsyncStreamWriter writer) await writer.WriteAsync(ExcelXml.EmptySheetXml); } + private async Task WriteDimensionPlaceholderAsync(MiniExcelAsyncStreamWriter writer) + { + var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension); + await writer.WriteAsync(WorksheetXml.DimensionPlaceholder); // end of code will be replaced + + return dimensionPlaceholderPostition; + } + + private async Task WriteDimensionAsync(MiniExcelAsyncStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + { + // Flush and save position so that we can get back again. + var position = await writer.FlushAsync(); + + writer.SetPosition(placeholderPosition); + await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + + writer.SetPosition(position); + } + private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter writer, IDataReader reader) { - long dimensionWritePosition = 0; + long dimensionPlaceholderPostition = 0; await writer.WriteAsync(WorksheetXml.StartWorksheet); var yIndex = 1; int maxColumnIndex; @@ -87,8 +106,7 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr { if (_configuration.FastMode) { - dimensionWritePosition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension); - await writer.WriteAsync(WorksheetXml.DimensionPlaceholder); // end of code will be replaced + dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); } var props = new List(); @@ -139,8 +157,7 @@ private async Task GenerateSheetByIDataReaderAsync(MiniExcelAsyncStreamWriter wr if (_configuration.FastMode) { - writer.SetPosition(dimensionWritePosition); - await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } } @@ -210,14 +227,12 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship); - long dimensionWritePosition = 0; + long dimensionPlaceholderPostition = 0; // We can write the dimensions directly if the row count is known if (_configuration.FastMode && rowCount == null) { - // Write a placeholder for the table dimensions and save thee position for later - dimensionWritePosition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension); - await writer.WriteAsync(WorksheetXml.DimensionPlaceholder); + dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer); } else { @@ -269,9 +284,7 @@ private async Task GenerateSheetByEnumerableAsync(MiniExcelAsyncStreamWriter wri // The dimension has already been written if row count is defined if (_configuration.FastMode && rowCount == null) { - // Seek back and write the dimensions of the table - writer.SetPosition(dimensionWritePosition); - await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index d43fd029..5299a2b6 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -109,19 +109,36 @@ private void WriteEmptySheet(MiniExcelStreamWriter writer) writer.Write(ExcelXml.EmptySheetXml); } + private long WriteDimensionPlaceholder(MiniExcelStreamWriter writer) + { + var dimensionPlaceholderPostition = writer.WriteAndFlush(WorksheetXml.StartDimension); + writer.Write(WorksheetXml.DimensionPlaceholder); // end of code will be replaced + + return dimensionPlaceholderPostition; + } + + private void WriteDimension(MiniExcelStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + { + // Flush and save position so that we can get back again. + var position = writer.Flush(); + + writer.SetPosition(placeholderPosition); + writer.WriteAndFlush($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + + writer.SetPosition(position); + } + private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReader reader) { - long dimensionWritePosition = 0; + long dimensionPlaceholderPosition = 0; writer.Write(WorksheetXml.StartWorksheet); var yIndex = 1; int maxColumnIndex; int maxRowIndex; { - if (_configuration.FastMode) { - dimensionWritePosition = writer.WriteAndFlush(WorksheetXml.StartDimension); - writer.Write(WorksheetXml.DimensionPlaceholder); // end of code will be replaced + dimensionPlaceholderPosition = WriteDimensionPlaceholder(writer); } var props = new List(); @@ -177,8 +194,7 @@ private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReade if (_configuration.FastMode) { - writer.SetPosition(dimensionWritePosition); - writer.WriteAndFlush($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPosition); } } @@ -248,14 +264,12 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable writer.Write(WorksheetXml.StartWorksheetWithRelationship); - long dimensionWritePosition = 0; + long dimensionPlaceholderPostition = 0; // We can write the dimensions directly if the row count is known if (_configuration.FastMode && rowCount == null) { - // Write a placeholder for the table dimensions and save thee position for later - dimensionWritePosition = writer.WriteAndFlush(WorksheetXml.StartDimension); - writer.Write(WorksheetXml.DimensionPlaceholder); + dimensionPlaceholderPostition = WriteDimensionPlaceholder(writer); } else { @@ -310,9 +324,7 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable // The dimension has already been written if row count is defined if (_configuration.FastMode && rowCount == null) { - // Seek back and write the dimensions of the table - writer.SetPosition(dimensionWritePosition); - writer.WriteAndFlush($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}"""); + WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition); } }