Skip to content

Commit

Permalink
Fix Issue 632, refactor sheet styles (#640)
Browse files Browse the repository at this point in the history
* fix for issue 606

* fix formatting

* add test

* refactor sheet styles, fix #632

* change tabs to spaces
  • Loading branch information
meld-cp authored Jul 22, 2024
1 parent 228b3c1 commit 67e97f3
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 243 deletions.
206 changes: 7 additions & 199 deletions src/MiniExcel/OpenXml/Constants/ExcelXml.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
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
{
internal static class ExcelXml
{
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>";

Expand All @@ -35,146 +28,6 @@ static ExcelXml()
<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"">
Expand Down Expand Up @@ -251,51 +104,6 @@ internal static string DrawingXml(FileDto file, int fileIndex)
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++;
}
}

}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -364,9 +365,9 @@ private string GetStylesXml(ICollection<ExcelColumnAttribute> columns)
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;
}
Expand Down
76 changes: 38 additions & 38 deletions src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public string ToXmlString(string prefix)
}
}

private List<XRowInfo> XRowInfos { get; set; }
private List<XRowInfo> XRowInfos { get; set; }

private readonly List<string> CalcChainCellRefs = new List<string>();

Expand Down Expand Up @@ -691,9 +691,9 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo
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)
Expand Down Expand Up @@ -757,9 +757,9 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo
.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)
Expand Down Expand Up @@ -805,48 +805,48 @@ private void ProcessFormulas( StringBuilder rowXml, int rowIndex )
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 );

var celRef = ExcelOpenXmlUtils.ConvertXyToCell( ci + 1, rowIndex );
CalcChainCellRefs.Add( celRef );

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

}
}
rowXml.Clear();
rowXml.Append( row.OuterXml );
}
rowXml.Append( row.OuterXml );
}

private static string ConvertToDateTimeString(KeyValuePair<string, PropInfo> propInfo, object cellValue)
{
Expand Down
2 changes: 2 additions & 0 deletions src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Loading

0 comments on commit 67e97f3

Please sign in to comment.