From 92b295d4b2c7717cb9297f8a27c41c3183bb0abc Mon Sep 17 00:00:00 2001
From: BaatenHannes <31864108+BaatenHannes@users.noreply.github.com>
Date: Sat, 2 Nov 2024 12:19:10 +0100
Subject: [PATCH] Async implementation of freezing top row (#684)
* Add async implementation of frozen rows and columns
* Add unit tests for async implementation of frozen rows and columns
* Fix formatting and delete temp file in unit test
* move freezing top row implementation to DefaultOpenXml.cs
---
.../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 9 ++
.../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 97 +++++++++++++++++++
.../OpenXml/ExcelOpenXmlSheetWriter.cs | 91 +----------------
.../MiniExcelOpenXmlAsyncTests.cs | 64 ++++++++++++
tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 18 ++--
5 files changed, 183 insertions(+), 96 deletions(-)
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
index 87cf374..d852a4d 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs
@@ -119,6 +119,9 @@ namespace MiniExcelLibs.OpenXml
}
maxColumnIndex = props.Count;
+ //sheet view
+ await writer.WriteAsync(GetSheetViews());
+
await WriteColumnsWidthsAsync(writer, props);
await writer.WriteAsync(WorksheetXml.StartSheetData);
@@ -241,6 +244,9 @@ namespace MiniExcelLibs.OpenXml
await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex)));
}
+ //sheet view
+ await writer.WriteAsync(GetSheetViews());
+
//cols:width
await WriteColumnsWidthsAsync(writer, props);
@@ -309,6 +315,9 @@ namespace MiniExcelLibs.OpenXml
props.Add(prop);
}
+ //sheet view
+ await writer.WriteAsync(GetSheetViews());
+
await WriteColumnsWidthsAsync(writer, props);
await writer.WriteAsync(WorksheetXml.StartSheetData);
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
index b511c51..23bd573 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
@@ -83,6 +83,103 @@ namespace MiniExcelLibs.OpenXml
return info;
}
+
+ private string GetSheetViews()
+ {
+ // exit early if no style to write
+ if (_configuration.FreezeRowCount <= 0 && _configuration.FreezeColumnCount <= 0)
+ {
+ return string.Empty;
+ }
+
+ var sb = new StringBuilder();
+
+ // start sheetViews
+ sb.Append(WorksheetXml.StartSheetViews);
+ sb.Append(WorksheetXml.StartSheetView());
+
+ // Write panes
+ sb.Append(GetPanes());
+
+ // end sheetViews
+ sb.Append(WorksheetXml.EndSheetView);
+ sb.Append(WorksheetXml.EndSheetViews);
+
+ return sb.ToString();
+ }
+
+ private string GetPanes()
+ {
+
+ var sb = new StringBuilder();
+
+ string activePane;
+ if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0)
+ {
+ activePane = "bottomRight";
+ }
+ else if (_configuration.FreezeColumnCount > 0)
+ {
+ activePane = "topRight";
+ }
+ else
+ {
+ activePane = "bottomLeft";
+ }
+ sb.Append(
+ WorksheetXml.StartPane(
+ xSplit: _configuration.FreezeColumnCount > 0 ? _configuration.FreezeColumnCount : (int?)null,
+ ySplit: _configuration.FreezeRowCount > 0 ? _configuration.FreezeRowCount : (int?)null,
+ topLeftCell: ExcelOpenXmlUtils.ConvertXyToCell(
+ _configuration.FreezeColumnCount + 1,
+ _configuration.FreezeRowCount + 1
+ ),
+ activePane: activePane,
+ state: "frozen"
+ )
+ );
+
+ // write pane selections
+ if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0)
+ {
+ // freeze row and column
+ /*
+
+
+
+ */
+ var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount + 1, 1);
+ sb.Append(WorksheetXml.PaneSelection("topRight", cellTR, cellTR));
+
+ var cellBL = ExcelOpenXmlUtils.ConvertXyToCell(1, _configuration.FreezeRowCount + 1);
+ sb.Append(WorksheetXml.PaneSelection("bottomLeft", cellBL, cellBL));
+
+ var cellBR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1);
+ sb.Append(WorksheetXml.PaneSelection("bottomRight", cellBR, cellBR));
+ }
+ else if (_configuration.FreezeColumnCount > 0)
+ {
+ // freeze column
+ /*
+
+ */
+ var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount, 1);
+ sb.Append(WorksheetXml.PaneSelection("topRight", cellTR, cellTR));
+
+ }
+ else
+ {
+ // freeze row
+ /*
+
+ */
+ sb.Append(WorksheetXml.PaneSelection("bottomLeft", null, null));
+
+ }
+
+ return sb.ToString();
+
+ }
private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName)
{
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
index c03abad..82b9532 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
@@ -152,7 +152,7 @@ namespace MiniExcelLibs.OpenXml
maxColumnIndex = props.Count;
//sheet view
- WriteSheetViews(writer);
+ writer.Write(GetSheetViews());
WriteColumnsWidths(writer, props);
@@ -279,7 +279,7 @@ namespace MiniExcelLibs.OpenXml
}
//sheet view
- WriteSheetViews(writer);
+ writer.Write(GetSheetViews());
//cols:width
WriteColumnsWidths(writer, props);
@@ -352,7 +352,7 @@ namespace MiniExcelLibs.OpenXml
}
//sheet view
- WriteSheetViews(writer);
+ writer.Write(GetSheetViews());
WriteColumnsWidths(writer, props);
@@ -411,91 +411,6 @@ namespace MiniExcelLibs.OpenXml
writer.Write(WorksheetXml.EndCols);
}
- private void WriteSheetViews(MiniExcelStreamWriter writer) {
- // exit early if no style to write
- if (_configuration.FreezeRowCount <= 0 && _configuration.FreezeColumnCount <= 0)
- {
- return;
- }
-
- // start sheetViews
- writer.Write(WorksheetXml.StartSheetViews);
- writer.Write(WorksheetXml.StartSheetView());
-
- // Write panes
- WritePanes(writer);
-
- // end sheetViews
- writer.Write(WorksheetXml.EndSheetView);
- writer.Write(WorksheetXml.EndSheetViews);
- }
-
- private void WritePanes(MiniExcelStreamWriter writer) {
-
- string activePane;
- if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0)
- {
- activePane = "bottomRight";
- }
- else if (_configuration.FreezeColumnCount > 0)
- {
- activePane = "topRight";
- }
- else
- {
- activePane = "bottomLeft";
- }
- writer.Write( WorksheetXml.StartPane(
- xSplit: _configuration.FreezeColumnCount > 0 ? _configuration.FreezeColumnCount : (int?)null,
- ySplit: _configuration.FreezeRowCount > 0 ? _configuration.FreezeRowCount : (int?)null,
- topLeftCell: ExcelOpenXmlUtils.ConvertXyToCell(
- _configuration.FreezeColumnCount + 1,
- _configuration.FreezeRowCount + 1
- ),
- activePane: activePane,
- state: "frozen"
- ) );
-
- // write pane selections
- if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0)
- {
- // freeze row and column
- /*
-
-
-
- */
- var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount+1, 1);
- writer.Write(WorksheetXml.PaneSelection("topRight", cellTR, cellTR));
-
- var cellBL = ExcelOpenXmlUtils.ConvertXyToCell(1, _configuration.FreezeRowCount+1);
- writer.Write(WorksheetXml.PaneSelection("bottomLeft", cellBL, cellBL));
-
- var cellBR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount+1, _configuration.FreezeRowCount+1);
- writer.Write(WorksheetXml.PaneSelection("bottomRight", cellBR, cellBR));
- }
- else if ( _configuration.FreezeColumnCount > 0 )
- {
- // freeze column
- /*
-
- */
- var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount, 1);
- writer.Write(WorksheetXml.PaneSelection("topRight", cellTR, cellTR));
-
- }
- else
- {
- // freeze row
- /*
-
- */
- writer.Write(WorksheetXml.PaneSelection("bottomLeft", null, null));
-
- }
-
- }
-
private static void PrintHeader(MiniExcelStreamWriter writer, List props)
{
var xIndex = 1;
diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
index b7dd134..364aafc 100644
--- a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
+++ b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
@@ -894,6 +894,70 @@ namespace MiniExcelLibs.Tests
File.Delete(path);
}
+
+ [Fact()]
+ public async Task SaveAsFrozenRowsAndColumnsTest()
+ {
+
+ var config = new OpenXmlConfiguration
+ {
+ FreezeRowCount = 1,
+ FreezeColumnCount = 2
+ };
+
+ {
+ // Test enumerable
+ var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
+ await MiniExcel.SaveAsAsync(
+ path,
+ new[] {
+ new { Column1 = "MiniExcel", Column2 = 1 },
+ new { Column1 = "Github", Column2 = 2}
+ },
+ configuration: config
+ );
+
+ using (var stream = File.OpenRead(path))
+ {
+ var rows = stream.Query(useHeaderRow: true).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));
+ File.Delete(path);
+ }
+
+ {
+ // test table
+ var table = new DataTable();
+ {
+ table.Columns.Add("a", typeof(string));
+ table.Columns.Add("b", typeof(decimal));
+ table.Columns.Add("c", typeof(bool));
+ table.Columns.Add("d", typeof(DateTime));
+ table.Rows.Add("some text", 1234567890, true, DateTime.Now);
+ table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now.Date);
+ }
+ var pathTable = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
+ await MiniExcel.SaveAsAsync(pathTable, table, configuration: config);
+
+ Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable));
+
+
+ // data reader
+ var reader = table.CreateDataReader();
+ var pathReader = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
+
+ await MiniExcel.SaveAsAsync(pathReader, reader, configuration: config);
+ Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable));
+ }
+
+ }
+
[Fact()]
public async Task SaveAsByDapperRows()
{
diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs
index 1afc692..82e0193 100644
--- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs
+++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs
@@ -166,7 +166,7 @@ namespace MiniExcelLibs.Tests
}
}
-
+
[Fact]
public void QueryRangeToIDictionary()
{
@@ -201,7 +201,7 @@ namespace MiniExcelLibs.Tests
Assert.Equal(null, rows[2].A);
Assert.Equal(2, rows[2].B);
- Assert.Equal(null, rows[2].C);
+ Assert.Equal(null, rows[2].C);
Assert.Equal(4, rows[2].D);
Assert.Equal(null, rows[3].A);
@@ -860,7 +860,8 @@ namespace MiniExcelLibs.Tests
}
[Fact()]
- public void SaveAsFrozenRowsAndColumnsTest() {
+ public void SaveAsFrozenRowsAndColumnsTest()
+ {
var config = new OpenXmlConfiguration
{
@@ -880,7 +881,8 @@ namespace MiniExcelLibs.Tests
configuration: config
);
- using (var stream = File.OpenRead(path)) {
+ using (var stream = File.OpenRead(path))
+ {
var rows = stream.Query(useHeaderRow: true).ToList();
Assert.Equal("MiniExcel", rows[0].Column1);
@@ -890,9 +892,9 @@ namespace MiniExcelLibs.Tests
}
Assert.Equal("A1:B3", Helpers.GetFirstSheetDimensionRefValue(path));
- //File.Delete(path);
+ File.Delete(path);
}
-
+
{
// test table
var table = new DataTable();
@@ -905,7 +907,7 @@ namespace MiniExcelLibs.Tests
table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now.Date);
}
var pathTable = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx");
- MiniExcel.SaveAs(pathTable, table, configuration: config );
+ MiniExcel.SaveAs(pathTable, table, configuration: config);
Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable));
@@ -1392,7 +1394,7 @@ namespace MiniExcelLibs.Tests
{
table.Columns.Add("Column1", typeof(string));
table.Columns.Add("Column2", typeof(int));
- table.Columns.Add("Column3", typeof(DateTime));
+ table.Columns.Add("Column3", typeof(DateTime));
table.Columns.Add("Column4", typeof(DateOnly));
table.Rows.Add("MiniExcel", 1, dateTime, onlyDate);
table.Rows.Add("Github", 2, dateTime, onlyDate);