From 67e97f36b5ccc5ca9eb2adb1076dceeca1a555da Mon Sep 17 00:00:00 2001
From: meld-cp <18450687+meld-cp@users.noreply.github.com>
Date: Tue, 23 Jul 2024 01:32:04 +1200
Subject: [PATCH] Fix Issue 632, refactor sheet styles (#640)
* fix for issue 606
* fix formatting
* add test
* refactor sheet styles, fix #632
* change tabs to spaces
---
src/MiniExcel/OpenXml/Constants/ExcelXml.cs | 206 +-----------------
.../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 5 +-
.../OpenXml/ExcelOpenXmlTemplate.Impl.cs | 72 +++---
src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs | 2 +
.../Styles/DefaultSheetStyleBuilder.cs | 156 +++++++++++++
.../OpenXml/Styles/ISheetStyleBuilder.cs | 10 +
.../Styles/MinimalSheetStyleBuilder.cs | 71 ++++++
.../OpenXml/Styles/SheetStyleBuilderHelper.cs | 24 ++
tests/MiniExcelTests/MiniExcelIssueTests.cs | 44 +++-
.../MiniExcelOpenXmlAsyncTests.cs | 6 +-
10 files changed, 355 insertions(+), 241 deletions(-)
create mode 100644 src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs
create mode 100644 src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs
create mode 100644 src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs
create mode 100644 src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs
diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs
index b1a0d7a..c238528 100644
--- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs
+++ b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs
@@ -1,9 +1,4 @@
-using MiniExcelLibs.Attributes;
-using MiniExcelLibs.OpenXml.Models;
-using MiniExcelLibs.Utils;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using MiniExcelLibs.OpenXml.Models;
namespace MiniExcelLibs.OpenXml.Constants
{
@@ -11,15 +6,13 @@ namespace MiniExcelLibs.OpenXml.Constants
{
static ExcelXml()
{
- DefaultRels = MinifyXml(DefaultRels);
- DefaultWorkbookXml = MinifyXml(DefaultWorkbookXml);
- DefaultStylesXml = MinifyXml(DefaultStylesXml);
- DefaultWorkbookXmlRels = MinifyXml(DefaultWorkbookXmlRels);
- DefaultSheetRelXml = MinifyXml(DefaultSheetRelXml);
- DefaultDrawing = MinifyXml(DefaultDrawing);
+ DefaultRels = ExcelOpenXmlUtils.MinifyXml( DefaultRels);
+ DefaultWorkbookXml = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXml);
+ DefaultWorkbookXmlRels = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXmlRels);
+ DefaultSheetRelXml = ExcelOpenXmlUtils.MinifyXml(DefaultSheetRelXml);
+ DefaultDrawing = ExcelOpenXmlUtils.MinifyXml(DefaultDrawing);
}
- private static string MinifyXml(string xml) => xml.Replace("\r", "").Replace("\n", "").Replace("\t", "");
internal static readonly string EmptySheetXml = $@"";
@@ -35,146 +28,6 @@ namespace MiniExcelLibs.OpenXml.Constants
";
- internal static readonly string NoneStylesXml = @"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-";
-
- #region StyleSheet
-
- private const int startUpNumFmts = 1;
- private const string NumFmtsToken = "{{numFmts}}";
- private const string NumFmtsCountToken = "{{numFmtCount}}";
-
- private const int startUpCellXfs = 5;
- private const string cellXfsToken = "{{cellXfs}}";
- private const string cellXfsCountToken = "{{cellXfsCount}}";
-
- internal static readonly string DefaultStylesXml = $@"
-
-
-
- {NumFmtsToken}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {cellXfsToken}
-
-
-
-
-";
-
- #endregion
-
internal static readonly string DefaultWorkbookXml = @"
@@ -251,51 +104,6 @@ namespace MiniExcelLibs.OpenXml.Constants
internal static string Sheet(SheetDto sheetDto, int sheetId)
=> $@"";
- internal static string SetupStyleXml(string styleXml, ICollection columns)
- {
- const int numFmtIndex = 166;
-
- var sb = new StringBuilder(styleXml);
- var columnsToApply = GenerateStyleIds(columns);
-
- var numFmts = columnsToApply.Select((x, i) =>
- {
- return new
- {
- numFmt =
-$@"",
-
- cellXfs =
-$@"
-
-
-"
- };
- }).ToArray();
-
- sb.Replace(NumFmtsToken, string.Join(string.Empty, numFmts.Select(x => x.numFmt)));
- sb.Replace(NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString());
-
- sb.Replace(cellXfsToken, string.Join(string.Empty, numFmts.Select(x => x.cellXfs)));
- sb.Replace(cellXfsCountToken, (5 + numFmts.Length).ToString());
- return sb.ToString();
- }
-
- private static IEnumerable GenerateStyleIds(ICollection dynamicColumns)
- {
- if (dynamicColumns == null)
- yield break;
-
- int index = 0;
- foreach (var g in dynamicColumns?.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format))
- {
- foreach (var col in g)
- col.FormatId = startUpCellXfs + index;
-
- yield return g.First();
- index++;
- }
- }
-
}
+
}
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
index 3ec3283..007e9b1 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs
@@ -1,6 +1,7 @@
using MiniExcelLibs.Attributes;
using MiniExcelLibs.OpenXml.Constants;
using MiniExcelLibs.OpenXml.Models;
+using MiniExcelLibs.OpenXml.Styles;
using MiniExcelLibs.Utils;
using MiniExcelLibs.Zip;
using System;
@@ -364,9 +365,9 @@ namespace MiniExcelLibs.OpenXml
switch (_configuration.TableStyles)
{
case TableStyles.None:
- return ExcelXml.SetupStyleXml(ExcelXml.NoneStylesXml, columns);
+ return new MinimalSheetStyleBuilder().Build( columns);
case TableStyles.Default:
- return ExcelXml.SetupStyleXml(ExcelXml.DefaultStylesXml, columns);
+ return new DefaultSheetStyleBuilder().Build( columns );
default:
return string.Empty;
}
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs
index 187382a..40cb8bc 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs
@@ -105,7 +105,7 @@ namespace MiniExcelLibs.OpenXml
}
}
- private List XRowInfos { get; set; }
+ private List XRowInfos { get; set; }
private readonly List CalcChainCellRefs = new List();
@@ -691,9 +691,9 @@ namespace MiniExcelLibs.OpenXml
var mergeBaseRowIndex = newRowIndex;
newRowIndex += rowInfo.IEnumerableMercell?.Height ?? 1;
- // replace formulas
- ProcessFormulas( rowXml, newRowIndex );
- writer.Write(CleanXml( rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
+ // replace formulas
+ ProcessFormulas( rowXml, newRowIndex );
+ writer.Write(CleanXml( rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
//mergecells
if (rowInfo.RowMercells != null)
@@ -757,9 +757,9 @@ namespace MiniExcelLibs.OpenXml
.Replace($"{{{{$enumrowend}}}}", enumrowend.ToString())
.AppendFormat("{0}>", row.Name);
- ProcessFormulas( rowXml, newRowIndex );
+ ProcessFormulas( rowXml, newRowIndex );
- writer.Write(CleanXml( rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
+ writer.Write(CleanXml( rowXml, endPrefix)); // pass StringBuilder for netcoreapp3.0 or above
//mergecells
if (rowInfo.RowMercells != null)
@@ -805,48 +805,48 @@ namespace MiniExcelLibs.OpenXml
return;
}
- XmlReaderSettings settings = new XmlReaderSettings { NameTable = _ns.NameTable };
- XmlParserContext context = new XmlParserContext( null, _ns, "", XmlSpace.Default );
- XmlReader reader = XmlReader.Create( new StringReader( rowXmlString ), settings, context );
+ XmlReaderSettings settings = new XmlReaderSettings { NameTable = _ns.NameTable };
+ XmlParserContext context = new XmlParserContext( null, _ns, "", XmlSpace.Default );
+ XmlReader reader = XmlReader.Create( new StringReader( rowXmlString ), settings, context );
XmlDocument d = new XmlDocument();
d.Load( reader );
var row = d.FirstChild as XmlElement;
- // convert cells starting with '$=' into formulas
- var cs = row.SelectNodes( $"x:c", _ns );
- for ( var ci = 0; ci < cs.Count; ci++ )
+ // convert cells starting with '$=' into formulas
+ var cs = row.SelectNodes( $"x:c", _ns );
+ for ( var ci = 0; ci < cs.Count; ci++ )
{
- var c = cs.Item( ci ) as XmlElement;
- if ( c == null ) {
- continue;
- }
- /* Target:
-
- SUM(C2:C7)
-
- */
- var vs = c.SelectNodes( $"x:v", _ns );
- foreach ( XmlElement v in vs )
+ var c = cs.Item( ci ) as XmlElement;
+ if ( c == null ) {
+ continue;
+ }
+ /* Target:
+
+ SUM(C2:C7)
+
+ */
+ var vs = c.SelectNodes( $"x:v", _ns );
+ foreach ( XmlElement v in vs )
{
- if ( !v.InnerText.StartsWith( "$=" ) )
+ if ( !v.InnerText.StartsWith( "$=" ) )
{
- continue;
- }
- var fNode = c.OwnerDocument.CreateElement( "f", Config.SpreadsheetmlXmlns );
- fNode.InnerText = v.InnerText.Substring( 2 );
- c.InsertBefore( fNode, v );
- c.RemoveChild( v );
+ continue;
+ }
+ var fNode = c.OwnerDocument.CreateElement( "f", Config.SpreadsheetmlXmlns );
+ fNode.InnerText = v.InnerText.Substring( 2 );
+ c.InsertBefore( fNode, v );
+ c.RemoveChild( v );
- var celRef = ExcelOpenXmlUtils.ConvertXyToCell( ci + 1, rowIndex );
- CalcChainCellRefs.Add( celRef );
+ var celRef = ExcelOpenXmlUtils.ConvertXyToCell( ci + 1, rowIndex );
+ CalcChainCellRefs.Add( celRef );
- }
- }
+ }
+ }
rowXml.Clear();
- rowXml.Append( row.OuterXml );
- }
+ rowXml.Append( row.OuterXml );
+ }
private static string ConvertToDateTimeString(KeyValuePair propInfo, object cellValue)
{
diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs
index f22f39e..946606f 100644
--- a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs
+++ b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs
@@ -9,6 +9,8 @@
#endif
static class ExcelOpenXmlUtils
{
+ public static string MinifyXml( string xml ) => xml.Replace( "\r", "" ).Replace( "\n", "" ).Replace( "\t", "" ).Trim();
+
///
/// Encode to XML (special characteres: ' " > < &)
///
diff --git a/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs b/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs
new file mode 100644
index 0000000..5468af0
--- /dev/null
+++ b/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs
@@ -0,0 +1,156 @@
+using MiniExcelLibs.Attributes;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MiniExcelLibs.OpenXml.Styles {
+
+ public class DefaultSheetStyleBuilder : ISheetStyleBuilder
+ {
+ private const int startUpNumFmts = 1;
+ private const string NumFmtsToken = "{{numFmts}}";
+ private const string NumFmtsCountToken = "{{numFmtCount}}";
+
+ private const int startUpCellXfs = 5;
+ private const string cellXfsToken = "{{cellXfs}}";
+ private const string cellXfsCountToken = "{{cellXfsCount}}";
+
+ internal static readonly string DefaultStylesXml = ExcelOpenXmlUtils.MinifyXml
+ ( $@"
+
+
+
+
+ {NumFmtsToken}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {cellXfsToken}
+
+
+
+
+ "
+ );
+
+ public string Build( ICollection columns )
+ {
+ const int numFmtIndex = 166;
+
+ var sb = new StringBuilder( DefaultStylesXml );
+ var columnsToApply = SheetStyleBuilderHelper.GenerateStyleIds( startUpCellXfs, columns );
+
+ var numFmts = columnsToApply.Select( ( x, i ) =>
+ {
+ return new
+ {
+ numFmt = $@"",
+
+ cellXfs = $@"
+
+
+"
+ };
+ } ).ToArray();
+
+ sb.Replace( NumFmtsToken, string.Join( string.Empty, numFmts.Select( x => x.numFmt ) ) );
+ sb.Replace( NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString() );
+
+ sb.Replace( cellXfsToken, string.Join( string.Empty, numFmts.Select( x => x.cellXfs ) ) );
+ sb.Replace( cellXfsCountToken, (5 + numFmts.Length).ToString() );
+ return sb.ToString();
+ }
+ }
+
+}
diff --git a/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs b/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs
new file mode 100644
index 0000000..5be8dca
--- /dev/null
+++ b/src/MiniExcel/OpenXml/Styles/ISheetStyleBuilder.cs
@@ -0,0 +1,10 @@
+using MiniExcelLibs.Attributes;
+using System.Collections.Generic;
+
+namespace MiniExcelLibs.OpenXml.Styles {
+ public interface ISheetStyleBuilder
+ {
+ string Build( ICollection columns );
+ }
+
+}
diff --git a/src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs b/src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs
new file mode 100644
index 0000000..ec7e87f
--- /dev/null
+++ b/src/MiniExcel/OpenXml/Styles/MinimalSheetStyleBuilder.cs
@@ -0,0 +1,71 @@
+using MiniExcelLibs.Attributes;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MiniExcelLibs.OpenXml.Styles {
+ public class MinimalSheetStyleBuilder : ISheetStyleBuilder
+ {
+ private const int startUpNumFmts = 1;
+ private const string NumFmtsToken = "{{numFmts}}";
+ private const string NumFmtsCountToken = "{{numFmtCount}}";
+
+ private const int startUpCellXfs = 5;
+ private const string cellXfsToken = "{{cellXfs}}";
+ private const string cellXfsCountToken = "{{cellXfsCount}}";
+
+ internal static readonly string NoneStylesXml = ExcelOpenXmlUtils.MinifyXml
+ ( $@"
+
+
+
+
+ {NumFmtsToken}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {cellXfsToken}
+
+ "
+ );
+
+ public string Build( ICollection columns )
+ {
+ const int numFmtIndex = 166;
+
+ var sb = new StringBuilder( NoneStylesXml );
+ var columnsToApply = SheetStyleBuilderHelper.GenerateStyleIds( startUpCellXfs, columns );
+
+ var numFmts = columnsToApply.Select( ( x, i ) => {
+ return new {
+ numFmt = $@"",
+ cellXfs = $@""
+ };
+ } ).ToArray();
+
+ sb.Replace( NumFmtsToken, string.Join( string.Empty, numFmts.Select( x => x.numFmt ) ) );
+ sb.Replace( NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString() );
+
+ sb.Replace( cellXfsToken, string.Join( string.Empty, numFmts.Select( x => x.cellXfs ) ) );
+ sb.Replace( cellXfsCountToken, (5 + numFmts.Length).ToString() );
+ return sb.ToString();
+ }
+ }
+
+}
diff --git a/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs b/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs
new file mode 100644
index 0000000..8d171ed
--- /dev/null
+++ b/src/MiniExcel/OpenXml/Styles/SheetStyleBuilderHelper.cs
@@ -0,0 +1,24 @@
+using MiniExcelLibs.Attributes;
+using MiniExcelLibs.Utils;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MiniExcelLibs.OpenXml.Styles {
+ public static class SheetStyleBuilderHelper
+ {
+ public static IEnumerable GenerateStyleIds( int startUpCellXfs, ICollection dynamicColumns ) {
+ if ( dynamicColumns == null )
+ yield break;
+
+ int index = 0;
+ foreach ( var g in dynamicColumns?.Where( x => !string.IsNullOrWhiteSpace( x.Format ) && new ExcelNumberFormat( x.Format ).IsValid ).GroupBy( x => x.Format ) ) {
+ foreach ( var col in g )
+ col.FormatId = startUpCellXfs + index;
+
+ yield return g.First();
+ index++;
+ }
+ }
+ }
+
+}
diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs
index e849293..b3ab18e 100644
--- a/tests/MiniExcelTests/MiniExcelIssueTests.cs
+++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs
@@ -1,4 +1,4 @@
-using Dapper;
+using Dapper;
using MiniExcelLibs.Attributes;
using MiniExcelLibs.Csv;
using MiniExcelLibs.Exceptions;
@@ -3692,5 +3692,47 @@ MyProperty4,MyProperty1,MyProperty5,MyProperty2,MyProperty6,,MyProperty3
}
+ [Fact]
+ public void Issue632_1()
+ {
+ //https://github.com/mini-software/MiniExcel/issues/632
+ var values = new List>();
+
+ foreach ( var item in Enumerable.Range( 1, 100 ) ) {
+ var dict = new Dictionary
+ {
+ { "Id", item },
+ { "Time", DateTime.Now.ToLocalTime() },
+ { "CPU Usage (%)", Math.Round( 56.345, 1 ) },
+ { "Memory Usage (%)", Math.Round( 98.234, 1 ) },
+ { "Disk Usage (%)", Math.Round( 32.456, 1 ) },
+ { "CPU Temperature (°C)", Math.Round( 74.234, 1 ) },
+ { "Voltage (V)", Math.Round( 6.3223, 1 ) },
+ { "Network Usage (Kb/s)", Math.Round( 4503.23422, 1 ) },
+ { "Instrument", "QT800050" }
+ };
+ values.Add( dict );
+ }
+
+ var config = new OpenXmlConfiguration
+ {
+ TableStyles = TableStyles.None,
+ DynamicColumns = new DynamicExcelColumn[]
+ {
+ //new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern + " " + CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern },
+ //new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = CultureInfo.InvariantCulture.DateTimeFormat.ShortDatePattern + " " + CultureInfo.InvariantCulture.DateTimeFormat.LongTimePattern },
+ //new DynamicExcelColumn("Time") { Index = 0, Width = 20 },
+ new DynamicExcelColumn("Time") { Index = 0, Width = 20, Format = "d.MM.yyyy" },
+ }
+ };
+
+ var path = Path.Combine(
+ Path.GetTempPath(),
+ string.Concat( nameof( MiniExcelIssueTests ), "_", nameof( Issue632_1 ), ".xlsx" )
+ );
+
+ MiniExcel.SaveAs( path, values, excelType: ExcelType.XLSX, configuration: config, overwriteFile: true );
+
+ }
}
}
\ No newline at end of file
diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
index ecf4fd0..b7dd134 100644
--- a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
+++ b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs
@@ -29,10 +29,10 @@ namespace MiniExcelLibs.Tests
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() });