From a79f70a6feee3ac1e2ad1342c4ba1ff83653edf9 Mon Sep 17 00:00:00 2001 From: swmal <897655+swmal@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:05:34 +0200 Subject: [PATCH 1/2] Fix #2572: namespace prefixes and calc attributes in worksheet XML --- .../Worksheet/XmlWriter/WorksheetXmlWriter.cs | 114 +++++------ .../Core/Worksheet/WorksheetXmlWriterTests.cs | 181 ++++++++++++++++++ src/EPPlusTest/EPPlus.Test.csproj | 6 + src/EPPlusTest/Workbooks/sc1083-flags.xlsx | Bin 0 -> 7015 bytes src/EPPlusTest/Workbooks/sc1083-sdk.xlsx | Bin 0 -> 10015 bytes 5 files changed, 247 insertions(+), 54 deletions(-) create mode 100644 src/EPPlusTest/Core/Worksheet/WorksheetXmlWriterTests.cs create mode 100644 src/EPPlusTest/Workbooks/sc1083-flags.xlsx create mode 100644 src/EPPlusTest/Workbooks/sc1083-sdk.xlsx diff --git a/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs b/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs index 28f85fe222..13f1e9550c 100644 --- a/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs +++ b/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs @@ -512,7 +512,7 @@ private void UpdateRowCellData(StreamWriter sw, string prefix) } else { - cache.Append($"<{cTag} r=\"{cse.CellAddress}\" s=\"{styleID}\"{ConvertUtil.GetCellType(v, true)}{mdAttr}>{GetFormulaValue(v, prefix)}"); + cache.Append($"<{cTag} r=\"{cse.CellAddress}\" s=\"{styleID}\"{ConvertUtil.GetCellType(v, true)}{mdAttr}><{fTag} t=\"shared\" si=\"{sfId}\" {mdAttrForFTag}/>{GetFormulaValue(v, prefix)}"); } } else @@ -1154,7 +1154,7 @@ private string UpdateConditionalFormattingAttributes( return cache.ToString(); } - private string GetXMLCFExt(string prefix, ExcelConditionalFormattingRule format, int i, int count) + private string GetXMLCFExt(string prefix, string mainPrefix, ExcelConditionalFormattingRule format, int i, int count) { var cache = new StringBuilder(); @@ -1349,7 +1349,7 @@ private string GetXMLCFExt(string prefix, ExcelConditionalFormattingRule format, cache.Append($"<{prefix}cfRule type=\"iconSet\" priority=\"{format.Priority}\" id=\"{{{uid}}}\">"); cache.Append($"<{prefix}iconSet "); - cache.Append($"iconSet=\"{iconSetString}\""); + cache.Append($"iconSet=\"{iconSetString}\""); if (showValue == false) { @@ -1514,7 +1514,8 @@ private string GetXMLCFExt(string prefix, ExcelConditionalFormattingRule format, if (format.Style.HasValue) { - WriteStyle(cache, prefix, format); + // Sc1083: The dxf children belong to the main schema, so they need the main prefix. + WriteStyle(cache, prefix, mainPrefix, format); } } cache.Append($""); @@ -1578,7 +1579,8 @@ private string UpdateExtLstConditionalFormatting(string mainPrefix) foreach (var rule in pivotCFs) { - cache.Append(GetXMLCFExt(prefix, rule, 0, 1)); + // Sc1083: Pass mainPrefix so elements from the main schema get the worksheet's namespace prefix. + cache.Append(GetXMLCFExt(prefix, mainPrefix, rule, 0, 1)); } foreach (var ruleList in addressDict) @@ -1586,7 +1588,8 @@ private string UpdateExtLstConditionalFormatting(string mainPrefix) for (int i = 0; i < ruleList.Value.Count; i++) { var format = ruleList.Value[i]; - cache.Append(GetXMLCFExt(prefix, ruleList.Value[i], i, ruleList.Value.Count)); + // Sc1083: Pass mainPrefix so elements from the main schema get the worksheet's namespace prefix. + cache.Append(GetXMLCFExt(prefix, mainPrefix, ruleList.Value[i], i, ruleList.Value.Count)); } } @@ -1637,11 +1640,11 @@ string WriteColorOption(string nodeName, ExcelDxfColor color) return returnString; } - private string WriteCfIcon(ExcelConditionalFormattingIconDataBarValue icon, bool gteCheck = true) + private string WriteCfIcon(string prefix, ExcelConditionalFormattingIconDataBarValue icon, bool gteCheck = true) { StringBuilder cache = new StringBuilder(); - cache.Append($""); } - void WriteStyle(StringBuilder cache, string prefix, ExcelConditionalFormattingRule format) + void WriteStyle(StringBuilder cache, string prefix, string mainPrefix, ExcelConditionalFormattingRule format) { + // #2572: dxf itself is in the x14 namespace (prefix), but its children belong to the main schema (mainPrefix). if (format.Style.HasValue) { cache.Append($"<{prefix}dxf>"); if (format.Style.Font.HasValue) { - cache.Append($""); + cache.Append($"<{mainPrefix}font>"); if (format.Style.Font.Bold == true) { - cache.Append($""); + cache.Append($"<{mainPrefix}b/>"); } if (format.Style.Font.Italic == true) { if (format.Style.Font.Bold == false || format.Style.Font.Bold == null) { - cache.Append(""); + cache.Append($"<{mainPrefix}b val=\"0\"/>"); } - cache.Append($""); + cache.Append($"<{mainPrefix}i/>"); } if (format.Style.Font.Bold == false && format.Style.Font.Italic == false) { - cache.Append(""); - cache.Append(""); + cache.Append($"<{mainPrefix}b val=\"0\"/>"); + cache.Append($"<{mainPrefix}i val=\"0\"/>"); } if (format.Style.Font.Strike == true) { - cache.Append($""); + cache.Append($"<{mainPrefix}strike/>"); } if (format.Style.Font.Underline.HasValue == true) { - cache.Append($""); + cache.Append($""); } if (format.Style.NumberFormat.HasValue) { var numberFormat = format.Style.NumberFormat.Format.EncodeXMLAttribute(); - cache.Append($""); } if (format.Style.Fill.HasValue) { - cache.Append(""); + cache.Append($"<{mainPrefix}fill>"); switch (format.Style.Fill.Style) { @@ -2085,28 +2091,28 @@ void WriteStyle(StringBuilder cache, string prefix, ExcelConditionalFormattingRu if (format.Style.Fill.PatternType != null && format.Style.Fill.PatternType != ExcelFillStyle.None) { - cache.Append($""); + cache.Append($"<{mainPrefix}patternFill patternType=\"{format.Style.Fill.PatternType.ToEnumString()}\">"); } else { - cache.Append(""); + cache.Append($"<{mainPrefix}patternFill>"); } if (format.Style.Fill.PatternColor.HasValue) { - cache.Append(WriteColorOption("fgColor", format.Style.Fill.PatternColor)); + cache.Append(WriteColorOption($"{mainPrefix}fgColor", format.Style.Fill.PatternColor)); } if (format.Style.Fill.BackgroundColor.HasValue) { - cache.Append(WriteColorOption("bgColor", format.Style.Fill.BackgroundColor)); + cache.Append(WriteColorOption($"{mainPrefix}bgColor", format.Style.Fill.BackgroundColor)); } - cache.Append(""); + cache.Append($""); break; case Style.eDxfFillStyle.GradientFill: - cache.Append(""); - cache.Append(""); + cache.Append($"<{mainPrefix}stop position=\"0\">"); - cache.Append(WriteColorOption("color", format.Style.Fill.Gradient.Colors[0].Color)); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Fill.Gradient.Colors[0].Color)); - cache.Append(""); - cache.Append(""); + cache.Append($""); + cache.Append($"<{mainPrefix}stop position=\"1.0\">"); - cache.Append(WriteColorOption("color", format.Style.Fill.Gradient.Colors[1].Color)); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Fill.Gradient.Colors[1].Color)); - cache.Append(""); + cache.Append($""); - cache.Append(""); + cache.Append($""); break; } - cache.Append(""); + cache.Append($""); } if (format.Style.Border.HasValue) { - cache.Append(""); + cache.Append($"<{mainPrefix}border>"); if (format.Style.Border.Left.HasValue) { - cache.Append($""); - cache.Append(WriteColorOption("color", format.Style.Border.Left.Color)); - cache.Append(""); + cache.Append($"<{mainPrefix}left style=\"{format.Style.Border.Left.Style.ToString().ToLower()}\">"); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Border.Left.Color)); + cache.Append($""); } if (format.Style.Border.Right.HasValue) { - cache.Append($""); - cache.Append(WriteColorOption("color", format.Style.Border.Right.Color)); - cache.Append(""); + cache.Append($"<{mainPrefix}right style=\"{format.Style.Border.Right.Style.ToString().ToLower()}\">"); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Border.Right.Color)); + cache.Append($""); } if (format.Style.Border.Top.HasValue) { - cache.Append($""); - cache.Append(WriteColorOption("color", format.Style.Border.Top.Color)); - cache.Append(""); + cache.Append($"<{mainPrefix}top style=\"{format.Style.Border.Top.Style.ToString().ToLower()}\">"); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Border.Top.Color)); + cache.Append($""); } if (format.Style.Border.Bottom.HasValue) { - cache.Append($""); - cache.Append(WriteColorOption("color", format.Style.Border.Bottom.Color)); - cache.Append(""); + cache.Append($"<{mainPrefix}bottom style=\"{format.Style.Border.Bottom.Style.ToString().ToLower()}\">"); + cache.Append(WriteColorOption($"{mainPrefix}color", format.Style.Border.Bottom.Color)); + cache.Append($""); } - cache.Append(""); - cache.Append(""); - cache.Append(""); + cache.Append($"<{mainPrefix}vertical/>"); + cache.Append($"<{mainPrefix}horizontal/>"); + cache.Append($""); } cache.Append($""); diff --git a/src/EPPlusTest/Core/Worksheet/WorksheetXmlWriterTests.cs b/src/EPPlusTest/Core/Worksheet/WorksheetXmlWriterTests.cs new file mode 100644 index 0000000000..84b7306642 --- /dev/null +++ b/src/EPPlusTest/Core/Worksheet/WorksheetXmlWriterTests.cs @@ -0,0 +1,181 @@ +/************************************************************************************************* + Required Notice: Copyright (C) EPPlus Software AB. + This software is licensed under PolyForm Noncommercial License 1.0.0 + and may only be used for noncommercial purposes + https://polyformproject.org/licenses/noncommercial/1.0.0/ + + A commercial license to use this software can be purchased at https://epplussoftware.com + ************************************************************************************************* + Date Author Change + ************************************************************************************************* + 10/12/2026 EPPlus Software AB Initial release, tests for issue #2572 + *************************************************************************************************/ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; +using OfficeOpenXml.ConditionalFormatting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; + +namespace EPPlusTest.Core.Worksheet +{ + /// + /// Tests for the XML written by the worksheet XML writer. + /// The prefix tests use a worksheet where the main schema has a namespace prefix (x:) and no default namespace, + /// as written by the Open XML SDK. Any element written without the prefix ends up in no namespace. + /// + [TestClass] + public class WorksheetXmlWriterTests : TestBase + { + private const string PrefixedWorkbook = "sc1083-sdk.xlsx"; + private const string CalcFlagsWorkbook = "sc1083-flags.xlsx"; + private const string WorksheetPart = "xl/worksheets/sheet1.xml"; + private const string SchemaMainX14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"; + + /// + /// Issue #2572: Shared formula children were written as <f> without the namespace prefix, + /// so Excel treated the cells as constants. + /// + [TestMethod] + public void SharedFormulaChildKeepsNamespacePrefix() + { + const string outputName = "WorksheetXmlWriter_SharedFormulaChild.xlsx"; + using (var p = OpenTemplatePackage(PrefixedWorkbook)) + { + SaveWorkbook(outputName, p); + } + + var doc = LoadPartXml(outputName, WorksheetPart); + var nsm = CreateNamespaceManager(doc); + + //Guard: the shared formula children must be written, otherwise the test does not exercise the shared formula path. + Assert.IsNotNull(doc.SelectSingleNode("//m:c[@r='C4']/m:f[@t='shared']", nsm), "C4 has no shared formula in the main namespace."); + Assert.IsNotNull(doc.SelectSingleNode("//m:c[@r='C5']/m:f[@t='shared']", nsm), "C5 has no shared formula in the main namespace."); + AssertNoElementsWithoutNamespace(doc); + } + + /// + /// Issue #2572: Icon set cfvo elements were written without the namespace prefix, + /// so Excel reported the workbook as corrupt. + /// + [TestMethod] + public void IconSetCfvoKeepsNamespacePrefix() + { + const string outputName = "WorksheetXmlWriter_IconSetCfvo.xlsx"; + using (var p = OpenTemplatePackage(PrefixedWorkbook)) + { + var ws = p.Workbook.Worksheets[0]; + ws.ConditionalFormatting.AddThreeIconSet(ws.Cells["A2:A5"], eExcelconditionalFormatting3IconsSetType.Arrows); + SaveWorkbook(outputName, p); + } + + var doc = LoadPartXml(outputName, WorksheetPart); + + //Guard: the icon set must be written, otherwise the test does not exercise the cfvo path. + Assert.IsTrue(doc.GetElementsByTagName("iconSet", ExcelPackage.schemaMain).Count > 0, "No iconSet element was written."); + AssertNoElementsWithoutNamespace(doc); + } + + /// + /// Issue #2572: The children of x14:dxf in extLst conditional formatting were written without the main namespace prefix, + /// so Excel silently dropped the format of the rule. + /// + [TestMethod] + public void ExtLstDxfChildrenKeepNamespacePrefix() + { + const string outputName = "WorksheetXmlWriter_ExtLstDxf.xlsx"; + using (var p = OpenTemplatePackage(PrefixedWorkbook)) + { + var ws = p.Workbook.Worksheets[0]; + var ws2 = p.Workbook.Worksheets.Add("Sheet2"); + ws2.Cells["A1"].Value = 3; + + //A reference to another worksheet forces the rule into extLst (x14). + var cf = ws.ConditionalFormatting.AddExpression(ws.Cells["A2:A5"]); + cf.Formula = "A2>Sheet2!$A$1"; + cf.Style.Font.Bold = true; + cf.Style.Fill.BackgroundColor.Color = System.Drawing.Color.Yellow; + SaveWorkbook(outputName, p); + } + + var doc = LoadPartXml(outputName, WorksheetPart); + + //Guard: the rule must be written to extLst, otherwise the test does not exercise the dxf path. + Assert.IsTrue(doc.GetElementsByTagName("conditionalFormatting", SchemaMainX14).Count > 0, "The rule was not written to extLst."); + Assert.IsTrue(doc.GetElementsByTagName("dxf", SchemaMainX14).Count > 0, "No x14:dxf element was written."); + AssertNoElementsWithoutNamespace(doc); + } + + /// + /// Issue #2572: The aca and ca attributes of an array formula leaked to the following formula cells, + /// as the attribute string was only reset for cells with flags. + /// + [TestMethod] + public void CalcAttributesDoNotLeakToNextFormulaCell() + { + const string outputName = "WorksheetXmlWriter_CalcAttributes.xlsx"; + using (var p = OpenTemplatePackage(CalcFlagsWorkbook)) + { + SaveWorkbook(outputName, p); + } + + var doc = LoadPartXml(outputName, WorksheetPart); + var nsm = CreateNamespaceManager(doc); + + //Guard: the array formula must keep its calc attributes, otherwise there is nothing that can leak. + var arrayFormula = doc.SelectSingleNode("//m:c[@r='D2']/m:f", nsm) as XmlElement; + Assert.IsNotNull(arrayFormula, "D2 has no formula element."); + Assert.AreEqual("1", arrayFormula.GetAttribute("aca"), "D2 lost its aca attribute on load or save."); + + foreach (var address in new[] { "C3", "C4", "C5" }) + { + var formula = doc.SelectSingleNode($"//m:c[@r='{address}']/m:f", nsm) as XmlElement; + Assert.IsNotNull(formula, $"{address} has no formula element."); + Assert.IsFalse(formula.HasAttribute("aca"), $"{address} inherited aca from a previous cell."); + Assert.IsFalse(formula.HasAttribute("ca"), $"{address} inherited ca from a previous cell."); + } + } + + private static XmlNamespaceManager CreateNamespaceManager(XmlDocument doc) + { + var nsm = new XmlNamespaceManager(doc.NameTable); + nsm.AddNamespace("m", ExcelPackage.schemaMain); + return nsm; + } + + private static XmlDocument LoadPartXml(string fileName, string partName) + { + using (var fileStream = File.OpenRead(_worksheetPath + fileName)) + using (var zipStream = new OfficeOpenXml.Packaging.Ionic.Zip.ZipInputStream(fileStream)) + { + OfficeOpenXml.Packaging.Ionic.Zip.ZipEntry entry; + while ((entry = zipStream.GetNextEntry()) != null) + { + if (entry.FileName.Equals(partName, StringComparison.OrdinalIgnoreCase)) + { + var doc = new XmlDocument(); + doc.Load(zipStream); + return doc; + } + } + } + Assert.Fail($"Part {partName} is missing in {fileName}"); + return null; + } + + private static void AssertNoElementsWithoutNamespace(XmlDocument doc) + { + var offenders = new List(); + foreach (XmlElement element in doc.GetElementsByTagName("*")) + { + if (string.IsNullOrEmpty(element.NamespaceURI)) + { + var xml = element.OuterXml; + offenders.Add(xml.Length > 120 ? xml.Substring(0, 120) : xml); + } + } + Assert.AreEqual(0, offenders.Count, "Elements without namespace: " + string.Join(" | ", offenders)); + } + } +} \ No newline at end of file diff --git a/src/EPPlusTest/EPPlus.Test.csproj b/src/EPPlusTest/EPPlus.Test.csproj index f65652daf8..5d47347490 100644 --- a/src/EPPlusTest/EPPlus.Test.csproj +++ b/src/EPPlusTest/EPPlus.Test.csproj @@ -251,6 +251,12 @@ PreserveNewest + + + PreserveNewest + + + PreserveNewest PreserveNewest diff --git a/src/EPPlusTest/Workbooks/sc1083-flags.xlsx b/src/EPPlusTest/Workbooks/sc1083-flags.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d430028c69dc50e3c95e6b271f0e8a0869e2a528 GIT binary patch literal 7015 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 QXD|Rg5630=x4#GYACARjZU6uP literal 0 HcmV?d00001 diff --git a/src/EPPlusTest/Workbooks/sc1083-sdk.xlsx b/src/EPPlusTest/Workbooks/sc1083-sdk.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3556173502672462fcc501241564efbf4ebe0507 GIT binary patch literal 10015 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 Date: Mon, 12 Oct 2026 16:46:28 +0200 Subject: [PATCH 2/2] Small fix in WorksheetXmlWriter --- src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs b/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs index 13f1e9550c..28cea9635f 100644 --- a/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs +++ b/src/EPPlus/Core/Worksheet/XmlWriter/WorksheetXmlWriter.cs @@ -646,12 +646,13 @@ private void UpdateRowCellData(StreamWriter sw, string prefix) private void SetCalcAttributesForArrayFormula(bool hasFlags, CellStoreEnumerator cse, ref string mdAttrForFTag) { + // #2572: Reset so attributes from a previous cell are not carried over to this cell. + mdAttrForFTag = ""; //Set calc attributes for array formula. We preserve them from load only at this point. if (hasFlags) { if (_ws._flags.Exists(cse.Row, cse.Column)) { - mdAttrForFTag = ""; if (_ws._flags.GetFlagValue(cse.Row, cse.Column, CellFlags.CellFlagAlwaysCalculateArray)) { mdAttrForFTag = $" aca=\"1\""; @@ -663,6 +664,7 @@ private void SetCalcAttributesForArrayFormula(bool hasFlags, CellStoreEnumerator } } } + private string GenerateArrayFormulaXml(string cTag, string fTag, string cellAddress, string styleID, string mdAttr, string mdAttrForFTag, string refAddress, object value, string formula, string prefix) { var cellType = ConvertUtil.GetCellType(value, true);