diff --git a/src/EPPlus/ConditionalFormatting/ExcelConditionalFormattingCollection.cs b/src/EPPlus/ConditionalFormatting/ExcelConditionalFormattingCollection.cs index f226ff88c..d602d7dd3 100644 --- a/src/EPPlus/ConditionalFormatting/ExcelConditionalFormattingCollection.cs +++ b/src/EPPlus/ConditionalFormatting/ExcelConditionalFormattingCollection.cs @@ -462,7 +462,12 @@ void ApplyIconSetExtValues( iconArr[i].Type = types[i].ToEnum() .GetValueOrDefault(); - if(double.TryParse(values[i], out double result)) + // A formula cfvo stores its expression in the @val attribute, even + // when that expression is a numeric constant. Only assign Value for + // the numeric-value types; otherwise route through Formula. This + // mirrors ReadIcon and the databar reader. + if(iconArr[i].Type != eExcelConditionalFormattingValueObjectType.Formula + && double.TryParse(values[i], NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) { iconArr[i].Value = result; } diff --git a/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs b/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs index b9ad62b69..baba29937 100644 --- a/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs +++ b/src/EPPlusTest/Issues/ConditionalFormattingIssues.cs @@ -2,9 +2,11 @@ using OfficeOpenXml; using OfficeOpenXml.ConditionalFormatting; using OfficeOpenXml.ConditionalFormatting.Contracts; +using OfficeOpenXml.ConditionalFormatting.Rules; using OfficeOpenXml.Style; using System.Drawing; using System.Globalization; +using System.IO; using System.Threading; namespace EPPlusTest.Issues @@ -216,5 +218,88 @@ public void i2381Excel() package.SaveAs(file); } } + + [TestMethod] + public void RoundTrip_ExtIconSetWithNumericFormulaCfvo_DoesNotThrow() + { + // Arrange - build an icon set that is written to the extLst + // (3Stars always goes to extLst) with a Formula-type threshold + // whose value is a numeric constant. Saving to a stream produces + // the exact same OOXML bytes as saving to a file. + var stream = new MemoryStream(); + + using (var package = new ExcelPackage()) + { + var ws = package.Workbook.Worksheets.Add("Sheet1"); + + for (int row = 1; row <= 10; row++) + { + ws.Cells[row, 1].Value = row; + } + + var iconSet = ws.ConditionalFormatting.AddThreeIconSet( + new ExcelAddress("A1:A10"), + eExcelconditionalFormatting3IconsSetType.Stars); + + // Third threshold: Formula type with a numeric constant. + iconSet.Icon3.Type = eExcelConditionalFormattingValueObjectType.Formula; + iconSet.Icon3.Formula = "67"; + + package.SaveAs(stream); + } + + // Act & Assert - reloading must not throw (threw before the fix + // in ApplyIconSetExtValues). + stream.Position = 0; + using (var package = new ExcelPackage(stream)) + { + var ws = package.Workbook.Worksheets[0]; + var iconSet = (IExcelConditionalFormattingThreeIconSet) + ws.ConditionalFormatting[0]; + + Assert.AreEqual(eExcelConditionalFormattingValueObjectType.Formula, iconSet.Icon3.Type); + Assert.AreEqual("67", iconSet.Icon3.Formula); + } + } + + [TestMethod] + public void RoundTrip_RegularIconSetWithNumericFormulaCfvo_DoesNotThrow() + { + // Arrange - a regular (non-ext) icon set with a Formula-type + // threshold whose value is a numeric constant. This exercises the + // ReadIcon path, which was already correct, guarding against a + // future regression there. + var stream = new MemoryStream(); + + using (var package = new ExcelPackage()) + { + var ws = package.Workbook.Worksheets.Add("Sheet1"); + + for (int row = 1; row <= 10; row++) + { + ws.Cells[row, 1].Value = row; + } + + var iconSet = ws.ConditionalFormatting.AddThreeIconSet( + new ExcelAddress("A1:A10"), + eExcelconditionalFormatting3IconsSetType.Arrows); // regular set, not extLst + + iconSet.Icon3.Type = eExcelConditionalFormattingValueObjectType.Formula; + iconSet.Icon3.Formula = "67"; + + package.SaveAs(stream); + } + + stream.Position = 0; + using (var package = new ExcelPackage(stream)) + { + var ws = package.Workbook.Worksheets[0]; + var iconSet = (IExcelConditionalFormattingThreeIconSet) + ws.ConditionalFormatting[0]; + + Assert.AreEqual(eExcelConditionalFormattingValueObjectType.Formula, iconSet.Icon3.Type); + Assert.AreEqual("67", iconSet.Icon3.Formula); + } + } } }