From 987c48160cf83adcb1f9c03d2c81091b957b3307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 6 Feb 2026 16:21:07 +0100 Subject: [PATCH 01/11] Added test. Seems correct --- src/EPPlusTest/Issues/StylingIssues.cs | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/EPPlusTest/Issues/StylingIssues.cs b/src/EPPlusTest/Issues/StylingIssues.cs index bb1598afe5..b9852a4678 100644 --- a/src/EPPlusTest/Issues/StylingIssues.cs +++ b/src/EPPlusTest/Issues/StylingIssues.cs @@ -413,6 +413,50 @@ public void i1839() Assert.AreEqual(288, p.Workbook.Worksheets[0].Cells["E31"].StyleID); SaveWorkbook("i1839-saved.xlsx", p); } + + [TestMethod] + public void s1005() + { + SwitchToCulture("de-DE"); + + if (!ExcelPackageSettings.CultureSpecificBuildInNumberFormats.ContainsKey("de-DE")) + { + ExcelPackageSettings.CultureSpecificBuildInNumberFormats.Add("de-DE", + new Dictionary + { + {14, "dd.mm.yyyy"}, + {15, "dd. mmm yy"}, + {16, "dd. mmm"}, + {17, "mmm yy"}, + {18, "hh:mm AM/PM" }, + {22, "dd.mm.yyyy hh:mm"}, + {39, "#,##0.00;-#,##0.00"}, + {47, "mm:ss,f"} + }); + } + + using (var p = OpenTemplatePackage("DE - Original.xlsx")) + { + var ws1 = p.Workbook.Worksheets[1]; + + var cell1 = ws1.Cells["D8"]; + var cell2 = ws1.Cells["F8"]; + var origText = cell1.Text; //The text lacks thousand seperator + var origText2 = cell2.Text; //The text lacks thousand seperator + + p.Workbook.Calculate(); + var calc11Text = cell1.Text; //The text now contains the thousand seperator + var calc12Text = cell2.Text; //The text now contains the thousand seperator + + var stopPoint = true; + + + SaveAndCleanup(p); + } + + SwitchBackToCurrentCulture(); + } + public class TestData { public int Id { get; set; } From 85b76453a74830117fdddafdb324ee114e2105ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 10 Feb 2026 13:12:16 +0100 Subject: [PATCH 02/11] Fixed bug using cell styleID instead of input format --- .../FormulaParsing/EpplusExcelDataProvider.cs | 20 ++- src/EPPlus/NumberFormatToTextArgs.cs | 43 ++++- .../Style/Interfaces/IExcelNumberFormat.cs | 23 +++ .../XmlAccess/ExcelNumberFormatWithoutId.cs | 23 +++ .../Style/XmlAccess/ExcelNumberFormatXml.cs | 13 +- src/EPPlusTest/Issues/StylingIssues.cs | 156 +++++++++++++++--- 6 files changed, 248 insertions(+), 30 deletions(-) create mode 100644 src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs create mode 100644 src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs diff --git a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs index 922835df62..f792220529 100644 --- a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs +++ b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs @@ -24,6 +24,7 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Ranges; using System.Runtime.InteropServices; using OfficeOpenXml.Utils.String; +using OfficeOpenXml.Style; namespace OfficeOpenXml.FormulaParsing { @@ -641,7 +642,24 @@ public override string GetFormat(object value, string format, out bool isValidFo { isValidFormat = true; var ws = _currentWorksheet ?? _context.CurrentWorksheet; - var arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, ws.GetStyleInner(_context.CurrentCell.Row, _context.CurrentCell.Column)); + + var existingId = ExcelNumberFormat.GetFromBuildIdFromFormat(format); + + NumberFormatToTextArgs arg; + if (existingId == int.MinValue) + { + //The format does not have a corresponding styleId + //Still allow the NumberFormatToTextHandler to see the format + arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, format); + //var ft = new ExcelFormatTranslator(format, -1); + //var frmt = ValueToTextHandler.FormatValue(value, false, ft, null, out isValidFormat); + //return frmt; + } + else + { + arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, existingId); + } + arg.FromFormula = true; return _workbook.NumberFormatToTextHandler(arg); } } diff --git a/src/EPPlus/NumberFormatToTextArgs.cs b/src/EPPlus/NumberFormatToTextArgs.cs index a4a3ec55d7..b052ccd481 100644 --- a/src/EPPlus/NumberFormatToTextArgs.cs +++ b/src/EPPlus/NumberFormatToTextArgs.cs @@ -12,6 +12,7 @@ Date Author Change *************************************************************************************************/ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Style; +using OfficeOpenXml.Style.Interfaces; using OfficeOpenXml.Style.XmlAccess; using OfficeOpenXml.Utils.String; @@ -23,6 +24,27 @@ namespace OfficeOpenXml public class NumberFormatToTextArgs { internal int _styleId; + + ExcelNumberFormatWithoutId fallbackNumberFormat = null; + + /// + /// Constructor when numberformat is not built in + /// + /// + /// + /// + /// + /// + internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object value, string numberFormat) + { + Worksheet = ws; + Row = row; + Column = column; + Value = value; + _styleId = -1; + fallbackNumberFormat = new ExcelNumberFormatWithoutId(numberFormat); + } + internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object value, int styleId) { Worksheet = ws; @@ -31,6 +53,9 @@ internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object v Value = value; _styleId = styleId; } + + public bool FromFormula = false; + /// /// The worksheet of the cell. /// @@ -46,11 +71,18 @@ internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object v /// /// The number format settings for the cell /// - public ExcelNumberFormatXml NumberFormat + public IExcelNumberFormat NumberFormat { get { - return ValueToTextHandler.GetNumberFormat(_styleId, Worksheet.Workbook.Styles); + if(fallbackNumberFormat != null) + { + return fallbackNumberFormat; + } + else + { + return ValueToTextHandler.GetNumberFormat(_styleId, Worksheet.Workbook.Styles); + } } } /// @@ -64,6 +96,13 @@ public string Text { get { + if(fallbackNumberFormat != null) + { + var ft = new ExcelFormatTranslator(NumberFormat.Format, -1); + bool isValidFormat = false; + var frmt = ValueToTextHandler.FormatValue(Value, false, ft, null, out isValidFormat); + return frmt; + } return ValueToTextHandler.GetFormattedText(Value, Worksheet.Workbook, _styleId, false); } } diff --git a/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs b/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs new file mode 100644 index 0000000000..594b8b05aa --- /dev/null +++ b/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OfficeOpenXml.Style.Interfaces +{ + public interface IExcelNumberFormat + { + /// + /// The numberformat string + /// + public string Format { get; } + /// + /// Number format Id + /// + public int NumFmtId { get; } + /// + /// If this numberformat is built in + /// + public bool BuildIn { get; } + } +} diff --git a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs new file mode 100644 index 0000000000..9b953ad1f6 --- /dev/null +++ b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OfficeOpenXml.Style.Interfaces; + +namespace OfficeOpenXml.Style.XmlAccess +{ + internal class ExcelNumberFormatWithoutId : IExcelNumberFormat + { + internal ExcelNumberFormatWithoutId(string format) + { + Format = format; + NumFmtId = -1; + BuildIn = false; + } + public string Format { get; private set; } + + public int NumFmtId { get; private set; } + + public bool BuildIn { get; private set; } + } +} diff --git a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs index 7b6c4a6653..a0c0eb650e 100644 --- a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs +++ b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs @@ -10,22 +10,23 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Style.Interfaces; +using OfficeOpenXml.Utils; using System; using System.Collections.Generic; -using System.Text; -using System.Xml; using System.Globalization; -using System.Text.RegularExpressions; -using OfficeOpenXml.Utils; -using System.Runtime.InteropServices; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; namespace OfficeOpenXml.Style.XmlAccess { /// /// Xml access class for number customFormats /// - public sealed class ExcelNumberFormatXml : StyleXmlHelper + public sealed class ExcelNumberFormatXml : StyleXmlHelper, IExcelNumberFormat { internal ExcelNumberFormatXml(XmlNamespaceManager nameSpaceManager) : base(nameSpaceManager) diff --git a/src/EPPlusTest/Issues/StylingIssues.cs b/src/EPPlusTest/Issues/StylingIssues.cs index b9852a4678..936b9aeb4c 100644 --- a/src/EPPlusTest/Issues/StylingIssues.cs +++ b/src/EPPlusTest/Issues/StylingIssues.cs @@ -8,6 +8,8 @@ using System.IO; using System.Globalization; using OfficeOpenXml.Style; +using OfficeOpenXml.FormulaParsing; + namespace EPPlusTest { [TestClass] @@ -414,6 +416,68 @@ public void i1839() SaveWorkbook("i1839-saved.xlsx", p); } + //[TestMethod] + //public void DirtyRichText() + //{ + // using(var pck = OpenPackage("dirtyRT.xlsx")) + // { + // var ws = pck.Workbook.Worksheets.Add("richText"); + + // ws.Cells["A1"].Value = 1001.1d; + // ws.Cells["C1"].Formula = "ROUND(A1, 1)"; + // ws.Cells["B1"].Formula = "\"My favorite number is: \"&TEXT(ROUND(A1,1),\"#,##0.00;(#,##0.00)\")"; + // ws.Cells["B1"].RichText.Add("My favorite number is: 1001.1", true); + // var cell1 = ws.Cells["B1"]; + + // var origRT = cell1.RichText; + // //var origText = cell1.Text; + + // ws.Calculate(opt => opt.PrecisionAndRoundingStrategy = PrecisionAndRoundingStrategy.Excel); + // var afterRt1 = cell1.Text; + // var cellRich = ws.Cells["B1"].RichText.Text; //A FRESH reference to the cell, yielding: 1001,10000 + + + // var OLDCellRich = cell1.RichText.Text; //Dirty COPY of the cell and its values, yielding: 1001.1\n + + // bool myBreak = true;//Place a breakpoint here + // //If you in debug look at cell1.>RichText.Text< + // //The debugger shows the value of the variable 'OLDCellRich' + // //If you look at >cell1<.RichText.Text (open the dropdonwn) and then look at cell1.>RichText.Text< + // //its value is now changed and no longer matches 'OLDCellRich'. + // //Looking at the value in the debugger changes the actual value but not OLDCellRich + + // //Conclusion: Debugging and observing values changes what the value is. + // //This is... unfortunate. + + // //Verification. This assert should be impossible to fail here. + // //No real code change has been made + // var OLDCellRichAfterDebug = cell1.RichText.Text; + // Assert.AreEqual(OLDCellRich, OLDCellRichAfterDebug); + + // //If assert above is thrown you debugged correctly to cause the issue. If not: + // //For clarification this assert throws no matter how you debug: + // ws.Cells["B1"].Clear(); + // ws.Cells["B1"].Formula = "\"My favorite number is: \"&TEXT(ROUND(A1,1),\"#,##0.00;(#,##0.00)\")"; + // ws.Cells["B1"].RichText.Add("My favorite number is: 1001.1", true); + + // var newCell1 = ws.Cells["B1"]; + // var origRt2 = newCell1.RichText; + + // ws.Calculate(); + // var secondCalc = newCell1.Text; + + // var myRef = ws.Cells["B1"].RichText.Text; //A FRESH reference to the cell, yielding: 1001,10000 + // var myCopy = newCell1.RichText.Text; //Dirty COPY of the cell and its values, yielding: 1001.1\n + + // //Just Get the text (Note even looking at the properties in debug causes this to evaluate) + // var observation = newCell1.RichText; + + // var myCopyPostObservartion = newCell1.RichText.Text; + + // Assert.AreEqual(myCopy, myCopyPostObservartion); + // } + //} + [TestMethod] public void s1005() { @@ -421,35 +485,36 @@ public void s1005() if (!ExcelPackageSettings.CultureSpecificBuildInNumberFormats.ContainsKey("de-DE")) { - ExcelPackageSettings.CultureSpecificBuildInNumberFormats.Add("de-DE", - new Dictionary - { - {14, "dd.mm.yyyy"}, - {15, "dd. mmm yy"}, - {16, "dd. mmm"}, - {17, "mmm yy"}, - {18, "hh:mm AM/PM" }, - {22, "dd.mm.yyyy hh:mm"}, - {39, "#,##0.00;-#,##0.00"}, - {47, "mm:ss,f"} - }); + ExcelPackageSettings.CultureSpecificBuildInNumberFormats.Add("de-DE", + new Dictionary + { + {14,"dd.MM.yyyy"}, + {15,"dd. MMM yy"}, + {16,"dd. MMM"}, + {17,"MMM yy"}, + {18,"hh:mm AM/PM" }, + {22,"dd.MM.yyyy hh:mm"}, + {37,"#,##0;-#,##0"}, + {38,"#,##0;[Rot]-#,##0"}, + {39,"#,##0.00;-#,##0.00"}, + {40,"#,##0.00;[Rot]-#,##0.00"}, + {47,"mm:ss,f"} + }); } + using (var p = OpenTemplatePackage("DE - Original.xlsx")) { - var ws1 = p.Workbook.Worksheets[1]; + p.Workbook.NumberFormatToTextHandler = CustomNumberFormatToTextHandler; - var cell1 = ws1.Cells["D8"]; - var cell2 = ws1.Cells["F8"]; - var origText = cell1.Text; //The text lacks thousand seperator - var origText2 = cell2.Text; //The text lacks thousand seperator + var ws1 = p.Workbook.Worksheets[1]; - p.Workbook.Calculate(); - var calc11Text = cell1.Text; //The text now contains the thousand seperator - var calc12Text = cell2.Text; //The text now contains the thousand seperator + var origText = ws1.Cells["D8"].Text; - var stopPoint = true; + ws1.Cells["D8"].Calculate(); + var cellRich = ws1.Cells["D8"].RichText.Text; + Assert.AreEqual("Zahlung einer Dividende von EUR 0,80 je Stückaktie (26.895.559,00)\nauf das Grundkapital von 69.928.453,64 EUR", cellRich); SaveAndCleanup(p); } @@ -457,6 +522,55 @@ public void s1005() SwitchBackToCurrentCulture(); } + private static string CustomNumberFormatToTextHandler(NumberFormatToTextArgs options) + { + var result = options.Text; + + switch (options.Value) + { + case DateTime dt: + { + if (dt.Millisecond >= 500) + dt = dt.AddSeconds(1).AddMilliseconds(-dt.Millisecond); + + switch (options.NumberFormat.Format) + { + case "[h]:mm:ss": + var excelTime = dt - new DateTime(1899, 12, 30); + result = $"{(int)excelTime.TotalHours}:{dt.Minute:00}:{dt.Second:00}"; + break; + case "h:mm AM/PM": + case "hh:mm AM/PM": + result = dt.ToString("hh:mm tt", CultureInfo.InvariantCulture); + break; + case "h:mm:ss AM/PM": + case "hh:mm:ss AM/PM": + result = dt.ToString("hh:mm:ss tt", CultureInfo.InvariantCulture); + break; + default: + result = dt.ToString(options.NumberFormat.Format); + break; + } + + break; + } + case double d: + { + switch (options.NumberFormat.Format) + { + case "#,##0.00;[Rot]-#,##0.00": + case "#,##0;[Rot]-#,##0": + result = d.ToString("N2", CultureInfo.InvariantCulture); + break; + } + + break; + } + } + + return result; + } + public class TestData { public int Id { get; set; } From 4671ffeafe156f41ebc2d3001b4ab35d3c1cc2e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 10 Feb 2026 15:14:51 +0100 Subject: [PATCH 03/11] Cleanup of comments etc. --- .../FormulaParsing/EpplusExcelDataProvider.cs | 3 - src/EPPlus/NumberFormatToTextArgs.cs | 7 +- src/EPPlusTest/Issues/StylingIssues.cs | 111 +++--------------- 3 files changed, 22 insertions(+), 99 deletions(-) diff --git a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs index f792220529..c51ca54c19 100644 --- a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs +++ b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs @@ -651,9 +651,6 @@ public override string GetFormat(object value, string format, out bool isValidFo //The format does not have a corresponding styleId //Still allow the NumberFormatToTextHandler to see the format arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, format); - //var ft = new ExcelFormatTranslator(format, -1); - //var frmt = ValueToTextHandler.FormatValue(value, false, ft, null, out isValidFormat); - //return frmt; } else { diff --git a/src/EPPlus/NumberFormatToTextArgs.cs b/src/EPPlus/NumberFormatToTextArgs.cs index b052ccd481..1bbfbe8b66 100644 --- a/src/EPPlus/NumberFormatToTextArgs.cs +++ b/src/EPPlus/NumberFormatToTextArgs.cs @@ -27,6 +27,11 @@ public class NumberFormatToTextArgs ExcelNumberFormatWithoutId fallbackNumberFormat = null; + /// + /// If these args are provided from a formula + /// + public bool FromFormula = false; + /// /// Constructor when numberformat is not built in /// @@ -54,8 +59,6 @@ internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object v _styleId = styleId; } - public bool FromFormula = false; - /// /// The worksheet of the cell. /// diff --git a/src/EPPlusTest/Issues/StylingIssues.cs b/src/EPPlusTest/Issues/StylingIssues.cs index 936b9aeb4c..b2884e627a 100644 --- a/src/EPPlusTest/Issues/StylingIssues.cs +++ b/src/EPPlusTest/Issues/StylingIssues.cs @@ -416,73 +416,12 @@ public void i1839() SaveWorkbook("i1839-saved.xlsx", p); } - //[TestMethod] - //public void DirtyRichText() - //{ - // using(var pck = OpenPackage("dirtyRT.xlsx")) - // { - // var ws = pck.Workbook.Worksheets.Add("richText"); - - // ws.Cells["A1"].Value = 1001.1d; - // ws.Cells["C1"].Formula = "ROUND(A1, 1)"; - // ws.Cells["B1"].Formula = "\"My favorite number is: \"&TEXT(ROUND(A1,1),\"#,##0.00;(#,##0.00)\")"; - // ws.Cells["B1"].RichText.Add("My favorite number is: 1001.1", true); - // var cell1 = ws.Cells["B1"]; - - // var origRT = cell1.RichText; - // //var origText = cell1.Text; - - // ws.Calculate(opt => opt.PrecisionAndRoundingStrategy = PrecisionAndRoundingStrategy.Excel); - // var afterRt1 = cell1.Text; - // var cellRich = ws.Cells["B1"].RichText.Text; //A FRESH reference to the cell, yielding: 1001,10000 - - - // var OLDCellRich = cell1.RichText.Text; //Dirty COPY of the cell and its values, yielding: 1001.1\n - - // bool myBreak = true;//Place a breakpoint here - // //If you in debug look at cell1.>RichText.Text< - // //The debugger shows the value of the variable 'OLDCellRich' - // //If you look at >cell1<.RichText.Text (open the dropdonwn) and then look at cell1.>RichText.Text< - // //its value is now changed and no longer matches 'OLDCellRich'. - // //Looking at the value in the debugger changes the actual value but not OLDCellRich - - // //Conclusion: Debugging and observing values changes what the value is. - // //This is... unfortunate. - - // //Verification. This assert should be impossible to fail here. - // //No real code change has been made - // var OLDCellRichAfterDebug = cell1.RichText.Text; - // Assert.AreEqual(OLDCellRich, OLDCellRichAfterDebug); - - // //If assert above is thrown you debugged correctly to cause the issue. If not: - // //For clarification this assert throws no matter how you debug: - // ws.Cells["B1"].Clear(); - // ws.Cells["B1"].Formula = "\"My favorite number is: \"&TEXT(ROUND(A1,1),\"#,##0.00;(#,##0.00)\")"; - // ws.Cells["B1"].RichText.Add("My favorite number is: 1001.1", true); - - // var newCell1 = ws.Cells["B1"]; - // var origRt2 = newCell1.RichText; - - // ws.Calculate(); - // var secondCalc = newCell1.Text; - - // var myRef = ws.Cells["B1"].RichText.Text; //A FRESH reference to the cell, yielding: 1001,10000 - // var myCopy = newCell1.RichText.Text; //Dirty COPY of the cell and its values, yielding: 1001.1\n - - // //Just Get the text (Note even looking at the properties in debug causes this to evaluate) - // var observation = newCell1.RichText; - - // var myCopyPostObservartion = newCell1.RichText.Text; - - // Assert.AreEqual(myCopy, myCopyPostObservartion); - // } - //} - [TestMethod] public void s1005() { SwitchToCulture("de-DE"); + //Set specific built-in formats if (!ExcelPackageSettings.CultureSpecificBuildInNumberFormats.ContainsKey("de-DE")) { ExcelPackageSettings.CultureSpecificBuildInNumberFormats.Add("de-DE", @@ -502,19 +441,28 @@ public void s1005() }); } - - using (var p = OpenTemplatePackage("DE - Original.xlsx")) + using (var p = OpenTemplatePackage("s1005.xlsx")) { - p.Workbook.NumberFormatToTextHandler = CustomNumberFormatToTextHandler; + //AND use a Custom Number Format + //This caused issues in the Text.cs file when we ran Calculate + p.Workbook.NumberFormatToTextHandler = CustomNumberFormatToTextExample; var ws1 = p.Workbook.Worksheets[1]; var origText = ws1.Cells["D8"].Text; + //Verify cell contents match when test was written + Assert.IsTrue(origText.Contains(".8000")); + Assert.IsTrue(origText.Contains("(26895559.000)")); + Assert.IsTrue(origText.Contains("69928453.64000")); + ws1.Cells["D8"].Calculate(); var cellRich = ws1.Cells["D8"].RichText.Text; - Assert.AreEqual("Zahlung einer Dividende von EUR 0,80 je Stückaktie (26.895.559,00)\nauf das Grundkapital von 69.928.453,64 EUR", cellRich); + //Verify formatting has changed appropriately for calculated string + Assert.IsTrue(cellRich.Contains("0,80")); + Assert.IsTrue(cellRich.Contains("(26.895.559,00)")); + Assert.IsTrue(cellRich.Contains("69.928.453,64")); SaveAndCleanup(p); } @@ -522,7 +470,7 @@ public void s1005() SwitchBackToCurrentCulture(); } - private static string CustomNumberFormatToTextHandler(NumberFormatToTextArgs options) + private static string CustomNumberFormatToTextExample(NumberFormatToTextArgs options) { var result = options.Text; @@ -530,40 +478,15 @@ private static string CustomNumberFormatToTextHandler(NumberFormatToTextArgs opt { case DateTime dt: { - if (dt.Millisecond >= 500) - dt = dt.AddSeconds(1).AddMilliseconds(-dt.Millisecond); - switch (options.NumberFormat.Format) { - case "[h]:mm:ss": - var excelTime = dt - new DateTime(1899, 12, 30); - result = $"{(int)excelTime.TotalHours}:{dt.Minute:00}:{dt.Second:00}"; - break; - case "h:mm AM/PM": - case "hh:mm AM/PM": - result = dt.ToString("hh:mm tt", CultureInfo.InvariantCulture); - break; - case "h:mm:ss AM/PM": - case "hh:mm:ss AM/PM": - result = dt.ToString("hh:mm:ss tt", CultureInfo.InvariantCulture); + case "dd. mmm yy": + result = dt.ToString("dd. mmm yy"); //Return your own formatted text. Example break; default: result = dt.ToString(options.NumberFormat.Format); break; } - - break; - } - case double d: - { - switch (options.NumberFormat.Format) - { - case "#,##0.00;[Rot]-#,##0.00": - case "#,##0;[Rot]-#,##0": - result = d.ToString("N2", CultureInfo.InvariantCulture); - break; - } - break; } } From a5b71e7eaba2670f9e4cde0098173d8b624d6cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Fri, 13 Feb 2026 16:43:09 +0100 Subject: [PATCH 04/11] Potential workaround --- .../Drawing/Chart/ChartSeriesTest.cs | 177 +++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs index 353a22c52a..2846c6b6b1 100644 --- a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs +++ b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs @@ -1,14 +1,22 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Castle.Components.DictionaryAdapter.Xml; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; using OfficeOpenXml; +using OfficeOpenXml.ConditionalFormatting; using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Drawing.Chart.ChartEx; +using OfficeOpenXml.Drawing.Interfaces; +using OfficeOpenXml.FormulaParsing.Utilities; +using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using System.Xml; namespace EPPlusTest.Drawing.Chart { @@ -241,5 +249,172 @@ public void SimpleChartDataLabels() SaveAndCleanup(p); } } + + //[TestMethod] + //public void GenerateExample() + //{ + // using (var package = OpenPackage("manualLayoutChartCommentRange.xlsx", true)) + // { + // var ws = package.Workbook.Worksheets.Add("ManualLayout"); + + // //Create some values + // ws.Cells["A1:A2"].Value = 5; + // ws.Cells["B1:B2"].Value = 10; + + // //Create a column chart + // var sChart = ws.Drawings.AddBarChart("ColumnChart", eBarChartType.ColumnClustered); + + // //Add series (clustered columns) to the chart. In this case 2 per series + // var s1 = sChart.Series.Add(ws.Cells["A1:A2"]); + // var s2 = sChart.Series.Add(ws.Cells["B1:B2"]); + + // //Add a general datalabel + // var label = s1.DataLabel; + // label.ShowValue = true; + + // //Add a specific datalabel to the first column in the cluster + // var dl = label.DataLabels.Add(0); + + // var myLabel = s1.DataLabel.DataLabels[0]; + + // //Offset the data label 10% of the charts width to the left + // //AKA Remove 10 from x coordinate + // dl.Layout.ManualLayout.Left = -10; + + // //Offset the data label 10% of the charts height to the top + // //AKA remove 10 from y coordinate + // dl.Layout.ManualLayout.Top = -10; + + // //Save the package at a path + // package.SaveAs(@"C:\temp\manualLayoutChart.xlsx"); + // } + //} + + + + [TestMethod] + public void ReadSimpleFile() + { + using (var package = OpenTemplatePackage("editedDataLabel.xlsx")) + { + var ws = package.Workbook.Worksheets[0]; + + var myChart = ws.Drawings[0].As.Chart.BarChart; + + var lbl = myChart.Series[0].DataLabel.DataLabels[0]; + + var lblTxtBody = myChart.Series[0].DataLabel.DataLabels[0].TextBody; + + + + //var serializedParent = JsonConvert.SerializeObject(test.Font); + //ExcelParagraph castedFont = JsonConvert.DeserializeObject(serializedParent); + + + //castedFont.Text = "My Overriding Comment"; + + //for (int i = 1; i < 4; i++) + //{ + // ws.Cells[i, 2].Value = $"Comment {i}"; + //} + + //var serie = myChart.Series.Add(ws.Cells["A1:A3"], ws.Cells["C1:C3"]); + //serie.DataLabel.ShowValue = true; + + SaveAndCleanup(package); + } + } + + [TestMethod] + public void ReadFile() + { + using (var package = OpenTemplatePackage("S1008.xlsx")) + { + var ws = package.Workbook.Worksheets[0]; + + //var myChart = ws.Drawings[0].As.Chart.ChartExtended; + var chart = ws.Drawings[0].As.Chart.LineChart; + + //var myRect = ws.Drawings.AddShape("MyShape", eShapeStyle.Rect); + + //ws.Calculate(); + + //ws.Workbook.Calculate(); + ////chart.Series.lab + ////var chart = ws.Drawings[0].As.Chart.Type; + + //var theCustomLabels = chart.Series[0].DataLabel.DataLabels; + + //var addedLabel = theCustomLabels.Add(21); + + //var label21 = theCustomLabels[21]; + //var label26 = theCustomLabels[26]; + + //var aNode = (XmlElement)label21.TopNode; + + //var idxNodeUnderDataLabel = myChart.SelectSingleNode($@"//c:dLbl/c:idx[@val='{20}']", nsm); + //var paragraphNodeForTheText = idxNodeUnderDataLabel.ParentNode.SelectSingleNode("c:tx/c:rich/a:p", nsm); + + + + var myChart = chart.ChartXml; + + string myComment = "Mislabeled resistors - wrong part defects"; + + //CommentOnDataLabel(myChart, 0, 21, myComment); + CommentOnDataLabel(myChart, 0,20, myComment); + //ready namespace manager + //var nsm = new XmlNamespaceManager(myChart.NameTable); + //nsm.AddNamespace(@"c", @"http://schemas.openxmlformats.org/drawingml/2006/chart"); + //nsm.AddNamespace(@"a", @"http://schemas.openxmlformats.org/drawingml/2006/main"); + + //int serieIdx = 0; + + //var serieNode = myChart.ChildNodes[2].SelectSingleNode(@"c:chart/c:plotArea/c:lineChart/c:ser[c:idx[@val='" + serieIdx + "']]", nsm); + //int dlblIdx = 20; + //var dataLabelToComment = serieNode.SelectSingleNode(@"c:dLbls/c:dLbl[c:idx[@val='" + dlblIdx + "']]", nsm); + + + //dataLabelToComment.InnerXml = $"{myComment}"; + + //"Mislabeled resistors - wrong part defects"; + //var firstSeries = myChart.SelectSingleNode("//c:ser", myChart.namespa); + //var idxNode = firstSeries.SelectSingleNode("//c:idx[@val=\"21\"]"); + + //firstSeries.SelectSingleNode() + + //var theSeries = chart.Series[0].DataPoints + //aNode. + //var myLitterals = chart.Series[0].StringLiteralsX; + //var myLitteralsOther = chart.Series[0].StringLiteralsY; + + //chart.Series[0].CreateCache(); + + //var dataPoint = chart.Series[0].DataPoints[21]; + + //var element = label21.TextBody.PathElement; + //label26. + //var fldParagraph = label21.TextBody.Paragraphs[0]; + //fldParagraph.Text + + SaveAndCleanup(package); + } + } + + static void CommentOnDataLabel(XmlDocument chartXml, int seriesIdx, int datalabelIdx, string myComment) + { + var nsm = new XmlNamespaceManager(chartXml.NameTable); + nsm.AddNamespace(@"c", @"http://schemas.openxmlformats.org/drawingml/2006/chart"); + nsm.AddNamespace(@"a", @"http://schemas.openxmlformats.org/drawingml/2006/main"); + + int serieIdx = 0; + + var serieNode = chartXml.ChildNodes[2].SelectSingleNode(@"c:chart/c:plotArea/c:lineChart/c:ser[c:idx[@val='" + serieIdx + "']]", nsm); + + var dataLabelToComment = serieNode.SelectSingleNode(@"c:dLbls/c:dLbl[c:idx[@val='" + datalabelIdx + "']]", nsm); + + var paragraphNodeForTheText = dataLabelToComment.SelectSingleNode("c:tx/c:rich/a:p", nsm); + paragraphNodeForTheText.InnerXml = $"{myComment}"; + } } } From 022db49e3826ca6fee6db253535dcf64d241afa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Tue, 17 Feb 2026 16:14:03 +0100 Subject: [PATCH 05/11] Added paragraphs to datalabels. Started adding extlst --- .../CellPictures/CellPicturesManager.cs | 5 + .../Drawing/Chart/ExcelChartDataLabel.cs | 26 +++--- .../Chart/ExcelChartDataLabelCollection.cs | 16 +++- .../Drawing/Chart/ExcelChartDataLabelItem.cs | 39 ++++++++ .../Chart/ExcelChartDataLabelStandard.cs | 29 +++++- .../Drawing/Chart/ExcelChartSerieDataLabel.cs | 93 ++++++++++++++++++- .../Drawing/Chart/ExcelChartStandardSerie.cs | 19 +++- .../Drawing/Chart/ExcelLineChartSerie.cs | 5 +- .../Drawing/Style/Text/ExcelTextBody.cs | 5 + src/EPPlus/Style/RichText/ExcelParagraph.cs | 2 + .../RichText/ExcelParagraphCollection.cs | 33 ++++++- .../Drawing/Chart/ChartSeriesTest.cs | 31 +++---- .../InCellImages/InCellImagesCacheTests.cs | 8 +- 13 files changed, 256 insertions(+), 55 deletions(-) diff --git a/src/EPPlus/CellPictures/CellPicturesManager.cs b/src/EPPlus/CellPictures/CellPicturesManager.cs index e3c98ba9eb..c8953ca5be 100644 --- a/src/EPPlus/CellPictures/CellPicturesManager.cs +++ b/src/EPPlus/CellPictures/CellPicturesManager.cs @@ -297,6 +297,11 @@ private void AddNewWebPicture(int row, int col, Uri imageUri, Uri addressUri, st } } + internal void ReadAndAddReference(PictureCacheKey key, uint vmId) + { + _referenceCache.Add(key, vmId); + } + private void AddReferenceToPicture(int row, int col, PictureCacheKey key, uint vmId) { var rv = _richDataStore.GetRichValue(vmId); diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabel.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabel.cs index 1b6cec48df..7e5300bfb1 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabel.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabel.cs @@ -26,7 +26,7 @@ public abstract class ExcelChartDataLabel : XmlHelper, IDrawingStyle { internal ExcelChart _chart; internal string _nodeName; - private string _nsPrefix; + internal protected string NsPrefix { private set; get; } private readonly string _formatPath; private readonly string _sourceLinkedPath; @@ -35,7 +35,7 @@ internal ExcelChartDataLabel(ExcelChart chart, XmlNamespaceManager ns, XmlNode n { _nodeName = nodeName; _chart = chart; - _nsPrefix = nsPrefix; + NsPrefix = nsPrefix; _formatPath = $"{nsPrefix}:numFmt/@formatCode"; _sourceLinkedPath = $"{nsPrefix}:numFmt/@sourceLinked"; } @@ -157,7 +157,7 @@ public ExcelDrawingFill Fill { if (_fill == null) { - _fill = new ExcelDrawingFill(_chart, NameSpaceManager, TopNode, $"{_nsPrefix}:spPr", SchemaNodeOrder); + _fill = new ExcelDrawingFill(_chart, NameSpaceManager, TopNode, $"{NsPrefix}:spPr", SchemaNodeOrder); } return _fill; } @@ -172,7 +172,7 @@ public ExcelDrawingBorder Border { if (_border == null) { - _border = new ExcelDrawingBorder(_chart, NameSpaceManager, TopNode, $"{_nsPrefix}:spPr/a:ln", SchemaNodeOrder); + _border = new ExcelDrawingBorder(_chart, NameSpaceManager, TopNode, $"{NsPrefix}:spPr/a:ln", SchemaNodeOrder); } return _border; } @@ -187,7 +187,7 @@ public ExcelDrawingEffectStyle Effect { if (_effect == null) { - _effect = new ExcelDrawingEffectStyle(_chart, NameSpaceManager, TopNode, $"{_nsPrefix}:spPr/a:effectLst", SchemaNodeOrder); + _effect = new ExcelDrawingEffectStyle(_chart, NameSpaceManager, TopNode, $"{NsPrefix}:spPr/a:effectLst", SchemaNodeOrder); } return _effect; } @@ -202,7 +202,7 @@ public ExcelDrawing3D ThreeD { if (_threeD == null) { - _threeD = new ExcelDrawing3D(NameSpaceManager, TopNode, $"{_nsPrefix}:spPr", SchemaNodeOrder); + _threeD = new ExcelDrawing3D(NameSpaceManager, TopNode, $"{NsPrefix}:spPr", SchemaNodeOrder); } return _threeD; } @@ -218,7 +218,7 @@ public ExcelTextFont Font { if (_font == null) { - _font = new ExcelTextFont(_chart, NameSpaceManager, TopNode, $"{_nsPrefix}:txPr/a:p/a:pPr/a:defRPr", SchemaNodeOrder, CreateDefaultText); + _font = new ExcelTextFont(_chart, NameSpaceManager, TopNode, $"{NsPrefix}:txPr/a:p/a:pPr/a:defRPr", SchemaNodeOrder, CreateDefaultText); } return _font; } @@ -233,7 +233,7 @@ public ExcelDrawingTextSettings TextSettings { if (_textSettings == null) { - _textSettings = new ExcelDrawingTextSettings(_chart, NameSpaceManager, TopNode, $"{_nsPrefix}:txPr/a:p/a:pPr/a:defRPr", SchemaNodeOrder); + _textSettings = new ExcelDrawingTextSettings(_chart, NameSpaceManager, TopNode, $"{NsPrefix}:txPr/a:p/a:pPr/a:defRPr", SchemaNodeOrder); } return _textSettings; } @@ -245,14 +245,14 @@ void IDrawingStyleBase.CreatespPr() private void CreateDefaultText() { - if (TopNode.SelectSingleNode($"{_nsPrefix}:txPr", NameSpaceManager) == null) + if (TopNode.SelectSingleNode($"{NsPrefix}:txPr", NameSpaceManager) == null) { - if (!ExistsNode($"{_nsPrefix}:spPr")) + if (!ExistsNode($"{NsPrefix}:spPr")) { - var spNode = CreateNode($"{_nsPrefix}:spPr"); + var spNode = CreateNode($"{NsPrefix}:spPr"); spNode.InnerXml = ""; } - var node = CreateNode($"{_nsPrefix}:txPr"); + var node = CreateNode($"{NsPrefix}:txPr"); node.InnerXml = ""; } @@ -268,7 +268,7 @@ public ExcelTextBody TextBody { if (_textBody == null) { - _textBody = new ExcelTextBody(NameSpaceManager, TopNode, $"{_nsPrefix}:txPr/a:bodyPr", SchemaNodeOrder, Font.CreateTopNode); + _textBody = new ExcelTextBody(NameSpaceManager, TopNode, $"{NsPrefix}:txPr/a:bodyPr", SchemaNodeOrder, Font.CreateTopNode); } return _textBody; } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs index 8b2b2a0a28..dcb8aa4785 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs @@ -13,7 +13,8 @@ Date Author Change using System; using System.Collections; using System.Collections.Generic; -using System.Reflection.Emit; +using System.ComponentModel; +using System.Data; using System.Xml; namespace OfficeOpenXml.Drawing.Chart @@ -31,14 +32,25 @@ internal ExcelChartDataLabelCollection(ExcelChart chart, XmlNamespaceManager ns, { SchemaNodeOrder = schemaNodeOrder; _list = new List(); - foreach (XmlNode dataLabelNode in TopNode.SelectNodes("c:dLbl", ns)) + var existingDataLabelNodes = TopNode.SelectNodes("c:dLbl", ns); + foreach (XmlNode dataLabelNode in existingDataLabelNodes) { _list.Add(new ExcelChartDataLabelItem(chart, ns, dataLabelNode, "", schemaNodeOrder)); } parentDatalabel = parent; _chart = chart; + } + + internal void InitializeDataLabelsXml() + { + if(_list.Count == 0) + { + var seriesNode = TopNode.ParentNode; + } + } + /// /// Adds a new chart label to the collection /// diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs index 8e0d06e5cb..3788b3cf46 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs @@ -10,6 +10,10 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; +using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions; +using OfficeOpenXml.Style; +using System.Collections.Generic; using System.Globalization; using System.Xml; @@ -20,10 +24,12 @@ namespace OfficeOpenXml.Drawing.Chart /// public class ExcelChartDataLabelItem : ExcelChartDataLabelStandard { + string _fontPropertiesPath = ""; internal ExcelChartDataLabelItem(ExcelChart chart, XmlNamespaceManager ns, XmlNode node, string nodeName, string[] schemaNodeOrder) : base(chart, ns, node, nodeName, schemaNodeOrder) { Layout = new ExcelLayout(NameSpaceManager, TopNode, $"c:layout","c:extLst/c:ext[1]/c15:layout", SchemaNodeOrder); + _fontPropertiesPath = $"{NsPrefix}:tx/{NsPrefix}:rich"; } /// @@ -31,6 +37,39 @@ internal ExcelChartDataLabelItem(ExcelChart chart, XmlNamespaceManager ns, XmlNo /// public ExcelLayout Layout { get; private set; } + ExcelParagraphCollection _paragraphs = null; + + /// + /// Access to text body properties + /// + private ExcelParagraphCollection ParagraphCollection + { + get + { + if (_paragraphs == null) + { + //var firstParaPath = _textBodyPropertiesParentPath + $"/{NsPrefix}:p"; + //par.SelectNodes("a:r", NameSpaceManager); + _paragraphs = new ExcelParagraphCollection(_chart, NameSpaceManager, TopNode, _fontPropertiesPath + "/a:p", SchemaNodeOrder); + } + return _paragraphs; + } + } + + /// + /// Replace datalabel text + /// + /// + public void OverWriteText(string replacementText) + { + ParagraphCollection.Clear(); + ParagraphCollection.Add(replacementText, true); + } + + internal List> GetExistingParagraphStrings() + { + return ParagraphCollection.GetParagraphTextLists(); + } /// /// The index of an individual datalabel /// diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs index 5552436b10..b56de55262 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs @@ -144,7 +144,7 @@ public override bool ShowSeriesName SetXmlNodeString(showSerPath, value ? "1" : "0"); } } - const string showPerentPath = "c:showPercent/@val"; + const string showPercentPath = "c:showPercent/@val"; /// /// Show percent values /// @@ -152,11 +152,11 @@ public override bool ShowPercent { get { - return GetXmlNodeBool(showPerentPath); + return GetXmlNodeBool(showPercentPath); } set { - SetXmlNodeString(showPerentPath, value ? "1" : "0"); + SetXmlNodeString(showPercentPath, value ? "1" : "0"); } } const string showLeaderLinesPath = "c:showLeaderLines/@val"; @@ -254,5 +254,28 @@ public override string Separator } } } + + internal void AddExtFieldTableEmpty() + { + CreateNode($"{extPath}/c15:dlblFieldTable"); + } + + + internal bool ShowDatalabelsRange + { + get + { + return GetXmlNodeBool($"{extPath}/c15:showDataLabelsRange"); + } + set + { + var rangePath = $"{extPath}/c15:showDataLabelsRange"; + if(ExistsNode(rangePath) == false) + { + CreateNode(rangePath); + } + SetBoolNode(rangePath, value); + } + } } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs index ce8b6c61e0..d5e7db70fe 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs @@ -10,13 +10,15 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Drawing.Interfaces; +using OfficeOpenXml.Drawing.Style.Effect; +using OfficeOpenXml.Style; using System; using System.Collections.Generic; +using System.Data; +using System.Linq; using System.Text; using System.Xml; -using OfficeOpenXml.Drawing.Interfaces; -using OfficeOpenXml.Drawing.Style.Effect; -using OfficeOpenXml.Style; namespace OfficeOpenXml.Drawing.Chart { @@ -26,7 +28,7 @@ namespace OfficeOpenXml.Drawing.Chart public sealed class ExcelChartSerieDataLabel : ExcelChartDataLabelStandard { internal ExcelChartSerieDataLabel(ExcelChart chart, XmlNamespaceManager ns, XmlNode node, string[] schemaNodeOrder) - : base(chart, ns,node,"dLbls", schemaNodeOrder) + : base(chart, ns, node, "dLbls", schemaNodeOrder) { Position = eLabelPosition.Center; } @@ -45,5 +47,88 @@ public ExcelChartDataLabelCollection DataLabels return _dataLabels; } } + + /// + /// Does the datalabels of this chart contain + /// Value From Cells + /// + public bool ValueFromCells { get { return DataLabelRange != null; } } + + ExcelRangeBase DataLabelRange = null; + + /// + /// Select datalabel range for + /// Value From Cells + /// + /// must be a single; cell, row or column + /// Thrown when input is not a cell, a row or a column + public void SelectRange(ExcelRangeBase address) + { + bool moreThanOneRow = address.Rows > 1; + bool moreThanOneColumn = address.Columns > 1; + + if (moreThanOneRow && moreThanOneColumn) + { + throw new InvalidExpressionException($"DataLabelRange cannot be set to invalid range: '{address.Address}'\n" + + $"The range must be a single cell, a single row or a single column"); + } + + DataLabelRange = address; + + //Has to get the series index: + var idxNode = (XmlElement)TopNode.ParentNode.SelectSingleNode($"{NsPrefix}:idx", NameSpaceManager); + var idxNodeValue = int.Parse(idxNode.GetAttribute("val")); + + var currentSeries = (ExcelChartStandardSerie)_chart.Series[idxNodeValue]; + + currentSeries.NameSpaceManager.AddNamespace("c15", ExcelPackage.schemaChart2012); + currentSeries.NameSpaceManager.AddNamespace("c16", ExcelPackage.schemaChart2014); + + string extPath = "c:extLst/c:ext"; + + XmlElement ext15Node; + + if (currentSeries.ExistsNode(extPath) == false) + { + XmlElement el = (XmlElement)currentSeries.CreateNode($"{extPath}"); + el.SetAttribute("xmlns:c15", ExcelPackage.schemaChart2012); + currentSeries.SetXmlNodeString($"{extPath}/@uri", "{02D57815-91ED-43cb-92C2-25804820EDAC}"); + ext15Node = el; + } + else + { + ext15Node = (XmlElement)currentSeries.GetNode($"{extPath}"); + } + + if (currentSeries.ExistsNode($"{extPath}[2]") == false) + { + XmlElement element = (XmlElement)currentSeries.CreateNode($"{extPath}", false, true); + element.SetAttribute("xmlns:c16", ExcelPackage.schemaChart2014); + currentSeries.SetXmlNodeString($"{extPath}[2]/@uri", "{C3380CC4-5D6E-409C-BE32-E72D297353CC}"); + var _guidId = Guid.NewGuid(); + + var extNode2 = currentSeries.GetNode($"{extPath}[2]"); + var uniqueIdNode = (XmlElement)CreateNode(extNode2, "c16:uniqueID"); + uniqueIdNode.SetAttribute("val", $"{{{_guidId}}}"); + } + + var dlblRangePath = $"{extPath}/c15:datalabelsRange"; + var datalabelsRange = currentSeries.CreateNode(dlblRangePath); + var formulaNode = currentSeries.CreateNode($"{dlblRangePath}/c15:f"); + formulaNode.InnerText = address.AddressAbsolute; + + if(DataLabels.Count == 0) + { + for (int i = 0; i < currentSeries.Series.Count(); i++) + { + var individualLabel = DataLabels.Add(i); + individualLabel.AddExtFieldTableEmpty(); + individualLabel.ShowDatalabelsRange = true; + } + } + + var rangeNode = currentSeries.CreateNode($"{dlblRangePath}/c15:dlblRangeCache"); + currentSeries.CreateCache(address.FullAddressAbsolute, rangeNode); + } } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs index 06c03b88d7..dd11c3a38e 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs @@ -19,6 +19,7 @@ Date Author Change using System.Globalization; using System.Runtime.CompilerServices; using System.IO; +using OfficeOpenXml.FormulaParsing.Utilities; namespace OfficeOpenXml.Drawing.Chart { /// @@ -28,7 +29,6 @@ public class ExcelChartStandardSerie : ExcelChartSerie { private readonly bool _isPivot; - double[] _NumberLiteralsY = null; double[] _NumberLiteralsX = null; string[] _StringLiteralsX = null; @@ -598,8 +598,9 @@ public void CreateCache() CreateCache(XSeries, node); } + } - private void CreateCache(string address, XmlNode node) + internal void CreateCache(string address, XmlNode node) { //var ws = _chart.WorkSheet; var wb = _chart.WorkSheet.Workbook; @@ -647,11 +648,21 @@ private void CreateCacheFromRange(XmlNode node, ExcelRangeBase range) var v = cse.Value._value; if (v != null) { - var d = Utils.TypeConversion.ConvertUtil.GetValueDouble(v); + string xmlValue = ""; + if(v.IsNumeric()) + { + var d = Utils.TypeConversion.ConvertUtil.GetValueDouble(v); + xmlValue = Utils.TypeConversion.ConvertUtil.GetValueForXml(d, range.Worksheet.Workbook.Date1904); + } + else + { + xmlValue = string.Format(CultureInfo.InvariantCulture, v.ToString()); + } + var ptNode = node.OwnerDocument.CreateElement("c", "pt", ExcelPackage.schemaChart); node.AppendChild(ptNode); ptNode.SetAttribute("idx", (cse.Row - startRow).ToString(CultureInfo.InvariantCulture)); - ptNode.InnerXml = $"{Utils.TypeConversion.ConvertUtil.GetValueForXml(d, range.Worksheet.Workbook.Date1904)}"; + ptNode.InnerXml = $"{xmlValue}"; items++; } } diff --git a/src/EPPlus/Drawing/Chart/ExcelLineChartSerie.cs b/src/EPPlus/Drawing/Chart/ExcelLineChartSerie.cs index f1e5f8ad67..d5f78979d6 100644 --- a/src/EPPlus/Drawing/Chart/ExcelLineChartSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelLineChartSerie.cs @@ -10,13 +10,14 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Drawing.Interfaces; using System; using System.Collections.Generic; +using System.Data; +using System.Drawing; using System.Globalization; using System.Text; using System.Xml; -using System.Drawing; -using OfficeOpenXml.Drawing.Interfaces; namespace OfficeOpenXml.Drawing.Chart { diff --git a/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs b/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs index ae0635b038..70a95ae6a6 100644 --- a/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs +++ b/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs @@ -412,5 +412,10 @@ internal void SetFromXml(XmlElement copyFromElement) element.SetAttribute(a.Name, a.NamespaceURI, a.Value); } } + + //internal ExcelParagraph GetFirstParagraph() + //{ + // //$"{_path}/a:spAutoFit" + //} } } diff --git a/src/EPPlus/Style/RichText/ExcelParagraph.cs b/src/EPPlus/Style/RichText/ExcelParagraph.cs index 841314f298..e90136448e 100644 --- a/src/EPPlus/Style/RichText/ExcelParagraph.cs +++ b/src/EPPlus/Style/RichText/ExcelParagraph.cs @@ -12,6 +12,7 @@ Date Author Change *************************************************************************************************/ using OfficeOpenXml.Drawing; using OfficeOpenXml.Drawing.Interfaces; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using System; using System.Collections.Generic; using System.Text; @@ -30,6 +31,7 @@ internal ExcelParagraph(IPictureRelationDocument pictureRelationDocument, XmlNam } const string TextPath = "../a:t"; + const string FldPath = "../a:fld"; /// /// Text /// diff --git a/src/EPPlus/Style/RichText/ExcelParagraphCollection.cs b/src/EPPlus/Style/RichText/ExcelParagraphCollection.cs index 0c75e93be5..841b5081a5 100644 --- a/src/EPPlus/Style/RichText/ExcelParagraphCollection.cs +++ b/src/EPPlus/Style/RichText/ExcelParagraphCollection.cs @@ -10,14 +10,15 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Drawing; using System; using System.Collections.Generic; -using System.Text; -using System.Xml; -using OfficeOpenXml.Drawing; using System.Drawing; -using System.Linq; using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; namespace OfficeOpenXml.Style { @@ -213,6 +214,30 @@ public string Text } } } + + internal List> GetParagraphTextLists() + { + List> strings = new List>(); + var pars = TopNode.SelectNodes(_path, NameSpaceManager); + + foreach(XmlNode paragraph in pars) + { + List paragraphTexts = new List(); + foreach (XmlNode node in paragraph.ChildNodes) + { + if (node.LocalName == "fld" || node.LocalName == "r") + { + var textNode = node.SelectSingleNode("a:t", NameSpaceManager); + var text = textNode.InnerText; + paragraphTexts.Add(text); + } + } + strings.Add(paragraphTexts); + } + + return strings; + } + #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() diff --git a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs index 2846c6b6b1..88e1b42382 100644 --- a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs +++ b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs @@ -328,41 +328,32 @@ public void ReadSimpleFile() [TestMethod] public void ReadFile() { - using (var package = OpenTemplatePackage("S1008.xlsx")) + using (var package = OpenTemplatePackage("S1008_NoComment.xlsx")) { var ws = package.Workbook.Worksheets[0]; - //var myChart = ws.Drawings[0].As.Chart.ChartExtended; var chart = ws.Drawings[0].As.Chart.LineChart; - //var myRect = ws.Drawings.AddShape("MyShape", eShapeStyle.Rect); + //var label = chart.Series[0].DataLabel.DataLabels[20]; - //ws.Calculate(); + //var paraStr = label.GetExistingParagraphStrings(); + //label.OverWriteText("Mislabeled resistors - wrong part defects"); - //ws.Workbook.Calculate(); - ////chart.Series.lab - ////var chart = ws.Drawings[0].As.Chart.Type; + //var paraStr2 = label.GetExistingParagraphStrings(); - //var theCustomLabels = chart.Series[0].DataLabel.DataLabels; + chart.Series[0].DataLabel.SelectRange(ws.Cells["E2:E53"]); - //var addedLabel = theCustomLabels.Add(21); + //chart.Series[0] - //var label21 = theCustomLabels[21]; - //var label26 = theCustomLabels[26]; + //chart.Series[0].XSeries = $"{{'Test{1}','Test{2}'}}"; - //var aNode = (XmlElement)label21.TopNode; + //string myComment = "Mislabeled resistors - wrong part defects"; - //var idxNodeUnderDataLabel = myChart.SelectSingleNode($@"//c:dLbl/c:idx[@val='{20}']", nsm); - //var paragraphNodeForTheText = idxNodeUnderDataLabel.ParentNode.SelectSingleNode("c:tx/c:rich/a:p", nsm); + //CommentOnDataLabel(myChart, 0,20, myComment); + //Save workbook - var myChart = chart.ChartXml; - - string myComment = "Mislabeled resistors - wrong part defects"; - - //CommentOnDataLabel(myChart, 0, 21, myComment); - CommentOnDataLabel(myChart, 0,20, myComment); //ready namespace manager //var nsm = new XmlNamespaceManager(myChart.NameTable); //nsm.AddNamespace(@"c", @"http://schemas.openxmlformats.org/drawingml/2006/chart"); diff --git a/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs b/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs index 1d8568ada8..df379cf0e2 100644 --- a/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs +++ b/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs @@ -1,10 +1,12 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using EPPlusTest.Properties; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; -using OfficeOpenXml; -using EPPlusTest.Properties; namespace EPPlusTest.InCellImages { From 144d32ca57683ae2f9dacf8e5e9350b01b7d021a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 18 Feb 2026 09:52:48 +0100 Subject: [PATCH 06/11] Fixed SelectRange method --- .../Chart/ExcelChartDataLabelStandard.cs | 2 +- .../Drawing/Chart/ExcelChartSerieDataLabel.cs | 10 +- .../Drawing/Chart/ChartSeriesTest.cs | 131 +----------------- 3 files changed, 13 insertions(+), 130 deletions(-) diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs index b56de55262..5ca5a9677d 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs @@ -274,7 +274,7 @@ internal bool ShowDatalabelsRange { CreateNode(rangePath); } - SetBoolNode(rangePath, value); + SetXmlNodeBool(rangePath+"/@val", value); } } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs index d5e7db70fe..bb0b1497ec 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs @@ -88,11 +88,13 @@ public void SelectRange(ExcelRangeBase address) XmlElement ext15Node; - if (currentSeries.ExistsNode(extPath) == false) + var c15Uri = "{02D57815-91ED-43cb-92C2-25804820EDAC}"; + + if (currentSeries.ExistsNode(extPath+ $"[@uri='{c15Uri}']") == false) { XmlElement el = (XmlElement)currentSeries.CreateNode($"{extPath}"); el.SetAttribute("xmlns:c15", ExcelPackage.schemaChart2012); - currentSeries.SetXmlNodeString($"{extPath}/@uri", "{02D57815-91ED-43cb-92C2-25804820EDAC}"); + currentSeries.SetXmlNodeString($"{extPath}/@uri", $"{c15Uri}"); ext15Node = el; } else @@ -119,7 +121,7 @@ public void SelectRange(ExcelRangeBase address) if(DataLabels.Count == 0) { - for (int i = 0; i < currentSeries.Series.Count(); i++) + for (int i = 0; i < currentSeries.NumberOfItems; i++) { var individualLabel = DataLabels.Add(i); individualLabel.AddExtFieldTableEmpty(); @@ -131,4 +133,4 @@ public void SelectRange(ExcelRangeBase address) currentSeries.CreateCache(address.FullAddressAbsolute, rangeNode); } } -} +} \ No newline at end of file diff --git a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs index 88e1b42382..9ad1c60322 100644 --- a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs +++ b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs @@ -250,48 +250,6 @@ public void SimpleChartDataLabels() } } - //[TestMethod] - //public void GenerateExample() - //{ - // using (var package = OpenPackage("manualLayoutChartCommentRange.xlsx", true)) - // { - // var ws = package.Workbook.Worksheets.Add("ManualLayout"); - - // //Create some values - // ws.Cells["A1:A2"].Value = 5; - // ws.Cells["B1:B2"].Value = 10; - - // //Create a column chart - // var sChart = ws.Drawings.AddBarChart("ColumnChart", eBarChartType.ColumnClustered); - - // //Add series (clustered columns) to the chart. In this case 2 per series - // var s1 = sChart.Series.Add(ws.Cells["A1:A2"]); - // var s2 = sChart.Series.Add(ws.Cells["B1:B2"]); - - // //Add a general datalabel - // var label = s1.DataLabel; - // label.ShowValue = true; - - // //Add a specific datalabel to the first column in the cluster - // var dl = label.DataLabels.Add(0); - - // var myLabel = s1.DataLabel.DataLabels[0]; - - // //Offset the data label 10% of the charts width to the left - // //AKA Remove 10 from x coordinate - // dl.Layout.ManualLayout.Left = -10; - - // //Offset the data label 10% of the charts height to the top - // //AKA remove 10 from y coordinate - // dl.Layout.ManualLayout.Top = -10; - - // //Save the package at a path - // package.SaveAs(@"C:\temp\manualLayoutChart.xlsx"); - // } - //} - - - [TestMethod] public void ReadSimpleFile() { @@ -305,22 +263,6 @@ public void ReadSimpleFile() var lblTxtBody = myChart.Series[0].DataLabel.DataLabels[0].TextBody; - - - //var serializedParent = JsonConvert.SerializeObject(test.Font); - //ExcelParagraph castedFont = JsonConvert.DeserializeObject(serializedParent); - - - //castedFont.Text = "My Overriding Comment"; - - //for (int i = 1; i < 4; i++) - //{ - // ws.Cells[i, 2].Value = $"Comment {i}"; - //} - - //var serie = myChart.Series.Add(ws.Cells["A1:A3"], ws.Cells["C1:C3"]); - //serie.DataLabel.ShowValue = true; - SaveAndCleanup(package); } } @@ -334,78 +276,17 @@ public void ReadFile() var chart = ws.Drawings[0].As.Chart.LineChart; - //var label = chart.Series[0].DataLabel.DataLabels[20]; - - //var paraStr = label.GetExistingParagraphStrings(); - //label.OverWriteText("Mislabeled resistors - wrong part defects"); - - //var paraStr2 = label.GetExistingParagraphStrings(); - - chart.Series[0].DataLabel.SelectRange(ws.Cells["E2:E53"]); + chart.Series[0].DataLabel.Separator = " "; - //chart.Series[0] + //Select comment range + chart.Series[0].DataLabel.SelectRange(ws.Cells["E1:E53"]); - //chart.Series[0].XSeries = $"{{'Test{1}','Test{2}'}}"; - - //string myComment = "Mislabeled resistors - wrong part defects"; - - //CommentOnDataLabel(myChart, 0,20, myComment); - - - //Save workbook - - //ready namespace manager - //var nsm = new XmlNamespaceManager(myChart.NameTable); - //nsm.AddNamespace(@"c", @"http://schemas.openxmlformats.org/drawingml/2006/chart"); - //nsm.AddNamespace(@"a", @"http://schemas.openxmlformats.org/drawingml/2006/main"); - - //int serieIdx = 0; - - //var serieNode = myChart.ChildNodes[2].SelectSingleNode(@"c:chart/c:plotArea/c:lineChart/c:ser[c:idx[@val='" + serieIdx + "']]", nsm); - //int dlblIdx = 20; - //var dataLabelToComment = serieNode.SelectSingleNode(@"c:dLbls/c:dLbl[c:idx[@val='" + dlblIdx + "']]", nsm); - - - //dataLabelToComment.InnerXml = $"{myComment}"; - - //"Mislabeled resistors - wrong part defects"; - //var firstSeries = myChart.SelectSingleNode("//c:ser", myChart.namespa); - //var idxNode = firstSeries.SelectSingleNode("//c:idx[@val=\"21\"]"); - - //firstSeries.SelectSingleNode() - - //var theSeries = chart.Series[0].DataPoints - //aNode. - //var myLitterals = chart.Series[0].StringLiteralsX; - //var myLitteralsOther = chart.Series[0].StringLiteralsY; - - //chart.Series[0].CreateCache(); - - //var dataPoint = chart.Series[0].DataPoints[21]; - - //var element = label21.TextBody.PathElement; - //label26. - //var fldParagraph = label21.TextBody.Paragraphs[0]; - //fldParagraph.Text + //Set the relevant labels to not show value + chart.Series[0].DataLabel.DataLabels[21].ShowValue = false; + chart.Series[0].DataLabel.DataLabels[26].ShowValue = false; SaveAndCleanup(package); } } - - static void CommentOnDataLabel(XmlDocument chartXml, int seriesIdx, int datalabelIdx, string myComment) - { - var nsm = new XmlNamespaceManager(chartXml.NameTable); - nsm.AddNamespace(@"c", @"http://schemas.openxmlformats.org/drawingml/2006/chart"); - nsm.AddNamespace(@"a", @"http://schemas.openxmlformats.org/drawingml/2006/main"); - - int serieIdx = 0; - - var serieNode = chartXml.ChildNodes[2].SelectSingleNode(@"c:chart/c:plotArea/c:lineChart/c:ser[c:idx[@val='" + serieIdx + "']]", nsm); - - var dataLabelToComment = serieNode.SelectSingleNode(@"c:dLbls/c:dLbl[c:idx[@val='" + datalabelIdx + "']]", nsm); - - var paragraphNodeForTheText = dataLabelToComment.SelectSingleNode("c:tx/c:rich/a:p", nsm); - paragraphNodeForTheText.InnerXml = $"{myComment}"; - } } } From 75cf091667dbd731e640000dcaa55dce4e60db86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 18 Feb 2026 10:07:36 +0100 Subject: [PATCH 07/11] Removed duplicate changes of develop8 --- .../Drawing/Style/Text/ExcelTextBody.cs | 5 -- .../FormulaParsing/EpplusExcelDataProvider.cs | 16 +--- src/EPPlus/NumberFormatToTextArgs.cs | 51 ++---------- .../Style/Interfaces/IExcelNumberFormat.cs | 23 ------ .../XmlAccess/ExcelNumberFormatWithoutId.cs | 23 ------ .../Style/XmlAccess/ExcelNumberFormatXml.cs | 7 +- .../InCellImages/InCellImagesCacheTests.cs | 2 - src/EPPlusTest/Issues/StylingIssues.cs | 80 ------------------- 8 files changed, 7 insertions(+), 200 deletions(-) delete mode 100644 src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs delete mode 100644 src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs diff --git a/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs b/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs index 70a95ae6a6..ae0635b038 100644 --- a/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs +++ b/src/EPPlus/Drawing/Style/Text/ExcelTextBody.cs @@ -412,10 +412,5 @@ internal void SetFromXml(XmlElement copyFromElement) element.SetAttribute(a.Name, a.NamespaceURI, a.Value); } } - - //internal ExcelParagraph GetFirstParagraph() - //{ - // //$"{_path}/a:spAutoFit" - //} } } diff --git a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs index c51ca54c19..a751b22f7d 100644 --- a/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs +++ b/src/EPPlus/FormulaParsing/EpplusExcelDataProvider.cs @@ -642,21 +642,7 @@ public override string GetFormat(object value, string format, out bool isValidFo { isValidFormat = true; var ws = _currentWorksheet ?? _context.CurrentWorksheet; - - var existingId = ExcelNumberFormat.GetFromBuildIdFromFormat(format); - - NumberFormatToTextArgs arg; - if (existingId == int.MinValue) - { - //The format does not have a corresponding styleId - //Still allow the NumberFormatToTextHandler to see the format - arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, format); - } - else - { - arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, existingId); - } - arg.FromFormula = true; + var arg = new NumberFormatToTextArgs(ws, _context.CurrentCell.Row, _context.CurrentCell.Column, value, ws.GetStyleInner(_context.CurrentCell.Row, _context.CurrentCell.Column)); return _workbook.NumberFormatToTextHandler(arg); } } diff --git a/src/EPPlus/NumberFormatToTextArgs.cs b/src/EPPlus/NumberFormatToTextArgs.cs index 1bbfbe8b66..6c4ce125b3 100644 --- a/src/EPPlus/NumberFormatToTextArgs.cs +++ b/src/EPPlus/NumberFormatToTextArgs.cs @@ -12,7 +12,6 @@ Date Author Change *************************************************************************************************/ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; using OfficeOpenXml.Style; -using OfficeOpenXml.Style.Interfaces; using OfficeOpenXml.Style.XmlAccess; using OfficeOpenXml.Utils.String; @@ -24,32 +23,6 @@ namespace OfficeOpenXml public class NumberFormatToTextArgs { internal int _styleId; - - ExcelNumberFormatWithoutId fallbackNumberFormat = null; - - /// - /// If these args are provided from a formula - /// - public bool FromFormula = false; - - /// - /// Constructor when numberformat is not built in - /// - /// - /// - /// - /// - /// - internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object value, string numberFormat) - { - Worksheet = ws; - Row = row; - Column = column; - Value = value; - _styleId = -1; - fallbackNumberFormat = new ExcelNumberFormatWithoutId(numberFormat); - } - internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object value, int styleId) { Worksheet = ws; @@ -74,18 +47,11 @@ internal NumberFormatToTextArgs(ExcelWorksheet ws, int row, int column, object v /// /// The number format settings for the cell /// - public IExcelNumberFormat NumberFormat - { - get + public ExcelNumberFormatXml NumberFormat + { + get { - if(fallbackNumberFormat != null) - { - return fallbackNumberFormat; - } - else - { - return ValueToTextHandler.GetNumberFormat(_styleId, Worksheet.Workbook.Styles); - } + return ValueToTextHandler.GetNumberFormat(_styleId, Worksheet.Workbook.Styles); } } /// @@ -98,14 +64,7 @@ public IExcelNumberFormat NumberFormat public string Text { get - { - if(fallbackNumberFormat != null) - { - var ft = new ExcelFormatTranslator(NumberFormat.Format, -1); - bool isValidFormat = false; - var frmt = ValueToTextHandler.FormatValue(Value, false, ft, null, out isValidFormat); - return frmt; - } + { return ValueToTextHandler.GetFormattedText(Value, Worksheet.Workbook, _styleId, false); } } diff --git a/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs b/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs deleted file mode 100644 index 594b8b05aa..0000000000 --- a/src/EPPlus/Style/Interfaces/IExcelNumberFormat.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace OfficeOpenXml.Style.Interfaces -{ - public interface IExcelNumberFormat - { - /// - /// The numberformat string - /// - public string Format { get; } - /// - /// Number format Id - /// - public int NumFmtId { get; } - /// - /// If this numberformat is built in - /// - public bool BuildIn { get; } - } -} diff --git a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs deleted file mode 100644 index 9b953ad1f6..0000000000 --- a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatWithoutId.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using OfficeOpenXml.Style.Interfaces; - -namespace OfficeOpenXml.Style.XmlAccess -{ - internal class ExcelNumberFormatWithoutId : IExcelNumberFormat - { - internal ExcelNumberFormatWithoutId(string format) - { - Format = format; - NumFmtId = -1; - BuildIn = false; - } - public string Format { get; private set; } - - public int NumFmtId { get; private set; } - - public bool BuildIn { get; private set; } - } -} diff --git a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs index a0c0eb650e..9707703dfc 100644 --- a/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs +++ b/src/EPPlus/Style/XmlAccess/ExcelNumberFormatXml.cs @@ -10,15 +10,10 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ -using OfficeOpenXml.Style.Interfaces; -using OfficeOpenXml.Utils; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; using System.Xml; namespace OfficeOpenXml.Style.XmlAccess @@ -26,7 +21,7 @@ namespace OfficeOpenXml.Style.XmlAccess /// /// Xml access class for number customFormats /// - public sealed class ExcelNumberFormatXml : StyleXmlHelper, IExcelNumberFormat + public sealed class ExcelNumberFormatXml : StyleXmlHelper { internal ExcelNumberFormatXml(XmlNamespaceManager nameSpaceManager) : base(nameSpaceManager) diff --git a/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs b/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs index df379cf0e2..5f6ca89d87 100644 --- a/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs +++ b/src/EPPlusTest/InCellImages/InCellImagesCacheTests.cs @@ -1,10 +1,8 @@ using EPPlusTest.Properties; using Microsoft.VisualStudio.TestTools.UnitTesting; using OfficeOpenXml; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; diff --git a/src/EPPlusTest/Issues/StylingIssues.cs b/src/EPPlusTest/Issues/StylingIssues.cs index b2884e627a..8acb0c90d5 100644 --- a/src/EPPlusTest/Issues/StylingIssues.cs +++ b/src/EPPlusTest/Issues/StylingIssues.cs @@ -8,7 +8,6 @@ using System.IO; using System.Globalization; using OfficeOpenXml.Style; -using OfficeOpenXml.FormulaParsing; namespace EPPlusTest { @@ -415,85 +414,6 @@ public void i1839() Assert.AreEqual(288, p.Workbook.Worksheets[0].Cells["E31"].StyleID); SaveWorkbook("i1839-saved.xlsx", p); } - - [TestMethod] - public void s1005() - { - SwitchToCulture("de-DE"); - - //Set specific built-in formats - if (!ExcelPackageSettings.CultureSpecificBuildInNumberFormats.ContainsKey("de-DE")) - { - ExcelPackageSettings.CultureSpecificBuildInNumberFormats.Add("de-DE", - new Dictionary - { - {14,"dd.MM.yyyy"}, - {15,"dd. MMM yy"}, - {16,"dd. MMM"}, - {17,"MMM yy"}, - {18,"hh:mm AM/PM" }, - {22,"dd.MM.yyyy hh:mm"}, - {37,"#,##0;-#,##0"}, - {38,"#,##0;[Rot]-#,##0"}, - {39,"#,##0.00;-#,##0.00"}, - {40,"#,##0.00;[Rot]-#,##0.00"}, - {47,"mm:ss,f"} - }); - } - - using (var p = OpenTemplatePackage("s1005.xlsx")) - { - //AND use a Custom Number Format - //This caused issues in the Text.cs file when we ran Calculate - p.Workbook.NumberFormatToTextHandler = CustomNumberFormatToTextExample; - - var ws1 = p.Workbook.Worksheets[1]; - - var origText = ws1.Cells["D8"].Text; - - //Verify cell contents match when test was written - Assert.IsTrue(origText.Contains(".8000")); - Assert.IsTrue(origText.Contains("(26895559.000)")); - Assert.IsTrue(origText.Contains("69928453.64000")); - - ws1.Cells["D8"].Calculate(); - var cellRich = ws1.Cells["D8"].RichText.Text; - - //Verify formatting has changed appropriately for calculated string - Assert.IsTrue(cellRich.Contains("0,80")); - Assert.IsTrue(cellRich.Contains("(26.895.559,00)")); - Assert.IsTrue(cellRich.Contains("69.928.453,64")); - - SaveAndCleanup(p); - } - - SwitchBackToCurrentCulture(); - } - - private static string CustomNumberFormatToTextExample(NumberFormatToTextArgs options) - { - var result = options.Text; - - switch (options.Value) - { - case DateTime dt: - { - switch (options.NumberFormat.Format) - { - case "dd. mmm yy": - result = dt.ToString("dd. mmm yy"); //Return your own formatted text. Example - break; - default: - result = dt.ToString(options.NumberFormat.Format); - break; - } - break; - } - } - - return result; - } - public class TestData { public int Id { get; set; } From 6318d1bd55154122903463cb0441789b793ea116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Wed, 18 Feb 2026 13:02:00 +0100 Subject: [PATCH 08/11] Fixed tab formatting of chart standard serie --- .../Drawing/Chart/ExcelChartStandardSerie.cs | 229 +++++++++--------- 1 file changed, 115 insertions(+), 114 deletions(-) diff --git a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs index dd11c3a38e..42650204ff 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs @@ -37,17 +37,17 @@ public class ExcelChartStandardSerie : ExcelChartSerie /// /// Literals for the Y serie, if the literal values are numeric /// - public override double[] NumberLiteralsY - { - get + public override double[] NumberLiteralsY + { + get { - if(string.IsNullOrEmpty(_seriesNumLitPath) == false && GetNode(_seriesNumLitPath) != null) + if (string.IsNullOrEmpty(_seriesNumLitPath) == false && GetNode(_seriesNumLitPath) != null) { ReadNumLiterals(_seriesNumLitPath, out _NumberLiteralsY); return _NumberLiteralsY; } return _NumberLiteralsY; - } + } protected set { _NumberLiteralsY = value; @@ -56,7 +56,7 @@ protected set /// /// Literals for the X serie, if the literal values are numeric /// - public override double[] NumberLiteralsX + public override double[] NumberLiteralsX { get { @@ -121,25 +121,25 @@ protected set /// Is pivotchart internal ExcelChartStandardSerie(ExcelChart chart, XmlNamespaceManager ns, XmlNode node, bool isPivot) : base(chart, ns, node) - { - _chart = chart; - _isPivot = isPivot; - SchemaNodeOrder = new string[] { "idx", "order", "tx", "spPr", "marker", "invertIfNegative", "pictureOptions", "explosion", "dPt", "dLbls", "trendline","errBars", "cat", "val", "xVal", "yVal", "smooth","shape", "bubbleSize", "bubble3D", "numRef", "numLit", "strRef", "strLit", "formatCode", "ptCount", "pt" }; - - if (_chart.ChartNode.LocalName=="scatterChart" || - _chart.ChartNode.LocalName.StartsWith("bubble", StringComparison.OrdinalIgnoreCase)) - { - _seriesTopPath = "c:yVal"; - _xSeriesTopPath = "c:xVal"; - } - else - { - _seriesTopPath = "c:val"; - _xSeriesTopPath = "c:cat"; - } - - _seriesPath = string.Format(_seriesPath, _seriesTopPath); - _numCachePath = string.Format(_numCachePath, _seriesTopPath); + { + _chart = chart; + _isPivot = isPivot; + SchemaNodeOrder = new string[] { "idx", "order", "tx", "spPr", "marker", "invertIfNegative", "pictureOptions", "explosion", "dPt", "dLbls", "trendline", "errBars", "cat", "val", "xVal", "yVal", "smooth", "shape", "bubbleSize", "bubble3D", "numRef", "numLit", "strRef", "strLit", "formatCode", "ptCount", "pt" }; + + if (_chart.ChartNode.LocalName == "scatterChart" || + _chart.ChartNode.LocalName.StartsWith("bubble", StringComparison.OrdinalIgnoreCase)) + { + _seriesTopPath = "c:yVal"; + _xSeriesTopPath = "c:xVal"; + } + else + { + _seriesTopPath = "c:val"; + _xSeriesTopPath = "c:cat"; + } + + _seriesPath = string.Format(_seriesPath, _seriesTopPath); + _numCachePath = string.Format(_numCachePath, _seriesTopPath); var np = string.Format(_xSeriesParentPath, _xSeriesTopPath, isPivot ? "c:multiLvlStrRef" : "c:numRef"); var sp = string.Format(_xSeriesParentPath, _xSeriesTopPath, isPivot ? "c:multiLvlStrRef" : "c:strRef"); @@ -157,54 +157,55 @@ internal ExcelChartStandardSerie(ExcelChart chart, XmlNamespaceManager ns, XmlNo _xSeriesStrLitPath = string.Format("{0}/c:strLit", _xSeriesTopPath); _xSeriesNumLitPath = string.Format("{0}/c:numLit", _xSeriesTopPath); - } - internal override void SetID(string id) - { - SetXmlNodeString("c:idx/@val",id); - SetXmlNodeString("c:order/@val", id); - } - const string headerPath="c:tx/c:v"; - /// - /// Header for the serie. - /// - public override string Header - { - get - { + } + internal override void SetID(string id) + { + SetXmlNodeString("c:idx/@val", id); + SetXmlNodeString("c:order/@val", id); + } + + const string headerPath = "c:tx/c:v"; + /// + /// Header for the serie. + /// + public override string Header + { + get + { return GetXmlNodeString(headerPath); } set { Cleartx(); - SetXmlNodeString(headerPath, value); + SetXmlNodeString(headerPath, value); } } - private void Cleartx() - { - var n = TopNode.SelectSingleNode("c:tx", NameSpaceManager); - if (n != null) - { - n.InnerXml = ""; - } - } - const string headerAddressPath = "c:tx/c:strRef/c:f"; + private void Cleartx() + { + var n = TopNode.SelectSingleNode("c:tx", NameSpaceManager); + if (n != null) + { + n.InnerXml = ""; + } + } + const string headerAddressPath = "c:tx/c:strRef/c:f"; /// - /// Header address for the serie. - /// - public override ExcelAddressBase HeaderAddress - { - get - { - string address = GetXmlNodeString(headerAddressPath); - if (address == "") - { - return null; - } - else - { - return new ExcelAddressBase(address); - } + /// Header address for the serie. + /// + public override ExcelAddressBase HeaderAddress + { + get + { + string address = GetXmlNodeString(headerAddressPath); + if (address == "") + { + return null; + } + else + { + return new ExcelAddressBase(address); + } } set { @@ -217,7 +218,7 @@ public override ExcelAddressBase HeaderAddress SetXmlNodeString(headerAddressPath, ExcelCellBase.GetFullAddress(value.WorkSheetName, value.Address)); SetXmlNodeString("c:tx/c:strRef/c:strCache/c:ptCount/@val", "0"); } - } + } string _seriesTopPath; string _seriesPath = "{0}/c:numRef/c:f"; string _numCachePath = "{0}/c:numRef/c:numCache"; @@ -227,18 +228,18 @@ public override ExcelAddressBase HeaderAddress /// public override string Series { - get - { - return GetXmlNodeString(_seriesPath); - } - set - { + get + { + return GetXmlNodeString(_seriesPath); + } + set + { value = value.Trim(); if (value.StartsWith("=", StringComparison.OrdinalIgnoreCase)) value = value.Substring(1); if (value.StartsWith("{", StringComparison.OrdinalIgnoreCase) && value.EndsWith("}", StringComparison.OrdinalIgnoreCase)) { GetLitValues(value, out double[] numLit, out string[] strLit); - if(strLit!=null) + if (strLit != null) { throw (new ArgumentException("Value series can't contain strings")); } @@ -255,22 +256,22 @@ public override string Series } - string _xSeries=null; - string _xSeriesTopPath; - string _xSeriesParentPath = "{0}/{1}"; - string _xSeriesPath = "{0}/{1}/c:f"; - string _xSeriesStrLitPath, _xSeriesNumLitPath; + string _xSeries = null; + string _xSeriesTopPath; + string _xSeriesParentPath = "{0}/{1}"; + string _xSeriesPath = "{0}/{1}/c:f"; + string _xSeriesStrLitPath, _xSeriesNumLitPath; /// /// Set an address for the horisontal labels /// - public override string XSeries - { - get - { - return GetXmlNodeString(_xSeriesPath); - } - set - { + public override string XSeries + { + get + { + return GetXmlNodeString(_xSeriesPath); + } + set + { _xSeries = value.Trim(); if (_xSeries.StartsWith("=", StringComparison.OrdinalIgnoreCase)) _xSeries = _xSeries.Substring(1); if (value.StartsWith("{", StringComparison.OrdinalIgnoreCase) && value.EndsWith("}", StringComparison.OrdinalIgnoreCase)) @@ -285,7 +286,7 @@ public override string XSeries NumberLiteralsX = null; StringLiteralsX = null; CreateNode(_xSeriesPath, true); - if(ExcelCellBase.IsValidAddress(_xSeries)) + if (ExcelCellBase.IsValidAddress(_xSeries)) { SetXmlNodeString(_xSeriesPath, ExcelCellBase.GetFullAddress(_chart.WorkSheet.Name, _xSeries)); } @@ -296,7 +297,7 @@ public override string XSeries SetXSerieFunction(); } } - } + } private void ReadNumLiterals(string path, out double[] numberLiterals) { @@ -306,9 +307,9 @@ private void ReadNumLiterals(string path, out double[] numberLiterals) foreach (XmlNode node in childNodes) { - if(node.NodeType==XmlNodeType.Element && node.LocalName == "pt") + if (node.NodeType == XmlNodeType.Element && node.LocalName == "pt") { - if(double.TryParse(node.InnerText, NumberStyles.Any, CultureInfo.InvariantCulture, out double numLit) == false) + if (double.TryParse(node.InnerText, NumberStyles.Any, CultureInfo.InvariantCulture, out double numLit) == false) { throw new InvalidDataException($"numberLiteral in xml node:'{node.Name}' in chart:'{_chart.Name}' with value:'{node.InnerText}' could not be parsed as double. Chart cannot be read."); } @@ -323,7 +324,7 @@ private void ReadStringLiterals(string path, out string[] stringLiterals) var parentNode = GetNode(path); List strLits = new(); - if(parentNode != null) + if (parentNode != null) { var childNodes = parentNode.ChildNodes; @@ -465,12 +466,12 @@ private void SetXSerieFunction() } private void SetLits(double[] numLit, string[] strLit, string numLitPath, string strLitPath) { - if(strLit!=null) + if (strLit != null) { XmlNode lit = CreateNode(strLitPath); SetLitArray(lit, strLit); } - else if(numLit!=null) + else if (numLit != null) { XmlNode lit = CreateNode(numLitPath); SetLitArray(lit, numLit); @@ -506,7 +507,7 @@ private void SetLitArray(XmlNode lit, string[] strLit) { //Remove previous child nodes var previousPt = lit.SelectNodes("c:pt", NameSpaceManager); - if(previousPt != null) + if (previousPt != null) { for (int i = 0; i < previousPt.Count; i++) { @@ -535,9 +536,9 @@ private void AddCount(XmlNode lit, int count) } ExcelChartTrendlineCollection _trendLines = null; - /// - /// Access to the trendline collection - /// + /// + /// Access to the trendline collection + /// public override ExcelChartTrendlineCollection TrendLines { get @@ -556,7 +557,7 @@ public override int NumberOfItems { get { - if(ExcelCellBase.IsValidAddress(Series)) + if (ExcelCellBase.IsValidAddress(Series)) { var a = new ExcelAddressBase(Series); return a.Rows; @@ -574,16 +575,16 @@ public override int NumberOfItems /// public void CreateCache() { - if (_isPivot) throw(new NotImplementedException("Cache for pivotcharts has not been implemented yet.")); + if (_isPivot) throw (new NotImplementedException("Cache for pivotcharts has not been implemented yet.")); if (!string.IsNullOrEmpty(Series)) { - if(new ExcelRangeBase(_chart.WorkSheet, Series).Columns > 1) + if (new ExcelRangeBase(_chart.WorkSheet, Series).Columns > 1) { throw (new InvalidOperationException("A serie cannot be multiple columns. Please add one serie per column to create a cache")); } var node = GetTopNode(Series, _seriesTopPath); - + CreateCache(Series, node); } @@ -634,7 +635,7 @@ internal void CreateCache(string address, XmlNode node) } CreateCacheFromRange(node, ws.Cells[address]); } - + } private void CreateCacheFromRange(XmlNode node, ExcelRangeBase range) @@ -642,14 +643,14 @@ private void CreateCacheFromRange(XmlNode node, ExcelRangeBase range) if (range == null) return; var startRow = range._fromRow; var items = 0; - var cse = new CellStoreEnumerator(range.Worksheet._values, startRow,range._fromCol, range._toRow, range._toCol); + var cse = new CellStoreEnumerator(range.Worksheet._values, startRow, range._fromCol, range._toRow, range._toCol); while (cse.Next()) { var v = cse.Value._value; if (v != null) { string xmlValue = ""; - if(v.IsNumeric()) + if (v.IsNumeric()) { var d = Utils.TypeConversion.ConvertUtil.GetValueDouble(v); xmlValue = Utils.TypeConversion.ConvertUtil.GetValueForXml(d, range.Worksheet.Workbook.Date1904); @@ -711,10 +712,10 @@ private XmlNode GetTopNode(string address, string seriesTopPath) if (addr.IsExternal) { var erIx = wb.ExternalLinks.GetExternalLink(addr._wb); - if(erIx>=0) + if (erIx >= 0) { var er = wb.ExternalLinks[erIx].As.ExternalWorkbook; - if(er.Package!=null) + if (er.Package != null) { var ws = er.Package.Workbook.Worksheets[addr.WorkSheetName]; var range = ws.Cells[addr.LocalAddress]; @@ -723,7 +724,7 @@ private XmlNode GetTopNode(string address, string seriesTopPath) else { var ws = er.CachedWorksheets[addr.WorkSheetName]; - if(ws==null) + if (ws == null) { v = null; } @@ -739,7 +740,7 @@ private XmlNode GetTopNode(string address, string seriesTopPath) v = null; } } - else + else { ExcelWorksheet ws; if (string.IsNullOrEmpty(addr.WorkSheetName)) @@ -763,22 +764,22 @@ private XmlNode GetTopNode(string address, string seriesTopPath) string cachePath; bool isNum; - if(Utils.TypeConversion.ConvertUtil.IsNumericOrDate(v) || v is null) + if (Utils.TypeConversion.ConvertUtil.IsNumericOrDate(v) || v is null) { cachePath = string.Format("{0}/c:numRef/c:numCache", seriesTopPath); isNum = true; } else { - cachePath=string.Format("{0}/c:strRef/c:strCache", seriesTopPath); + cachePath = string.Format("{0}/c:strRef/c:strCache", seriesTopPath); isNum = false; } var node = CreateNode(cachePath); if (node.HasChildNodes) { - if(isNum) + if (isNum) { - if(node.FirstChild.LocalName== "formatCode") + if (node.FirstChild.LocalName == "formatCode") { node.InnerXml = node.FirstChild.OuterXml; } @@ -789,7 +790,7 @@ private XmlNode GetTopNode(string address, string seriesTopPath) } else { - node.InnerXml = ""; + node.InnerXml = ""; } } CreateNode($"{cachePath}/c:ptCount"); @@ -807,14 +808,14 @@ internal static XmlElement CreateSerieElement(ExcelChart chart) //If the chart is added from a chart template, then use the chart templates series xml if (chart._drawings._seriesTemplateXml != null) { - if(chart._drawings._seriesTemplateXml.Count != 0) + if (chart._drawings._seriesTemplateXml.Count != 0) { ser.InnerXml = chart._drawings._seriesTemplateXml[0]; return ser; } } - int idx = FindIndex(chart._topChart??chart); + int idx = FindIndex(chart._topChart ?? chart); ser.InnerXml = string.Format("{2}{5}{0}{3}{4}", AddExplosion(chart.ChartType), idx, AddSpPrAndScatterPoint(chart.ChartType), AddAxisNodes(chart.ChartType), AddSmooth(chart.ChartType), AddMarker(chart.ChartType)); return ser; } From 5e331783d23d98ac2585dd57fbff4166b5e7f239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 19 Feb 2026 15:47:17 +0100 Subject: [PATCH 09/11] Simplified methods. Added reading of range --- .../Drawing/Chart/ExcelBarChartSerie.cs | 2 +- .../Chart/ExcelChartDataLabelCollection.cs | 2 +- .../Drawing/Chart/ExcelChartDataLabelItem.cs | 4 +- .../Chart/ExcelChartDataLabelStandard.cs | 14 +- .../Drawing/Chart/ExcelChartSerieDataLabel.cs | 128 +++++++++++------- .../Drawing/Chart/ExcelChartStandardSerie.cs | 67 +++++++++ .../Drawing/Chart/ChartSeriesTest.cs | 125 +++++++++++++++++ 7 files changed, 285 insertions(+), 57 deletions(-) diff --git a/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs b/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs index feb80bdd6f..ce0cf52961 100644 --- a/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelBarChartSerie.cs @@ -44,7 +44,7 @@ public ExcelChartSerieDataLabel DataLabel { if (_dataLabel == null) { - if (ExcelChartDataLabelStandard.ForbiddDataLabelPosition(_chart) == false) + if (ExcelChartDataLabelStandard.IsDataLabelPositionForbidden(_chart) == false) { _dataLabel = new ExcelChartSerieDataLabel(_chart, NameSpaceManager, TopNode, SchemaNodeOrder); } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs index dcb8aa4785..613500be9c 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelCollection.cs @@ -84,7 +84,7 @@ private ExcelChartDataLabelItem CreateDataLabel(int idx) dl.ShowLegendKey = parentDatalabel.ShowLegendKey; dl.ShowLeaderLines = true; dl.ShowValue = true; - dl.Position = eLabelPosition.Center; + dl.Position = parentDatalabel.Position; if (idx < _list.Count) { diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs index 3788b3cf46..3331d7923f 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs @@ -60,7 +60,7 @@ private ExcelParagraphCollection ParagraphCollection /// Replace datalabel text /// /// - public void OverWriteText(string replacementText) + public void SetText(string replacementText) { ParagraphCollection.Clear(); ParagraphCollection.Add(replacementText, true); @@ -84,5 +84,7 @@ public int Index SetXmlNodeString("c:idx/@val", value.ToString(CultureInfo.InvariantCulture)); } } + + internal ExcelAddressBase SingleCellAddressFromSeries; } } diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs index 5ca5a9677d..0bbe7e6212 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelStandard.cs @@ -77,7 +77,10 @@ internal ExcelChartDataLabelStandard(ExcelChart chart, XmlNamespaceManager ns, X const string positionPath = "c:dLblPos/@val"; /// /// Position of the labels - /// Note: Only Center, InEnd and InBase are allowed for dataLabels on stacked columns + ///
BE AWARE! For SERIES labels and all underlying labels:
+ /// Setting a position not available for this label in the Excel UI May cause a corrupt file.
+ /// Note: Only Center, InEnd and InBase are allowed for dataLabels on stacked columns
+ /// (Same applies to most BarCharts but they allow OutEnd) ///
public override eLabelPosition Position { @@ -87,14 +90,19 @@ public override eLabelPosition Position } set { - if (ForbiddDataLabelPosition(_chart)) + if (IsDataLabelPositionForbidden(_chart)) { throw new InvalidOperationException("Can't set data label position on a 3D-chart"); } + if(_chart.ChartType == eChartType.ColumnClustered && value == eLabelPosition.Top) + { + throw new InvalidOperationException($"DataLabelPosition: '{value}' is not allowed on chart of type: '{_chart.ChartType}' \n " + + $"because it would cause a corrupt file"); + } SetXmlNodeString(positionPath, GetPosText(value)); } } - internal static bool ForbiddDataLabelPosition(ExcelChart _chart) + internal static bool IsDataLabelPositionForbidden(ExcelChart _chart) { return _chart.IsType3D() && !_chart.IsTypePie() && _chart.ChartType != eChartType.Line3D || _chart.IsTypeDoughnut(); diff --git a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs index bb0b1497ec..c9bfa044ea 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartSerieDataLabel.cs @@ -12,11 +12,13 @@ Date Author Change *************************************************************************************************/ using OfficeOpenXml.Drawing.Interfaces; using OfficeOpenXml.Drawing.Style.Effect; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.Style; using System; using System.Collections.Generic; using System.Data; using System.Linq; +using System.Net; using System.Text; using System.Xml; @@ -31,6 +33,13 @@ internal ExcelChartSerieDataLabel(ExcelChart chart, XmlNamespaceManager ns, XmlN : base(chart, ns, node, "dLbls", schemaNodeOrder) { Position = eLabelPosition.Center; + var parentSeries = GetParentSeries(); + + var address = parentSeries.GetDataLabelRange(); + if (string.IsNullOrEmpty(address) == false) + { + DataLabelRange = chart.WorkSheet.Cells[address]; + } } ExcelChartDataLabelCollection _dataLabels = null; /// @@ -43,6 +52,23 @@ public ExcelChartDataLabelCollection DataLabels if (_dataLabels == null) { _dataLabels = new ExcelChartDataLabelCollection(_chart, NameSpaceManager, TopNode, SchemaNodeOrder, this as ExcelChartDataLabelStandard); + + //Fill datalabel addresses + if(DataLabelRange != null) + { + var address = DataLabelRange; + for(int i = 0; i< _dataLabels.Count(); i++) + { + if (address.Rows > address.Columns) + { + _dataLabels[i].SingleCellAddressFromSeries = address.TakeSingleCell(i, 0); + } + else + { + _dataLabels[i].SingleCellAddressFromSeries = address.TakeSingleCell(0, i); + } + } + } } return _dataLabels; } @@ -54,7 +80,23 @@ public ExcelChartDataLabelCollection DataLabels /// public bool ValueFromCells { get { return DataLabelRange != null; } } - ExcelRangeBase DataLabelRange = null; + internal ExcelRangeBase DataLabelRange { get; private set; } = null; + + + ExcelChartStandardSerie GetParentSeries() + { + //TODO: The way we aquire the Series instance here is clumsy. + //Fix as part of datalabel refactor? + //Perhaps the series of a series label should be part of its constructor. + //Or use an eventhandler + //For a single case however that feels overkill. + + //Has to get the series index: + var idxNode = (XmlElement)TopNode.ParentNode.SelectSingleNode($"{NsPrefix}:idx", NameSpaceManager); + var idxNodeValue = int.Parse(idxNode.GetAttribute("val")); + //Get the series this datalabel is on + return (ExcelChartStandardSerie)_chart.Series[idxNodeValue]; + } /// /// Select datalabel range for @@ -64,6 +106,11 @@ public ExcelChartDataLabelCollection DataLabels /// Thrown when input is not a cell, a row or a column public void SelectRange(ExcelRangeBase address) { + //TODO: Arguably this is just another series with a series cache. + //Same as Cat or Val except that it is added in Ext on the Serie node + //ShowValue property essentially changes the datalabels in the same way. + //This could be unified somehow so that all serie ranges; Cat, Val and DataLabelRange are handled the same way. + bool moreThanOneRow = address.Rows > 1; bool moreThanOneColumn = address.Columns > 1; @@ -75,62 +122,41 @@ public void SelectRange(ExcelRangeBase address) DataLabelRange = address; - //Has to get the series index: - var idxNode = (XmlElement)TopNode.ParentNode.SelectSingleNode($"{NsPrefix}:idx", NameSpaceManager); - var idxNodeValue = int.Parse(idxNode.GetAttribute("val")); - - var currentSeries = (ExcelChartStandardSerie)_chart.Series[idxNodeValue]; - - currentSeries.NameSpaceManager.AddNamespace("c15", ExcelPackage.schemaChart2012); - currentSeries.NameSpaceManager.AddNamespace("c16", ExcelPackage.schemaChart2014); - - string extPath = "c:extLst/c:ext"; - - XmlElement ext15Node; + var currentSeries = GetParentSeries(); + //Set the ext data needed in the Series node + currentSeries.SetDataLabelRange(address); - var c15Uri = "{02D57815-91ED-43cb-92C2-25804820EDAC}"; - - if (currentSeries.ExistsNode(extPath+ $"[@uri='{c15Uri}']") == false) - { - XmlElement el = (XmlElement)currentSeries.CreateNode($"{extPath}"); - el.SetAttribute("xmlns:c15", ExcelPackage.schemaChart2012); - currentSeries.SetXmlNodeString($"{extPath}/@uri", $"{c15Uri}"); - ext15Node = el; - } - else - { - ext15Node = (XmlElement)currentSeries.GetNode($"{extPath}"); - } - - if (currentSeries.ExistsNode($"{extPath}[2]") == false) - { - XmlElement element = (XmlElement)currentSeries.CreateNode($"{extPath}", false, true); - element.SetAttribute("xmlns:c16", ExcelPackage.schemaChart2014); - currentSeries.SetXmlNodeString($"{extPath}[2]/@uri", "{C3380CC4-5D6E-409C-BE32-E72D297353CC}"); - var _guidId = Guid.NewGuid(); - - var extNode2 = currentSeries.GetNode($"{extPath}[2]"); - var uniqueIdNode = (XmlElement)CreateNode(extNode2, "c16:uniqueID"); - uniqueIdNode.SetAttribute("val", $"{{{_guidId}}}"); - } - - var dlblRangePath = $"{extPath}/c15:datalabelsRange"; - var datalabelsRange = currentSeries.CreateNode(dlblRangePath); - var formulaNode = currentSeries.CreateNode($"{dlblRangePath}/c15:f"); - formulaNode.InnerText = address.AddressAbsolute; - - if(DataLabels.Count == 0) + //Create the Datalabels if they do not exist + if (DataLabels.Count < currentSeries.NumberOfItems) { for (int i = 0; i < currentSeries.NumberOfItems; i++) { - var individualLabel = DataLabels.Add(i); - individualLabel.AddExtFieldTableEmpty(); - individualLabel.ShowDatalabelsRange = true; + ExcelChartDataLabelItem currentLabel; + if (DataLabels.Count - 1 < i) + { + currentLabel = DataLabels.Add(i); + } + else + { + currentLabel = DataLabels[i]; + } + currentLabel.AddExtFieldTableEmpty(); + currentLabel.ShowDatalabelsRange = true; + + if (address.Rows > address.Columns) + { + currentLabel.SingleCellAddressFromSeries = address.TakeSingleCell(i, 0); + } + else + { + currentLabel.SingleCellAddressFromSeries = address.TakeSingleCell(0, i); + } + + ////Adds field CellRange to the paragraph of the label + ///For backwards compatability if opened in excel versions prior to Excel 2013 + //currentLabel.AddField("CELLRANGE"); } } - - var rangeNode = currentSeries.CreateNode($"{dlblRangePath}/c15:dlblRangeCache"); - currentSeries.CreateCache(address.FullAddressAbsolute, rangeNode); } } } \ No newline at end of file diff --git a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs index 42650204ff..52c5c6cc2f 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartStandardSerie.cs @@ -20,6 +20,7 @@ Date Author Change using System.Runtime.CompilerServices; using System.IO; using OfficeOpenXml.FormulaParsing.Utilities; + namespace OfficeOpenXml.Drawing.Chart { /// @@ -34,6 +35,9 @@ public class ExcelChartStandardSerie : ExcelChartSerie string[] _StringLiteralsX = null; string[] _StringLiteralsY = null; + const string extPath = "c:extLst/c:ext"; + const string dlblRangePath = "c:extLst/c:ext/c15:datalabelsRange"; + /// /// Literals for the Y serie, if the literal values are numeric /// @@ -164,6 +168,69 @@ internal override void SetID(string id) SetXmlNodeString("c:order/@val", id); } + internal bool HasDataLabelRange() + { + return ExistsNode(dlblRangePath); + } + + internal string GetDataLabelRange() + { + if(ExistsNode($"{dlblRangePath}/c15:f")) + { + return GetXmlNodeString($"{dlblRangePath}/c15:f"); + } + return null; + } + + internal void AddExtLstXml() + { + NameSpaceManager.AddNamespace("c15", ExcelPackage.schemaChart2012); + NameSpaceManager.AddNamespace("c16", ExcelPackage.schemaChart2014); + + XmlElement ext15Node; + + var c15Uri = "{02D57815-91ED-43cb-92C2-25804820EDAC}"; + + //Only add node if it doesn't already exist + if (ExistsNode(extPath + $"[@uri='{c15Uri}']") == false) + { + XmlElement el = (XmlElement)CreateNode($"{extPath}"); + el.SetAttribute("xmlns:c15", ExcelPackage.schemaChart2012); + SetXmlNodeString($"{extPath}/@uri", $"{c15Uri}"); + ext15Node = el; + } + else + { + ext15Node = (XmlElement)GetNode($"{extPath}"); + } + + //Only add node if it doesn't already exist + if (ExistsNode($"{extPath}[2]") == false) + { + XmlElement element = (XmlElement)CreateNode($"{extPath}", false, true); + element.SetAttribute("xmlns:c16", ExcelPackage.schemaChart2014); + SetXmlNodeString($"{extPath}[2]/@uri", "{C3380CC4-5D6E-409C-BE32-E72D297353CC}"); + var _guidId = Guid.NewGuid(); + + var extNode2 = GetNode($"{extPath}[2]"); + var uniqueIdNode = (XmlElement)CreateNode(extNode2, "c16:uniqueID"); + uniqueIdNode.SetAttribute("val", $"{{{_guidId}}}"); + } + } + + + internal void SetDataLabelRange(ExcelRangeBase address) + { + AddExtLstXml(); + + var datalabelsRange = CreateNode(dlblRangePath); + var formulaNode = CreateNode($"{dlblRangePath}/c15:f"); + formulaNode.InnerText = address.AddressAbsolute; + + var rangeNode = CreateNode($"{dlblRangePath}/c15:dlblRangeCache"); + CreateCache(address.FullAddressAbsolute, rangeNode); + } + const string headerPath = "c:tx/c:v"; /// /// Header for the serie. diff --git a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs index 9ad1c60322..97592e2c99 100644 --- a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs +++ b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs @@ -250,6 +250,92 @@ public void SimpleChartDataLabels() } } + [TestMethod] + //TODO: This test is one instance of a larger problem + //Many datalabels have different allowed positions depending on chart type + //Going against it will often create corrupt files. + //See microsoft offical documentation: + //"MS-OE376" page 659 2.1.1475 Part 4 Section 5.7.2.48, dLblPos (Data Label Position) for details. + public void TopIsDisallowedOnBarDataLabels() + { + using (var p = new ExcelPackage()) + { + var ws = p.Workbook.Worksheets.Add("DataLabelSheet"); + + ws.Cells["A1"].Value = "Week"; + ws.Cells["B1"].Value = "Income"; + + ws.Cells["A2:A10"].Formula = $"\"Week \"&(ROW()-1)"; + ws.Cells["B2:B10"].Formula = $"(ROW()-1)*7"; + ws.Calculate(); + + var chart = ws.Drawings.AddBarChart("columnChart", eBarChartType.ColumnClustered); + chart.Series.Add(ws.Cells["B2:B10"], ws.Cells["A2:A10"]); + + var SeriesDataLabel = chart.Series[0].DataLabel; + + Assert.Throws(() => SeriesDataLabel.Position = eLabelPosition.Top); + } + } + + [TestMethod] + public void CreateFileWithDataLabelsManualAndGeneral() + { + using (var p = OpenPackage("dlblMissMatchTest.xlsx", true)) + { + var ws = p.Workbook.Worksheets.Add("DataLabelSheet"); + + ws.Cells["A1"].Value = "Week"; + ws.Cells["B1"].Value = "Income"; + + ws.Cells["A2:A10"].Formula = $"\"Week \"&(ROW()-1)"; + ws.Cells["B2:B10"].Formula = $"(ROW()-1)*7"; + ws.Cells["C2:C10"].Formula = $"\"Comment \"&(ROW()-1)"; + ws.Calculate(); + + var chart = ws.Drawings.AddBarChart("columnChart", eBarChartType.ColumnClustered); + + var barSerie = chart.Series.Add(ws.Cells["B2:B10"], ws.Cells["A2:A10"]); + var sDlbl = barSerie.DataLabel; + + sDlbl.Separator = ","; + sDlbl.ShowValue = true; + sDlbl.ShowCategory = true; + sDlbl.Position = eLabelPosition.OutEnd; + + sDlbl.SelectRange(ws.Cells["C2:C10"]); + Assert.AreEqual(ws.Cells["C2:C10"], barSerie.DataLabel.DataLabelRange); + + Assert.AreEqual("C7", chart.Series[0].DataLabel.DataLabels[5].SingleCellAddressFromSeries.Address); + Assert.AreEqual("Comment 6", ws.Cells["C7"].Text); + + //Ensure replacement text works + var labelFive = chart.Series[0].DataLabel.DataLabels[5]; + labelFive.SetText("My replacement text"); + + Assert.AreEqual("My replacement text", labelFive.GetExistingParagraphStrings()[0][0]); + + SaveAndCleanup(p); + } + + //Ensure data is read correctly after write + using (var p = OpenPackage("dlblMissMatchTest.xlsx")) + { + var ws = p.Workbook.Worksheets[0]; + var chart = ws.Drawings[0].As.Chart.BarChart; + + var barSerie = chart.Series[0]; + Assert.AreEqual(ws.Cells["C2:C10"], barSerie.DataLabel.DataLabelRange); + + Assert.AreEqual("C7", chart.Series[0].DataLabel.DataLabels[5].SingleCellAddressFromSeries.Address); + Assert.AreEqual("Comment 6", ws.Cells["C7"].Text); + + //Ensure replacement text works + var labelFive = chart.Series[0].DataLabel.DataLabels[5]; + Assert.AreEqual("My replacement text", labelFive.GetExistingParagraphStrings()[0][0]); + } + } + [TestMethod] public void ReadSimpleFile() { @@ -285,6 +371,45 @@ public void ReadFile() chart.Series[0].DataLabel.DataLabels[21].ShowValue = false; chart.Series[0].DataLabel.DataLabels[26].ShowValue = false; + Assert.AreEqual("E22", chart.Series[0].DataLabel.DataLabels[21].SingleCellAddressFromSeries.Address); + Assert.AreEqual("First Comment", ws.Cells["E22"].Text); + + SaveAndCleanup(package); + } + } + + [TestMethod] + public void TestAddCommentRangeToExistingFile() + { + using (var package = OpenTemplatePackage("S1008_NoComment.xlsx")) + { + var ws = package.Workbook.Worksheets[0]; + + var commentText = "Added Comment"; + + ws.Cells["E30"].Value = commentText; + + var chart = ws.Drawings[0].As.Chart.LineChart; + chart.Series[0].DataLabel.Separator = " "; + + //Select comment range + chart.Series[0].DataLabel.SelectRange(ws.Cells["E2:E53"]); + + //Note that since we start on E2 the datalabel idx becomes 20 for row 22 etc. + var label1 = chart.Series[0].DataLabel.DataLabels[20]; + var label2 = chart.Series[0].DataLabel.DataLabels[25]; + var label3 = chart.Series[0].DataLabel.DataLabels[28]; + + //Set the relevant labels to not show value as we only want them to show comments + label1.ShowValue = false; + label2.ShowValue = false; + label3.ShowValue = false; + + Assert.AreEqual("E30", chart.Series[0].DataLabel.DataLabels[28].SingleCellAddressFromSeries.Address); + Assert.AreEqual(commentText, ws.Cells["E30"].Text); + + //XforSave is set soley on labels that are not truly neccesary + SaveAndCleanup(package); } } From 12ceaf43e3961c7f35f9a15b28a85fdaf11a63f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 19 Feb 2026 15:55:45 +0100 Subject: [PATCH 10/11] Removed unnecesary comment --- src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs index 3331d7923f..417c05e041 100644 --- a/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs +++ b/src/EPPlus/Drawing/Chart/ExcelChartDataLabelItem.cs @@ -48,8 +48,6 @@ private ExcelParagraphCollection ParagraphCollection { if (_paragraphs == null) { - //var firstParaPath = _textBodyPropertiesParentPath + $"/{NsPrefix}:p"; - //par.SelectNodes("a:r", NameSpaceManager); _paragraphs = new ExcelParagraphCollection(_chart, NameSpaceManager, TopNode, _fontPropertiesPath + "/a:p", SchemaNodeOrder); } return _paragraphs; From 0cb71b1e3bcd813468ec370bd68b8f92a59b906b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ossian=20Edstr=C3=B6m?= Date: Thu, 19 Feb 2026 16:22:16 +0100 Subject: [PATCH 11/11] Fixed minor test issue --- src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs index 97592e2c99..eb4ae0bd44 100644 --- a/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs +++ b/src/EPPlusTest/Drawing/Chart/ChartSeriesTest.cs @@ -372,7 +372,7 @@ public void ReadFile() chart.Series[0].DataLabel.DataLabels[26].ShowValue = false; Assert.AreEqual("E22", chart.Series[0].DataLabel.DataLabels[21].SingleCellAddressFromSeries.Address); - Assert.AreEqual("First Comment", ws.Cells["E22"].Text); + Assert.AreEqual("First comment", ws.Cells["E22"].Text); SaveAndCleanup(package); }