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);