From a264f6078021019ae1857c6c70bf36830042cb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 24 Feb 2026 15:20:28 +0100 Subject: [PATCH 1/3] Fixed reading of table dxfstyles in VBA --- src/EPPlus/Constants/ExtLstUris.cs | 10 +- src/EPPlus/Style/Dxf/DxfStyleHandler.cs | 9 ++ src/EPPlus/Style/Dxf/ExcelDxfStyle.cs | 3 +- src/EPPlus/Table/ExcelTableDxfBase.cs | 3 + src/EPPlus/XmlHelper.cs | 33 +++++ src/EPPlusTest/VBA/VBATests.cs | 154 ++++++++++++++++++++++++ 6 files changed, 210 insertions(+), 2 deletions(-) diff --git a/src/EPPlus/Constants/ExtLstUris.cs b/src/EPPlus/Constants/ExtLstUris.cs index fd78aeda61..d000ca8ef1 100644 --- a/src/EPPlus/Constants/ExtLstUris.cs +++ b/src/EPPlus/Constants/ExtLstUris.cs @@ -56,8 +56,16 @@ internal class ExtLstUris internal const string ConditionalFormattingUri = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"; internal const string ExtChildUri = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"; - //Feature property bag refrence used for checkboxes is styling. + //Feature property bag refrence used for checkboxes is styling. (xfComplementXf) + /// + /// NOT FOR DXF + /// internal const string FeaturePropertyBag = "{C7286773-470A-42A8-94C5-96B5CB345126}"; + //dxfComplementExt + /// + /// FOR DXF + /// + internal const string FeaturePropertyBagDxf = "{0417FA29-78FA-4A13-93AC-8FF0FAFDF519}"; internal const string Connection2010Uri = "{DE250136-89BD-433C-8126-D09CA5730AF9}"; } diff --git a/src/EPPlus/Style/Dxf/DxfStyleHandler.cs b/src/EPPlus/Style/Dxf/DxfStyleHandler.cs index e6539e3093..a6c2ee7ad9 100644 --- a/src/EPPlus/Style/Dxf/DxfStyleHandler.cs +++ b/src/EPPlus/Style/Dxf/DxfStyleHandler.cs @@ -136,6 +136,15 @@ private static void UpdateDxfXmlTables(ExcelStyles styles, XmlNode dxfsNode, Exc foreach (var column in tbl.Columns) { column.HeaderRowDxfId = AddDxfNode(styles.Dxfs, dxfsNode, column.HeaderRowStyle); + + //if (column.DataDxfId.HasValue && column.DataStyle.DxfId == column.DataDxfId) + //{ + // //This is a no-op. The col already has the correct DataDxfId + //} + //else + //{ + // column.DataDxfId = AddDxfNode(styles.Dxfs, dxfsNode, column.DataStyle); + //} column.DataDxfId = AddDxfNode(styles.Dxfs, dxfsNode, column.DataStyle); column.TotalsRowDxfId = AddDxfNode(styles.Dxfs, dxfsNode, column.TotalsRowStyle); } diff --git a/src/EPPlus/Style/Dxf/ExcelDxfStyle.cs b/src/EPPlus/Style/Dxf/ExcelDxfStyle.cs index f74feaae82..64955bd68c 100644 --- a/src/EPPlus/Style/Dxf/ExcelDxfStyle.cs +++ b/src/EPPlus/Style/Dxf/ExcelDxfStyle.cs @@ -35,6 +35,7 @@ internal ExcelDxfStyle(XmlNamespaceManager nameSpaceManager, XmlNode topNode, Ex Font.SetValuesFromXml(_helper); Alignment.SetValuesFromXml(_helper); Protection.SetValuesFromXml(_helper); + Checkbox = _helper.ExistsNode($"d:extLst/d:ext[@uri='{ExtLstUris.FeaturePropertyBagDxf}']/xfpb:DXFComplement"); } } /// @@ -122,7 +123,7 @@ internal override void CreateNodes(XmlHelper helper, string path) var cmplNode = (XmlElement)helper.CreateNode("d:extLst/d:ext/xfpb:DXFComplement"); var extNode = (XmlElement)cmplNode.ParentNode; extNode.SetAttribute("xmlns:xfpb", Schemas.schemaFeaturePropertyBag); - extNode.SetAttribute("uri", ExtLstUris.FeaturePropertyBag); + extNode.SetAttribute("uri", ExtLstUris.FeaturePropertyBagDxf); cmplNode.SetAttribute("i", "0"); _styles._hasDxfCheckbox = true; } diff --git a/src/EPPlus/Table/ExcelTableDxfBase.cs b/src/EPPlus/Table/ExcelTableDxfBase.cs index 96c8872030..2b718105f0 100644 --- a/src/EPPlus/Table/ExcelTableDxfBase.cs +++ b/src/EPPlus/Table/ExcelTableDxfBase.cs @@ -26,6 +26,9 @@ internal void InitDxf(ExcelStyles styles, ExcelTable table, ExcelTableColumn tab _tableColumn = tableColumn; HeaderRowStyle = styles.GetDxf(HeaderRowDxfId, SetHeaderStyle); DataStyle = styles.GetDxf(DataDxfId, SetDataStyle); + + DataStyle.DxfId = DataDxfId.HasValue ? DataDxfId.Value : int.MinValue; + TotalsRowStyle = styles.GetDxf(TotalsRowDxfId, SetTotalsStyle); } internal int? HeaderRowDxfId diff --git a/src/EPPlus/XmlHelper.cs b/src/EPPlus/XmlHelper.cs index 62b753a7b7..74584a47e1 100644 --- a/src/EPPlus/XmlHelper.cs +++ b/src/EPPlus/XmlHelper.cs @@ -24,6 +24,7 @@ Date Author Change using OfficeOpenXml.Packaging.Ionic.Zip; using OfficeOpenXml.Utils.TypeConversion; using OfficeOpenXml.Utils.EnumUtils; +using System.Xml.XPath; namespace OfficeOpenXml { @@ -595,6 +596,38 @@ internal XmlNode GetNode(string path) { return TopNode.SelectSingleNode(path, NameSpaceManager); } + + /// + /// If Get Node doesn't work. + /// Simplified way of applying the default node prefix to empty args. + /// + /// + /// + internal XmlNode GetDefaultNode(string path) + { + var retNode = GetNode(path); + + if (retNode == null) + { + var defaultPrefix = NameSpaceManager.LookupPrefix(NameSpaceManager.DefaultNamespace); + + var splitArgs = path.Split('/'); + + for (int i = 0; i < splitArgs.Length; i++) + { + if (splitArgs[i].Contains(":") == false) + { + //No prefix add default prefix + splitArgs[i] = splitArgs[i].Insert(0, defaultPrefix + ":"); + } + } + var alteredExp = string.Join("/", splitArgs.ToArray()); + retNode = GetNode(alteredExp); + } + + return retNode; + } + internal XmlNodeList GetNodes(string path) { return TopNode.SelectNodes(path, NameSpaceManager); diff --git a/src/EPPlusTest/VBA/VBATests.cs b/src/EPPlusTest/VBA/VBATests.cs index 423cfcb2dd..a6389948e9 100644 --- a/src/EPPlusTest/VBA/VBATests.cs +++ b/src/EPPlusTest/VBA/VBATests.cs @@ -1,5 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OfficeOpenXml; +using OfficeOpenXml.Constants; +using OfficeOpenXml.Drawing; using OfficeOpenXml.Utils.VBA; using OfficeOpenXml.VBA; using OfficeOpenXml.VBA.ContentHash; @@ -12,6 +14,8 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using System.Xml; +using System.Xml.XPath; namespace EPPlusTest.VBA { @@ -300,5 +304,155 @@ public void VbaModuleNameShouldAllowSpace() var module = package.Workbook.VbaProject.Modules.AddModule("My BubbleChartModule"); module.Code = sb.ToString(); } + + [TestMethod] + public void ReadFile() + { + var fileName = "MyVBACheckboxes"; + var fileEnding = ".xlsm"; + + using (var package = OpenPackage(fileName + fileEnding)) + { + var ws = package.Workbook.Worksheets[0]; + + var tbl = ws.Tables[0]; + var cols = tbl.Columns; + + //Verify style bools + Assert.IsTrue(tbl.DataStyle.Checkbox); + Assert.IsTrue(cols[0].DataStyle.Checkbox); + Assert.IsTrue(cols[1].DataStyle.Checkbox); + + var topNode = cols[0].DataStyle._helper.TopNode; + cols[0].DataStyle._helper.GetDefaultNode("extLst/ext"); + //cols[0].DataStyle._helper.NameSpaceManager.em + //var myNode = cols[0].DataStyle._helper.GetNode("d:extLst/d:ext"); + + //var nsm = cols[0].DataStyle._helper.NameSpaceManager; + + //var dn = nsm.DefaultNamespace; + + //cols[0].DataStyle._helper.NameSpaceManager.RemoveNamespace(nsm.LookupPrefix(dn), dn); + + //var myNode2 = cols[0].DataStyle._helper.GetNode("extLst/ext"); + + //cols[0].DataStyle._helper.NameSpaceManager.RemoveNamespace("", cols[0].DataStyle._helper.NameSpaceManager.DefaultNamespace); + + //var myNode3 = cols[0].DataStyle._helper.CreateNode("extLst/ext"); + + + //List childXml = new(); + + //foreach(XmlNode child in tNode.ChildNodes) + //{ + // childXml.Add(child.OuterXml); + //} + + //cols[0].DataStyle._helper.NameSpaceManager.AddNamespace("xlmns:xfpb", "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"); + + //var str2 = cols[0].DataStyle._helper.TopNode.SelectSingleNode("extLst"); + + //ws.Workbook.Styles + var extNode = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext[@uri='{ExtLstUris.FeaturePropertyBagDxf}']"); + var extNodeCol2 = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext[@uri='{ExtLstUris.FeaturePropertyBagDxf}']"); + + //Verify dxf property bag + Assert.IsTrue(extNode.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); + Assert.IsTrue(extNodeCol2.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); + + var outFile = GetOutputFile("", fileName + "_Resaved" + fileEnding); + package.SaveAs(outFile); + } + } + + [TestMethod] + public void EnsureEpplusReadsXlsmDxfIdsForTablesCorrectly() + { + var fileName = "MyVBACheckboxes"; + var fileEnding = ".xlsm"; + + using (var package = OpenPackage(fileName + fileEnding, true)) + { + var ws = package.Workbook.Worksheets.Add("TableCheckboxes"); + package.Workbook.CreateVBAProject(); + + var tbl = ws.Tables.Add(ws.Cells["B2:C3"], "Table1"); + tbl.ShowHeader = true; + tbl.DataStyle.Checkbox = true; + tbl.Columns[0].DataStyle.Checkbox = true; + tbl.Columns[1].DataStyle.Checkbox = true; + + ws.Cells["B3:C3"].Value = false; + + SaveAndCleanup(package); + } + + using (var package = OpenPackage(fileName + fileEnding)) + { + var ws = package.Workbook.Worksheets[0]; + + var tbl = ws.Tables[0]; + var cols = tbl.Columns; + + //Verify style bools + Assert.IsTrue(tbl.DataStyle.Checkbox); + Assert.IsTrue(cols[0].DataStyle.Checkbox); + Assert.IsTrue(cols[1].DataStyle.Checkbox); + + //Verify dxf property bag + Assert.IsTrue(cols[0].DataStyle._helper.GetXmlNodeString("extLst/ext[@uri]") == ExtLstUris.FeaturePropertyBagDxf); + Assert.IsTrue(cols[1].DataStyle._helper.GetXmlNodeString("extLst/ext[@uri]") == ExtLstUris.FeaturePropertyBagDxf); + + var outFile = GetOutputFile("", fileName + "_Resaved" + fileEnding); + package.SaveAs(outFile); + } + } + + [TestMethod] + public void testRead() + { + using (var package = OpenTemplatePackage("Checkboxes2.xlsm")) + { + //ws.Drawings.AddShape("VBASampleRect", eShapeStyle.RoundRect); + + //var sb = new StringBuilder(); + //sb.AppendLine("Private Sub Workbook_Open()"); + //sb.AppendLine(" [Tabelle1].Shapes(\"VBASampleRect\").TextEffect.Text = \"This text is set from VBA!\""); + //sb.AppendLine("End Sub"); + //package.Workbook.CodeModule.Code = sb.ToString(); + //package.Workbook.CreateVBAProject(); + + //var table = ws.Tables.Add(ws.Cells["B3:C4"], "Table1"); + //table.ShowHeader = true; + //ws.Cells["B4:C4"].Value = false; + //ws.Cells["B4:C4"].Style.Checkbox = true; + + //var ws = package.Workbook.Worksheets[0]; + //var table = ws.Tables[0]; + //table.AddRow(1); + //table.AddRow(1); + //Adjust this if you wish/don't have a temp folder + + //Get the worksheet and the module and some data just to ensure any potential getters are called + //var ws = package.Workbook.Worksheets[0]; + //var cell1 = ws.Cells["A1"]; + //var yourTable = ws.Tables[0]; + //var col1OfTable = ws.Tables[0].Columns[0]; + //var vbaCodeModule = package.Workbook.CodeModule; + + + //string myPath = "C:\\temp\\"; + + + //var ws = package.Workbook.Worksheets[0]; + //var table = ws.Tables[0]; + //var dataDxfId = table.Columns[0].DataDxfId; + + + //DirectoryInfo outputDir = new DirectoryInfo(myPath); + //package.SaveAs(new FileInfo(outputDir.FullName + @"CheckBoxExample.xlsm")); + SaveAndCleanup(package); + } + } } } From a5a083977bc51cd807e4cf6ef7f89df282372ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 24 Feb 2026 15:30:47 +0100 Subject: [PATCH 2/3] Removed unnecesary tests/comments --- src/EPPlusTest/VBA/VBATests.cs | 118 +++------------------------------ 1 file changed, 9 insertions(+), 109 deletions(-) diff --git a/src/EPPlusTest/VBA/VBATests.cs b/src/EPPlusTest/VBA/VBATests.cs index a6389948e9..197b5a6308 100644 --- a/src/EPPlusTest/VBA/VBATests.cs +++ b/src/EPPlusTest/VBA/VBATests.cs @@ -305,66 +305,6 @@ public void VbaModuleNameShouldAllowSpace() module.Code = sb.ToString(); } - [TestMethod] - public void ReadFile() - { - var fileName = "MyVBACheckboxes"; - var fileEnding = ".xlsm"; - - using (var package = OpenPackage(fileName + fileEnding)) - { - var ws = package.Workbook.Worksheets[0]; - - var tbl = ws.Tables[0]; - var cols = tbl.Columns; - - //Verify style bools - Assert.IsTrue(tbl.DataStyle.Checkbox); - Assert.IsTrue(cols[0].DataStyle.Checkbox); - Assert.IsTrue(cols[1].DataStyle.Checkbox); - - var topNode = cols[0].DataStyle._helper.TopNode; - cols[0].DataStyle._helper.GetDefaultNode("extLst/ext"); - //cols[0].DataStyle._helper.NameSpaceManager.em - //var myNode = cols[0].DataStyle._helper.GetNode("d:extLst/d:ext"); - - //var nsm = cols[0].DataStyle._helper.NameSpaceManager; - - //var dn = nsm.DefaultNamespace; - - //cols[0].DataStyle._helper.NameSpaceManager.RemoveNamespace(nsm.LookupPrefix(dn), dn); - - //var myNode2 = cols[0].DataStyle._helper.GetNode("extLst/ext"); - - //cols[0].DataStyle._helper.NameSpaceManager.RemoveNamespace("", cols[0].DataStyle._helper.NameSpaceManager.DefaultNamespace); - - //var myNode3 = cols[0].DataStyle._helper.CreateNode("extLst/ext"); - - - //List childXml = new(); - - //foreach(XmlNode child in tNode.ChildNodes) - //{ - // childXml.Add(child.OuterXml); - //} - - //cols[0].DataStyle._helper.NameSpaceManager.AddNamespace("xlmns:xfpb", "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"); - - //var str2 = cols[0].DataStyle._helper.TopNode.SelectSingleNode("extLst"); - - //ws.Workbook.Styles - var extNode = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext[@uri='{ExtLstUris.FeaturePropertyBagDxf}']"); - var extNodeCol2 = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext[@uri='{ExtLstUris.FeaturePropertyBagDxf}']"); - - //Verify dxf property bag - Assert.IsTrue(extNode.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); - Assert.IsTrue(extNodeCol2.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); - - var outFile = GetOutputFile("", fileName + "_Resaved" + fileEnding); - package.SaveAs(outFile); - } - } - [TestMethod] public void EnsureEpplusReadsXlsmDxfIdsForTablesCorrectly() { @@ -399,60 +339,20 @@ public void EnsureEpplusReadsXlsmDxfIdsForTablesCorrectly() Assert.IsTrue(cols[0].DataStyle.Checkbox); Assert.IsTrue(cols[1].DataStyle.Checkbox); + var topNode = cols[0].DataStyle._helper.TopNode; + cols[0].DataStyle._helper.GetDefaultNode("extLst/ext"); + + //ws.Workbook.Styles + var extNode = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext"); + var extNodeCol2 = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext"); + //Verify dxf property bag - Assert.IsTrue(cols[0].DataStyle._helper.GetXmlNodeString("extLst/ext[@uri]") == ExtLstUris.FeaturePropertyBagDxf); - Assert.IsTrue(cols[1].DataStyle._helper.GetXmlNodeString("extLst/ext[@uri]") == ExtLstUris.FeaturePropertyBagDxf); + Assert.IsTrue(extNode.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); + Assert.IsTrue(extNodeCol2.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); var outFile = GetOutputFile("", fileName + "_Resaved" + fileEnding); package.SaveAs(outFile); } } - - [TestMethod] - public void testRead() - { - using (var package = OpenTemplatePackage("Checkboxes2.xlsm")) - { - //ws.Drawings.AddShape("VBASampleRect", eShapeStyle.RoundRect); - - //var sb = new StringBuilder(); - //sb.AppendLine("Private Sub Workbook_Open()"); - //sb.AppendLine(" [Tabelle1].Shapes(\"VBASampleRect\").TextEffect.Text = \"This text is set from VBA!\""); - //sb.AppendLine("End Sub"); - //package.Workbook.CodeModule.Code = sb.ToString(); - //package.Workbook.CreateVBAProject(); - - //var table = ws.Tables.Add(ws.Cells["B3:C4"], "Table1"); - //table.ShowHeader = true; - //ws.Cells["B4:C4"].Value = false; - //ws.Cells["B4:C4"].Style.Checkbox = true; - - //var ws = package.Workbook.Worksheets[0]; - //var table = ws.Tables[0]; - //table.AddRow(1); - //table.AddRow(1); - //Adjust this if you wish/don't have a temp folder - - //Get the worksheet and the module and some data just to ensure any potential getters are called - //var ws = package.Workbook.Worksheets[0]; - //var cell1 = ws.Cells["A1"]; - //var yourTable = ws.Tables[0]; - //var col1OfTable = ws.Tables[0].Columns[0]; - //var vbaCodeModule = package.Workbook.CodeModule; - - - //string myPath = "C:\\temp\\"; - - - //var ws = package.Workbook.Worksheets[0]; - //var table = ws.Tables[0]; - //var dataDxfId = table.Columns[0].DataDxfId; - - - //DirectoryInfo outputDir = new DirectoryInfo(myPath); - //package.SaveAs(new FileInfo(outputDir.FullName + @"CheckBoxExample.xlsm")); - SaveAndCleanup(package); - } - } } } From 2d2859ef144335f9acdf24d3bd919b6a37a9bf49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 24 Feb 2026 16:23:23 +0100 Subject: [PATCH 3/3] added template test. Reverted unnecesary change --- src/EPPlus/Table/ExcelTableDxfBase.cs | 3 --- src/EPPlusTest/VBA/VBATests.cs | 36 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/EPPlus/Table/ExcelTableDxfBase.cs b/src/EPPlus/Table/ExcelTableDxfBase.cs index 2b718105f0..96c8872030 100644 --- a/src/EPPlus/Table/ExcelTableDxfBase.cs +++ b/src/EPPlus/Table/ExcelTableDxfBase.cs @@ -26,9 +26,6 @@ internal void InitDxf(ExcelStyles styles, ExcelTable table, ExcelTableColumn tab _tableColumn = tableColumn; HeaderRowStyle = styles.GetDxf(HeaderRowDxfId, SetHeaderStyle); DataStyle = styles.GetDxf(DataDxfId, SetDataStyle); - - DataStyle.DxfId = DataDxfId.HasValue ? DataDxfId.Value : int.MinValue; - TotalsRowStyle = styles.GetDxf(TotalsRowDxfId, SetTotalsStyle); } internal int? HeaderRowDxfId diff --git a/src/EPPlusTest/VBA/VBATests.cs b/src/EPPlusTest/VBA/VBATests.cs index 197b5a6308..659d711f41 100644 --- a/src/EPPlusTest/VBA/VBATests.cs +++ b/src/EPPlusTest/VBA/VBATests.cs @@ -305,6 +305,42 @@ public void VbaModuleNameShouldAllowSpace() module.Code = sb.ToString(); } + + [TestMethod] + public void ReadAndSaveTemplateDxfIdsCheckbox() + { + using (var package = OpenTemplatePackage("i2273.xlsx")) + { + var ws = package.Workbook.Worksheets[0]; + SaveAndCleanup(package); + } + + using (var package = OpenPackage("i2273.xlsx")) + { + var ws = package.Workbook.Worksheets[0]; + + var tbl = ws.Tables[0]; + var cols = tbl.Columns; + + //Verify style bools + Assert.IsTrue(cols[0].DataStyle.Checkbox); + Assert.IsTrue(cols[1].DataStyle.Checkbox); + + var topNode = cols[0].DataStyle._helper.TopNode; + cols[0].DataStyle._helper.GetDefaultNode("extLst/ext"); + + //ws.Workbook.Styles + var extNode = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext"); + var extNodeCol2 = (XmlElement)cols[0].DataStyle._helper.GetDefaultNode($"extLst/ext"); + + //Verify dxf property bag + Assert.IsTrue(extNode.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); + Assert.IsTrue(extNodeCol2.GetAttribute("uri") == ExtLstUris.FeaturePropertyBagDxf); + + SaveAndCleanup(package); + } + } + [TestMethod] public void EnsureEpplusReadsXlsmDxfIdsForTablesCorrectly() {