Fix dimension writing in FastMode (#659)

* Add tests for fast mode

* Fix dimension writing in FastMode
This commit is contained in:
Nikolai Norum Hansen 2024-08-25 09:59:32 +02:00 committed by GitHub
parent 6d81ddc59f
commit 29d4ee8af8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 129 additions and 28 deletions

View File

@ -8,10 +8,9 @@ namespace MiniExcelLibs.OpenXml.Constants
internal const string StartWorksheetWithRelationship = @"<?xml version=""1.0"" encoding=""utf-8""?><x:worksheet xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"" xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" >";
internal const string EndWorksheet = "</x:worksheet>";
internal const string StartDimension = @"<x:dimension ref=""";
internal const string StartDimension = "<x:dimension ref=\"";
internal const string DimensionPlaceholder = " />";
internal static string Dimension(string dimensionRef)
=> $"{StartDimension}{dimensionRef}\"/>";
internal static string Dimension(string dimensionRef) => $"{StartDimension}{dimensionRef}\" />";
internal const string StartSheetViews = "<x:sheetViews>";
internal const string EndSheetViews = "</x:sheetViews>";

View File

@ -77,9 +77,28 @@ namespace MiniExcelLibs.OpenXml
await writer.WriteAsync(ExcelXml.EmptySheetXml);
}
private async Task<long> 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 @@ namespace MiniExcelLibs.OpenXml
{
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<ExcelColumnInfo>();
@ -139,8 +157,7 @@ namespace MiniExcelLibs.OpenXml
if (_configuration.FastMode)
{
writer.SetPosition(dimensionWritePosition);
await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""");
await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition);
}
}
@ -210,14 +227,12 @@ namespace MiniExcelLibs.OpenXml
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 @@ namespace MiniExcelLibs.OpenXml
// 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);
}
}

View File

@ -109,19 +109,36 @@ namespace MiniExcelLibs.OpenXml
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<ExcelColumnInfo>();
@ -177,8 +194,7 @@ namespace MiniExcelLibs.OpenXml
if (_configuration.FastMode)
{
writer.SetPosition(dimensionWritePosition);
writer.WriteAndFlush($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""");
WriteDimension(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPosition);
}
}
@ -248,14 +264,12 @@ namespace MiniExcelLibs.OpenXml
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 @@ namespace MiniExcelLibs.OpenXml
// 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);
}
}

View File

@ -3734,5 +3734,82 @@ MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3
MiniExcel.SaveAs( path, values, excelType: ExcelType.XLSX, configuration: config, overwriteFile: true );
}
private class Issue658TestData
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
/// <summary>
/// https://github.com/mini-software/MiniExcel/issues/658
/// </summary>
[Fact]
public void Issue_658()
{
static IEnumerable<Issue658TestData> 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<Issue658TestData>().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++;
}
}
/// <summary>
/// https://github.com/mini-software/MiniExcel/issues/658
/// </summary>
/// <returns></returns>
[Fact]
public async Task Issue_658_async()
{
static IEnumerable<Issue658TestData> 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<Issue658TestData>()).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++;
}
}
}
}