Fix Issue 632, refactor sheet styles (#640)

* fix for issue 606

* fix formatting

* add test

* refactor sheet styles, fix #632

* change tabs to spaces
This commit is contained in:
meld-cp 2024-07-23 01:32:04 +12:00 committed by GitHub
parent 228b3c180f
commit 67e97f36b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 355 additions and 241 deletions

View File

@ -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 = $@"<?xml version=""1.0"" encoding=""utf-8""?><x:worksheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main""><x:dimension ref=""A1""/><x:sheetData></x:sheetData></x:worksheet>";
@ -35,146 +28,6 @@ namespace MiniExcelLibs.OpenXml.Constants
<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"" Target=""/xl/sharedStrings.xml"" Id=""R3db9602ace778fdb"" />
</Relationships>";
internal static readonly string NoneStylesXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:fonts>
<x:font />
</x:fonts>
<x:fills>
<x:fill />
</x:fills>
<x:borders>
<x:border />
</x:borders>
<x:cellStyleXfs>
<x:xf />
</x:cellStyleXfs>
<x:cellXfs>
<x:xf />
<x:xf />
<x:xf />
<x:xf numFmtId=""14"" applyNumberFormat=""1"" />
</x:cellXfs>
</x:styleSheet>";
#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 = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:numFmts count=""{NumFmtsCountToken}"">
<x:numFmt numFmtId=""0"" formatCode="""" />
{NumFmtsToken}
</x:numFmts>
<x:fonts count=""2"">
<x:font>
<x:vertAlign val=""baseline"" />
<x:sz val=""11"" />
<x:color rgb=""FF000000"" />
<x:name val=""Calibri"" />
<x:family val=""2"" />
</x:font>
<x:font>
<x:vertAlign val=""baseline"" />
<x:sz val=""11"" />
<x:color rgb=""FFFFFFFF"" />
<x:name val=""Calibri"" />
<x:family val=""2"" />
</x:font>
</x:fonts>
<x:fills count=""3"">
<x:fill>
<x:patternFill patternType=""none"" />
</x:fill>
<x:fill>
<x:patternFill patternType=""gray125"" />
</x:fill>
<x:fill>
<x:patternFill patternType=""solid"">
<x:fgColor rgb=""284472C4"" />
</x:patternFill>
</x:fill>
</x:fills>
<x:borders count=""2"">
<x:border diagonalUp=""0"" diagonalDown=""0"">
<x:left style=""none"">
<x:color rgb=""FF000000"" />
</x:left>
<x:right style=""none"">
<x:color rgb=""FF000000"" />
</x:right>
<x:top style=""none"">
<x:color rgb=""FF000000"" />
</x:top>
<x:bottom style=""none"">
<x:color rgb=""FF000000"" />
</x:bottom>
<x:diagonal style=""none"">
<x:color rgb=""FF000000"" />
</x:diagonal>
</x:border>
<x:border diagonalUp=""0"" diagonalDown=""0"">
<x:left style=""thin"">
<x:color rgb=""FF000000"" />
</x:left>
<x:right style=""thin"">
<x:color rgb=""FF000000"" />
</x:right>
<x:top style=""thin"">
<x:color rgb=""FF000000"" />
</x:top>
<x:bottom style=""thin"">
<x:color rgb=""FF000000"" />
</x:bottom>
<x:diagonal style=""none"">
<x:color rgb=""FF000000"" />
</x:diagonal>
</x:border>
</x:borders>
<x:cellStyleXfs count=""3"">
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""0"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""14"" fontId=""1"" fillId=""2"" borderId=""1"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
</x:cellStyleXfs>
<x:cellXfs count=""{cellXfsCountToken}"">
<x:xf></x:xf>
<x:xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""left"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""14"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1"" applyAlignment=""1"">
<x:alignment horizontal=""fill""/>
</x:xf>
{cellXfsToken}
</x:cellXfs>
<x:cellStyles count=""1"">
<x:cellStyle name=""Normal"" xfId=""0"" builtinId=""0"" />
</x:cellStyles>
</x:styleSheet>";
#endregion
internal static readonly string DefaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:workbook xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""
xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
@ -251,51 +104,6 @@ namespace MiniExcelLibs.OpenXml.Constants
internal static string Sheet(SheetDto sheetDto, int sheetId)
=> $@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(sheetDto.Name)}"" sheetId=""{sheetId}""{(string.IsNullOrWhiteSpace(sheetDto.State) ? string.Empty : $" state=\"{sheetDto.State}\"")} r:id=""{sheetDto.ID}"" />";
internal static string SetupStyleXml(string styleXml, ICollection<ExcelColumnAttribute> columns)
{
const int numFmtIndex = 166;
var sb = new StringBuilder(styleXml);
var columnsToApply = GenerateStyleIds(columns);
var numFmts = columnsToApply.Select((x, i) =>
{
return new
{
numFmt =
$@"<x:numFmt numFmtId=""{numFmtIndex + i}"" formatCode=""{x.Format}"" />",
cellXfs =
$@"<x:xf numFmtId=""{numFmtIndex + i}"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>"
};
}).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<ExcelColumnAttribute> GenerateStyleIds(ICollection<ExcelColumnAttribute> 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++;
}
}
}
}

View File

@ -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;
}

View File

@ -105,7 +105,7 @@ namespace MiniExcelLibs.OpenXml
}
}
private List<XRowInfo> XRowInfos { get; set; }
private List<XRowInfo> XRowInfos { get; set; }
private readonly List<string> CalcChainCellRefs = new List<string>();
@ -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:
<c r="C8" s="3">
<f>SUM(C2:C7)</f>
</c>
*/
var vs = c.SelectNodes( $"x:v", _ns );
foreach ( XmlElement v in vs )
var c = cs.Item( ci ) as XmlElement;
if ( c == null ) {
continue;
}
/* Target:
<c r="C8" s="3">
<f>SUM(C2:C7)</f>
</c>
*/
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<string, PropInfo> propInfo, object cellValue)
{

View File

@ -9,6 +9,8 @@
#endif
static class ExcelOpenXmlUtils
{
public static string MinifyXml( string xml ) => xml.Replace( "\r", "" ).Replace( "\n", "" ).Replace( "\t", "" ).Trim();
/// <summary>
/// Encode to XML (special characteres: &apos; &quot; &gt; &lt; &amp;)
/// </summary>

View File

@ -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
( $@"
<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:numFmts count=""{NumFmtsCountToken}"">
<x:numFmt numFmtId=""0"" formatCode="""" />
{NumFmtsToken}
</x:numFmts>
<x:fonts count=""2"">
<x:font>
<x:vertAlign val=""baseline"" />
<x:sz val=""11"" />
<x:color rgb=""FF000000"" />
<x:name val=""Calibri"" />
<x:family val=""2"" />
</x:font>
<x:font>
<x:vertAlign val=""baseline"" />
<x:sz val=""11"" />
<x:color rgb=""FFFFFFFF"" />
<x:name val=""Calibri"" />
<x:family val=""2"" />
</x:font>
</x:fonts>
<x:fills count=""3"">
<x:fill>
<x:patternFill patternType=""none"" />
</x:fill>
<x:fill>
<x:patternFill patternType=""gray125"" />
</x:fill>
<x:fill>
<x:patternFill patternType=""solid"">
<x:fgColor rgb=""284472C4"" />
</x:patternFill>
</x:fill>
</x:fills>
<x:borders count=""2"">
<x:border diagonalUp=""0"" diagonalDown=""0"">
<x:left style=""none"">
<x:color rgb=""FF000000"" />
</x:left>
<x:right style=""none"">
<x:color rgb=""FF000000"" />
</x:right>
<x:top style=""none"">
<x:color rgb=""FF000000"" />
</x:top>
<x:bottom style=""none"">
<x:color rgb=""FF000000"" />
</x:bottom>
<x:diagonal style=""none"">
<x:color rgb=""FF000000"" />
</x:diagonal>
</x:border>
<x:border diagonalUp=""0"" diagonalDown=""0"">
<x:left style=""thin"">
<x:color rgb=""FF000000"" />
</x:left>
<x:right style=""thin"">
<x:color rgb=""FF000000"" />
</x:right>
<x:top style=""thin"">
<x:color rgb=""FF000000"" />
</x:top>
<x:bottom style=""thin"">
<x:color rgb=""FF000000"" />
</x:bottom>
<x:diagonal style=""none"">
<x:color rgb=""FF000000"" />
</x:diagonal>
</x:border>
</x:borders>
<x:cellStyleXfs count=""3"">
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""0"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""14"" fontId=""1"" fillId=""2"" borderId=""1"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
</x:cellStyleXfs>
<x:cellXfs count=""{cellXfsCountToken}"">
<x:xf></x:xf>
<x:xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""0"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""left"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""14"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>
<x:xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyBorder=""1"" applyAlignment=""1"">
<x:alignment horizontal=""fill""/>
</x:xf>
{cellXfsToken}
</x:cellXfs>
<x:cellStyles count=""1"">
<x:cellStyle name=""Normal"" xfId=""0"" builtinId=""0"" />
</x:cellStyles>
</x:styleSheet>"
);
public string Build( ICollection<ExcelColumnAttribute> 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 = $@"<x:numFmt numFmtId=""{numFmtIndex + i}"" formatCode=""{x.Format}"" />",
cellXfs = $@"<x:xf numFmtId=""{numFmtIndex + i}"" fontId=""0"" fillId=""0"" borderId=""1"" xfId=""0"" applyNumberFormat=""1"" applyFill=""1"" applyBorder=""1"" applyAlignment=""1"" applyProtection=""1"">
<x:alignment horizontal=""general"" vertical=""bottom"" textRotation=""0"" wrapText=""0"" indent=""0"" relativeIndent=""0"" justifyLastLine=""0"" shrinkToFit=""0"" readingOrder=""0"" />
<x:protection locked=""1"" hidden=""0"" />
</x:xf>"
};
} ).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();
}
}
}

View File

@ -0,0 +1,10 @@
using MiniExcelLibs.Attributes;
using System.Collections.Generic;
namespace MiniExcelLibs.OpenXml.Styles {
public interface ISheetStyleBuilder
{
string Build( ICollection<ExcelColumnAttribute> columns );
}
}

View File

@ -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
( $@"
<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:numFmts count=""{NumFmtsCountToken}"">
<x:numFmt numFmtId=""0"" formatCode="""" />
{NumFmtsToken}
</x:numFmts>
<x:fonts>
<x:font />
</x:fonts>
<x:fills>
<x:fill />
</x:fills>
<x:borders>
<x:border />
</x:borders>
<x:cellStyleXfs>
<x:xf />
</x:cellStyleXfs>
<x:cellXfs count=""{cellXfsCountToken}"">
<x:xf />
<x:xf />
<x:xf />
<x:xf numFmtId=""14"" applyNumberFormat=""1"" />
<x:xf />
{cellXfsToken}
</x:cellXfs>
</x:styleSheet>"
);
public string Build( ICollection<ExcelColumnAttribute> 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 = $@"<x:numFmt numFmtId=""{numFmtIndex + i}"" formatCode=""{x.Format}"" />",
cellXfs = $@"<x:xf numFmtId=""{numFmtIndex + i}"" applyNumberFormat=""1"" />"
};
} ).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();
}
}
}

View File

@ -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<ExcelColumnAttribute> GenerateStyleIds( int startUpCellXfs, ICollection<ExcelColumnAttribute> 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++;
}
}
}
}

View File

@ -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<Dictionary<string, object>>();
foreach ( var item in Enumerable.Range( 1, 100 ) ) {
var dict = new Dictionary<string, object>
{
{ "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 );
}
}
}

View File

@ -29,10 +29,10 @@ namespace MiniExcelLibs.Tests
string path = GetTempXlsxPath();
char[] chars = new char[] {'\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008',
'\u0009', //<HT>
'\u000A', //<LF>
'\u000B','\u000C',
'\u000A', //<LF>
'\u000B','\u000C',
'\u000D', //<CR>
'\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() });