diff --git a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs index 5f1f7202..6b625222 100644 --- a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs +++ b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs @@ -13,9 +13,34 @@ internal class WorksheetXml internal static string Dimension(string dimensionRef) => $"{StartDimension}{dimensionRef}\"/>"; + internal const string StartSheetViews = ""; + internal const string EndSheetViews = ""; + + internal static string StartSheetView( int tabSelected=1, int workbookViewId=0 ) + => $""; + internal const string EndSheetView = ""; + internal const string StartSheetData = ""; internal const string EndSheetData = ""; + internal static string StartPane( int? xSplit, int? ySplit, string topLeftCell, string activePane, string state ) + => string.Concat( + ""); + + internal static string PaneSelection( string pane, string activeCell, string sqref) + => string.Concat( + $""); + internal static string StartRow(int rowIndex) => $""; internal const string EndRow = ""; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 79e211d6..35d1e430 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -1,5 +1,4 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.OpenXml.Constants; using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.Utils; using MiniExcelLibs.Zip; @@ -7,12 +6,10 @@ using System.Collections; using System.Collections.Generic; using System.Data; -using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; -using static MiniExcelLibs.Utils.ImageHelper; namespace MiniExcelLibs.OpenXml { @@ -136,6 +133,9 @@ private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReade } maxColumnIndex = props.Count; + //sheet view + WriteSheetViews(writer); + WriteColumnsWidths(writer, props); writer.Write(WorksheetXml.StartSheetData); @@ -260,6 +260,9 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable writer.Write(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, maxColumnIndex))); } + //sheet view + WriteSheetViews(writer); + //cols:width WriteColumnsWidths(writer, props); @@ -331,6 +334,9 @@ private void GenerateSheetByDataTable(MiniExcelStreamWriter writer, DataTable va var prop = GetColumnInfosFromDynamicConfiguration(columnName); props.Add(prop); } + + //sheet view + WriteSheetViews(writer); WriteColumnsWidths(writer, props); @@ -389,6 +395,91 @@ private static void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable 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/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs index d4d320ac..218d9f42 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs @@ -8,6 +8,8 @@ public class OpenXmlConfiguration : Configuration public bool FillMergedCells { get; set; } public TableStyles TableStyles { get; set; } = TableStyles.Default; public bool AutoFilter { get; set; } = true; + public int FreezeRowCount { get; set; } = 1; + public int FreezeColumnCount { get; set; } = 0; public bool EnableConvertByteArray { get; set; } = true; public bool IgnoreTemplateParameterMissing { get; set; } = true; public bool EnableWriteNullValueCell { get; set; } = true; diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 5af73bb3..5155b3cc 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -56,10 +56,10 @@ public void SaveAsControlChracter() string path = GetTempXlsxPath(); char[] chars = new char[] {'\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', '\u0009', // - '\u000A', // - '\u000B','\u000C', + '\u000A', // + '\u000B','\u000C', '\u000D', // - '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', + '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' }; var input = chars.Select(s => new { Test = s.ToString() }); @@ -823,6 +823,67 @@ public void SaveAsByIEnumerableIDictionary() } } + [Fact()] + public void SaveAsFrozenRowsAndColumnsTest() { + + var config = new OpenXmlConfiguration + { + FreezeRowCount = 1, + FreezeColumnCount = 2 + }; + + { + // Test enumerable + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + MiniExcel.SaveAs( + 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"); + MiniExcel.SaveAs(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"); + + MiniExcel.SaveAs(pathReader, reader, configuration: config); + Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(pathTable)); + } + + } + [Fact()] public void SaveAsByDapperRows() {