From b32b71a7f67239f5a76a93869368b3e4fb7cc03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Tue, 11 Aug 2026 09:40:24 +0200 Subject: [PATCH 1/5] Fixes issue #2459. Fix for copying tables/pivottables within a worksheet. --- .../Core/Worksheet/WorksheetCopyHelper.cs | 36 ++++++------- .../FormulaParsing/CalculateExtensions.cs | 2 +- .../DependencyChain/RpnFormulaExecution.cs | 51 +++++++++++++++++-- .../Functions/FunctionParameterInformation.cs | 4 ++ .../Functions/MathFunctions/AverageIfs.cs | 2 +- .../Excel/Functions/MathFunctions/CountIfs.cs | 2 +- .../MathFunctions/RangeCriteriaFunction.cs | 16 +++--- .../Excel/Functions/MathFunctions/SumIfs.cs | 2 +- .../Issues/FormulaCalculationIssues.cs | 12 ++++- 9 files changed, 92 insertions(+), 35 deletions(-) diff --git a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs index 05cdf6a85e..8460b0271b 100644 --- a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs +++ b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs @@ -111,15 +111,14 @@ internal static ExcelWorksheet Copy(ExcelWorksheets targetWorksheets, string nam CopySlicers(sourceWorksheet, targetWorksheet); CopyDrawing(sourceWorksheet, targetWorksheet); } - List> copiedTableNames = null; + Dictionary copiedTableNames = null, copiedPivotTableNames=null; if (sourceWorksheet.Tables.Count > 0) { - copiedTableNames = CopyTable(sourceWorksheet, targetWorksheet); - } + copiedTableNames = CopyTable(sourceWorksheet, targetWorksheet); } if (sourceWorksheet.PivotTables.Count > 0) { - CopyPivotTable(sourceWorksheet, targetWorksheet); + copiedPivotTableNames = CopyPivotTable(sourceWorksheet, targetWorksheet); } CopyDefinedNames(sourceWorksheet, targetWorksheet); @@ -142,7 +141,7 @@ internal static ExcelWorksheet Copy(ExcelWorksheets targetWorksheets, string nam //Copy dfx styles used in conditional formatting. if (!(sourceWorksheet.Workbook == targetWorksheet.Workbook)) { - CopyDxfStyles(sourceWorksheet, targetWorksheet); + CopyDxfStyles(sourceWorksheet, targetWorksheet, copiedTableNames, copiedPivotTableNames); } //Copy the VBA code @@ -192,7 +191,7 @@ internal static ExcelWorksheet Copy(ExcelWorksheets targetWorksheets, string nam return targetWorksheet; } - private static void ApplyTableCopyOptions(ExcelWorksheet added, ExcelWorksheetCopyOptions options, List> copiedTableNames) + private static void ApplyTableCopyOptions(ExcelWorksheet added, ExcelWorksheetCopyOptions options, Dictionary copiedTableNames) { if (options == null || options.TableCopyHandler == null || copiedTableNames == null) { @@ -986,6 +985,7 @@ private static void CopyDefinedNames(ExcelWorksheet Copy, ExcelWorksheet added) wbName.IsNameHidden = name.IsNameHidden; } } + //Copy names from formulas. if (sameWorkbook == false) { @@ -1065,9 +1065,9 @@ private static bool HasExternalReference(string formula) return false; } - private static List> CopyTable(ExcelWorksheet sourceWs, ExcelWorksheet destWs) + private static Dictionary CopyTable(ExcelWorksheet sourceWs, ExcelWorksheet destWs) { - var copiedTableNames = new List>(); + var copiedTableNames = new Dictionary(); string prevName = ""; //First copy the table XML foreach (var tbl in sourceWs.Tables) @@ -1101,7 +1101,7 @@ private static List> CopyTable(ExcelWorksheet sourc int Id = destWs.Workbook._nextTableID++; prevName = name; - copiedTableNames.Add(new KeyValuePair(tbl.Name, name)); + copiedTableNames.Add(tbl.Name, name); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); @@ -1185,7 +1185,7 @@ private static List> CopyTable(ExcelWorksheet sourc return copiedTableNames; } - private static void CopyPivotTable(ExcelWorksheet sourceWs, ExcelWorksheet destWs) + private static Dictionary CopyPivotTable(ExcelWorksheet sourceWs, ExcelWorksheet destWs) { sourceWs._package.Workbook.ReadAllPivotTables(); string prevName = ""; @@ -1274,6 +1274,8 @@ private static void CopyPivotTable(ExcelWorksheet sourceWs, ExcelWorksheet destW } //Can't have a cell selected when "group editing" avoids pop-up by not selecting sheet. destWs.View.SetTabSelected(false); + + return nameMap; } private static void CreateCacheInNewPackage(ExcelWorksheet sourceWs, ExcelPivotTable tbl, ZipPackagePart partTbl) @@ -1344,32 +1346,32 @@ private static void ChangeToWsLocalPivotTable(ExcelWorksheet sourceWs, Dictionar } } } - private static void CopyDxfStyles(ExcelWorksheet sourceWs, ExcelWorksheet destWs) + private static void CopyDxfStyles(ExcelWorksheet sourceWs, ExcelWorksheet destWs, Dictionary copiedTableNames, Dictionary copiedPivotTableNames) { //DxfStyleHandler.UpdateDxfXml(copy.Workbook); var dxfStyleCashe = new Dictionary(); - CopyDxfStylesTables(sourceWs, destWs); - CopyDxfStylesPivotTables(sourceWs, destWs, dxfStyleCashe); + CopyDxfStylesTables(sourceWs, destWs, copiedTableNames); + CopyDxfStylesPivotTables(sourceWs, destWs, dxfStyleCashe, copiedPivotTableNames); CopyDxfStylesConditionalFormatting(sourceWs, destWs, dxfStyleCashe); } - private static void CopyDxfStylesTables(ExcelWorksheet sourceWs, ExcelWorksheet destWs) + private static void CopyDxfStylesTables(ExcelWorksheet sourceWs, ExcelWorksheet destWs, Dictionary copiedTableNames) { //Table formats for (int i = 0; i < sourceWs.Tables.Count; i++) { var tblFrom = sourceWs.Tables[i]; - var tblTo = destWs.Tables[i]; //Use Name, as id can differ if the worksheets are in different workbooks. + var tblTo = destWs.Tables[copiedTableNames[tblFrom.Name]]; //Use Name, as id can differ if the worksheets are in different workbooks. DxfStyleHandler.CopyDxfStylesTable(tblFrom, tblTo); } } - private static void CopyDxfStylesPivotTables(ExcelWorksheet sourceWs, ExcelWorksheet destWs, Dictionary dxfStyleCache) + private static void CopyDxfStylesPivotTables(ExcelWorksheet sourceWs, ExcelWorksheet destWs, Dictionary dxfStyleCache, Dictionary copiedPivotTableNames) { //Table formats foreach (var pt in sourceWs.PivotTables) { var ix = 0; - var newPt = destWs.PivotTables[pt.Name]; + var newPt = destWs.PivotTables[copiedPivotTableNames[pt.Name]]; foreach (var a in pt.Styles._list) { var addedStyle = newPt.Styles[ix++]; diff --git a/src/EPPlus/FormulaParsing/CalculateExtensions.cs b/src/EPPlus/FormulaParsing/CalculateExtensions.cs index 6db4e071ac..95e9c527ce 100644 --- a/src/EPPlus/FormulaParsing/CalculateExtensions.cs +++ b/src/EPPlus/FormulaParsing/CalculateExtensions.cs @@ -82,7 +82,7 @@ public static void Calculate(this ExcelWorkbook workbook, ExcelCalculationOption try { #endif - var dc =RpnFormulaExecution.Execute(workbook, options); + var dc = RpnFormulaExecution.Execute(workbook, options); dc._parsingContext.RangeCriteriaCache?.Clear(); if (workbook.FormulaParser.Logger != null) { diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs index f40dcd62ec..23319d108a 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs @@ -1259,9 +1259,11 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai f._tokenIndex++; continue; } - } - return e.GetAddress(); + if (t.TokenType == TokenType.CellAddress || t.TokenType == TokenType.ExcelAddress) //Full column and full row addresses will be returned when processing the : operator. + { + return e.GetAddress(); + } } break; case TokenType.NameValue: @@ -1281,7 +1283,10 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai { if (IsSingleAddress(f)) { - return nameAddress; + foreach(var a in nameAddress) + { + return GetCriteriaRange(depChain._parsingContext, f, a); + } } } } @@ -1388,7 +1393,7 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai { if ((f._funcStack.Count == 0 || ShouldIgnoreAddress(f._funcStack.Peek()) == false) && r.Address != null) { - return [r.Address.Clone()]; + return GetCriteriaRange(depChain._parsingContext, f, r.Address.Clone()); } } } @@ -1419,7 +1424,7 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai var cr = s.Peek().Compile(); if (cr.Address != null) { - return [cr.Address]; + return GetCriteriaRange(depChain._parsingContext, f, cr.Address); } } @@ -1488,6 +1493,42 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai return null; } + private static FormulaRangeAddress[] GetCriteriaRange(ParsingContext ctx,RpnFormula f, FormulaRangeAddress address) + { + + if (address.ExternalReferenceIx <=0 && f._funcStack.Count > 0) + { + var lfe = f._funcStack.Peek(); + var pi = lfe._function.ParametersInfo.GetParameterInfo(lfe._argPos); + if (pi == FunctionParameterInformation.AdjustCriteriaParameterAddress) + { + var q = new Queue(); + lfe._function.GetNewParameterAddress(CreateArgumentsForParameterAddress(f, lfe),lfe._argPos, ctx, ref q); + return q.ToArray(); + } + } + return [address]; + } + + private static IList CreateArgumentsForParameterAddress(RpnFormula f, FunctionExpression fe) + { + var ix = 0; + var l = new List(); + foreach(var e in f.ExpressionStack.Reverse()) + { + if (fe._function.ParametersInfo.GetParameterInfo(ix)!=FunctionParameterInformation.AdjustParameterAddress) + { + l.Add(e.Compile()); + } + else + { + l.Add(null); + } + ix++; + } + return l; + } + private static ExpressionCondition GetCondition(CompileResult v) { if (v.ResultValue is IRangeInfo ri) diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/FunctionParameterInformation.cs b/src/EPPlus/FormulaParsing/Excel/Functions/FunctionParameterInformation.cs index 9dbfe60141..dfc9ed8cab 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/FunctionParameterInformation.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/FunctionParameterInformation.cs @@ -54,5 +54,9 @@ public enum FunctionParameterInformation /// The parameter is a variable which value is calculated by the next parameter. /// IsParameterVariable = 0x80, + /// + /// A hierarcal criteria + /// + AdjustCriteriaParameterAddress = 0x100 } } diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/AverageIfs.cs b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/AverageIfs.cs index 50c63d6aec..4a68d7a2e6 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/AverageIfs.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/AverageIfs.cs @@ -48,7 +48,7 @@ internal class AverageIfs : RangeCriteriaFunction { return FunctionParameterInformation.Normal; } - return FunctionParameterInformation.AdjustParameterAddress; + return FunctionParameterInformation.AdjustCriteriaParameterAddress; })); public override void GetNewParameterAddress(IList args, int index, ParsingContext ctx, ref Queue addresses) diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/CountIfs.cs b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/CountIfs.cs index 664cff771b..ae775fab68 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/CountIfs.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/CountIfs.cs @@ -36,7 +36,7 @@ internal class CountIfs : RangeCriteriaFunction } if (argumentIndex % 2 == 0) { - return FunctionParameterInformation.AdjustParameterAddress; + return FunctionParameterInformation.AdjustCriteriaParameterAddress; } return FunctionParameterInformation.IgnoreErrorInPreExecute; })); diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/RangeCriteriaFunction.cs b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/RangeCriteriaFunction.cs index 69014fcdf0..a61af1043b 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/RangeCriteriaFunction.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/RangeCriteriaFunction.cs @@ -272,7 +272,7 @@ protected static Queue EnqueueMatchingAddresses(IRangeInfo protected IEnumerable GetMatchingIndicesFromArguments(int argStartIx, IList args, ParsingContext ctx, int maxIndex = 31, bool convertNumericStrings = true) { //Return the addresses matching the criteria in the queue - var argRanges = new List(); + var criteriaRanges = new List(); var criteria = new List(); for (var ix = argStartIx; ix < maxIndex; ix += 2) { @@ -280,27 +280,27 @@ protected IEnumerable GetMatchingIndicesFromArguments(int argStartIx, IList var arg = args[ix]; if (arg.Result is IRangeInfo rangeInfo) { - argRanges.Add(new RangeOrValue { Range = rangeInfo }); + criteriaRanges.Add(new RangeOrValue { Range = rangeInfo }); } else { - argRanges.Add(new RangeOrValue { Value = arg.ResultValue }); + criteriaRanges.Add(new RangeOrValue { Value = arg.ResultValue }); } if (args[ix + 1].Result is IRangeInfo critInfo) { - criteria.Add(new RangeOrValue { Range = critInfo }); + criteria.Add(critInfo.GetValue(0, 0)); } else { - criteria.Add(new RangeOrValue { Value = args[ix + 1].ResultValue }); + criteria.Add(args[ix + 1].ResultValue); } } - IEnumerable matchIndexes = GetMatchIndexes(argRanges[0], criteria[0], ctx, convertNumericStrings); + IEnumerable matchIndexes = GetMatchIndexes(criteriaRanges[0], criteria[0], ctx, convertNumericStrings); var enumerable = matchIndexes as IList ?? matchIndexes.ToList(); - for (var ix = 1; ix < argRanges.Count && enumerable.Any(); ix++) + for (var ix = 1; ix < criteriaRanges.Count && enumerable.Any(); ix++) { - var indexes = GetMatchIndexes(argRanges[ix], criteria[ix], ctx, convertNumericStrings); + var indexes = GetMatchIndexes(criteriaRanges[ix], criteria[ix], ctx, convertNumericStrings); matchIndexes = matchIndexes.Intersect(indexes); } diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/SumIfs.cs b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/SumIfs.cs index 75356df006..38e481e142 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/SumIfs.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/MathFunctions/SumIfs.cs @@ -54,7 +54,7 @@ public override void ConfigureArrayBehaviour(ArrayBehaviourConfig config) { return FunctionParameterInformation.Normal; } - return FunctionParameterInformation.AdjustParameterAddress; + return FunctionParameterInformation.AdjustCriteriaParameterAddress; })); public override void GetNewParameterAddress(IList args, int index, ParsingContext ctx, ref Queue addresses) diff --git a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs index 00d0a968a6..5706632311 100644 --- a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs +++ b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs @@ -1749,7 +1749,17 @@ public void s1060() Assert.AreEqual(4520.75, result); } } - + [TestMethod] + public void s1065() + { + using (var p = OpenTemplatePackage("s1065.xlsx")) + { + p.Workbook.Calculate(); + var ws = p.Workbook.Worksheets[1]; + var result = (double)ws.Cells["D69"].Value; + Assert.AreEqual(-310522.61, result, 0.01); + } + } } } From f7b67bd0d00cba1588da6b57de8d90b78896a734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 12 Aug 2026 16:06:07 +0200 Subject: [PATCH 2/5] Fix for issue #2463 --- src/EPPlus/Core/CellStore/RangeHashset.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/EPPlus/Core/CellStore/RangeHashset.cs b/src/EPPlus/Core/CellStore/RangeHashset.cs index e3df3b93f2..d919f4d88b 100644 --- a/src/EPPlus/Core/CellStore/RangeHashset.cs +++ b/src/EPPlus/Core/CellStore/RangeHashset.cs @@ -136,6 +136,10 @@ internal bool Merge(ref FormulaRangeAddress newAddress) { var spillRanges = new List(); byte isAdded = 0; + if(newAddress.FromCol < 1 || newAddress.FromRow < 1) + { + return false; + } for (int c = newAddress.FromCol; c <= newAddress.ToCol; c++) { var rowSpan = (((long)newAddress.FromRow - 1) << 20) | ((long)newAddress.ToRow - 1); From 690238a48e116cab91af889110fdb6a17e2db692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 12 Aug 2026 16:11:26 +0200 Subject: [PATCH 3/5] Fix for issue #2463 --- .../Core/Worksheet/WorksheetCopyHelper.cs | 1 - .../DependencyChain/RpnFormulaExecution.cs | 26 ++++++++++++++++--- .../Issues/FormulaCalculationIssues.cs | 16 ++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs index b8e56b9981..7e2c8c54de 100644 --- a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs +++ b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs @@ -116,7 +116,6 @@ internal static ExcelWorksheet Copy(ExcelWorksheets targetWorksheets, string nam { copiedTableNames = CopyTable(sourceWorksheet, targetWorksheet); } - Dictionary copiedPivotTableNames = null; if (sourceWorksheet.PivotTables.Count > 0) { copiedPivotTableNames = CopyPivotTable(sourceWorksheet, targetWorksheet); diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs index 23319d108a..bbc025113a 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs @@ -449,6 +449,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d rd?.Merge(f._row, f._column); depChain.StartOfChain(); } + ExecuteFormula: try { @@ -497,15 +498,16 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d addresses = f._expressions[f._tokenIndex].GetAddress(); } depChain.AddFormulaToChain(f, addresses); - if (GetAddressesToFollow(depChain, f, options, ref addresses, ref rd, ref ws)) { goto FollowChain; } + f._tokenIndex++; goto ExecuteFormula; } } + CompileResult cr; if (f._tokenIndex == int.MaxValue) //int.MaxValue means we have an invalid formulas and we should return a name error { @@ -518,8 +520,13 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d if (cr != null && f.IsLambda == false && (writeToCell || depChain._formulaStack.Count > 0)) // If calculating single cell via the FormulaParser.Parse method we should not write to the cells { + if (f._ws != null) + { + rd = AddOrGetRDFromWsIx(depChain, f._ws.IndexInList); + } SetValueToWorkbook(depChain, f, rd, cr, options, ref depChainPos); - + + //We are in a dirty cell recalculation and have a new position in the chain. //We should return to the caller and let it continue from the new position in the chain. //We use this technique to avoid stack overflow exceptions when recalculating dirty cells with long dependency chains. @@ -543,6 +550,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d f._tokenIndex++; goto ExecuteFormula; } + rd = AddOrGetRDFromWsIx(depChain, f._enumeratorWorksheetIx); goto NextFormula; } @@ -565,7 +573,9 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d { if (depChain.processedCells.Contains(ExcelCellBase.GetCellId(ws?.IndexInList ?? ushort.MaxValue, firstAddress.FromRow, firstAddress.FromCol)) == false) { + rd?.Merge(firstAddress.FromRow, firstAddress.FromCol); + if (ws._formulas.Exists(firstAddress.FromRow, firstAddress.FromCol, ref v) && v != null) { depChain._formulaStack.Push(f); @@ -574,6 +584,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d } } f._tokenIndex++; + goto ExecuteFormula; } else @@ -605,7 +616,6 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d goto NextFormula; } } - MergeToRd(rd, row, col, rPos, fe, true); f._formulaEnumerator = null; @@ -655,6 +665,12 @@ private static bool GetAddressesToFollow(RpnOptimizedDependencyChain depChain, R var needsClean = false; for (int i = 0; i < addresses.Length; i++) { + if (addresses[i].FromRow<1 || addresses[i].FromCol<1) + { + addresses[i] = null; + needsClean = true; + continue; + } var address = addresses[i].Clone(); if (address.ExternalReferenceIx > 0) //We don't follow dep chain into external references. { @@ -774,6 +790,7 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn { //Add dynamic array formula support here. var dirtyRange = ArrayFormulaOutput.FillDynamicArrayFromRangeInfo(f, ri, rd, depChain); + if (dirtyRange != null && dirtyRange.Length > 0) { RecalculateDirtyCells(dirtyRange, depChain, rd, options); @@ -791,11 +808,14 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn (f._flags & FormulaFlags.IsAlwaysDynamic) == FormulaFlags.IsAlwaysDynamic) && f.CanBeDynamicArray) { + var dirtyRange = ArrayFormulaOutput.FillDynamicArraySingleValue(f, cr, rd, depChain); + if (dirtyRange != null && dirtyRange.Length > 0) { RecalculateDirtyCells(dirtyRange, depChain, rd, options); } + depChain.HasAnyArrayFormula = true; } else if (cr.ResultType == CompileResultType.LocalImage) diff --git a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs index 5706632311..886acc257e 100644 --- a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs +++ b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs @@ -1760,6 +1760,22 @@ public void s1065() Assert.AreEqual(-310522.61, result, 0.01); } } + [TestMethod] + public void s1066() + { + using (var p = OpenTemplatePackage("s1066.xlsx")) + { + p.Workbook.Calculate(); + //var ws = p.Workbook.Worksheets["Balance"]; + //var ws = p.Workbook.Worksheets["Technical"]; + //ws.Cells["A14"].Calculate(); + //var result = ws.Cells["A14"].Value; + //Assert.AreEqual("CHT0", result); + var ws = p.Workbook.Worksheets["Tax All"]; + var result = ws.Cells["G15"].Value; + Assert.AreEqual("CH-0% output tax foreign/foreign", result); + } + } } } From 55da432b9b931e40f7196cfc2289b9be0134d927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Wed, 12 Aug 2026 16:14:35 +0200 Subject: [PATCH 4/5] Cleaned up test --- src/EPPlusTest/Issues/FormulaCalculationIssues.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs index 886acc257e..979246d25a 100644 --- a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs +++ b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs @@ -1766,11 +1766,6 @@ public void s1066() using (var p = OpenTemplatePackage("s1066.xlsx")) { p.Workbook.Calculate(); - //var ws = p.Workbook.Worksheets["Balance"]; - //var ws = p.Workbook.Worksheets["Technical"]; - //ws.Cells["A14"].Calculate(); - //var result = ws.Cells["A14"].Value; - //Assert.AreEqual("CHT0", result); var ws = p.Workbook.Worksheets["Tax All"]; var result = ws.Cells["G15"].Value; Assert.AreEqual("CH-0% output tax foreign/foreign", result); From 0676998860ceac5b4b01dba91fc20283e914a21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= Date: Mon, 17 Aug 2026 09:15:52 +0200 Subject: [PATCH 5/5] Moved Range Dictionary lookup into SaveWorkbook in RpnFormulaExecute --- .../DependencyChain/RpnFormulaExecution.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs index bbc025113a..f91b4574bf 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs @@ -520,12 +520,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d if (cr != null && f.IsLambda == false && (writeToCell || depChain._formulaStack.Count > 0)) // If calculating single cell via the FormulaParser.Parse method we should not write to the cells { - if (f._ws != null) - { - rd = AddOrGetRDFromWsIx(depChain, f._ws.IndexInList); - } - SetValueToWorkbook(depChain, f, rd, cr, options, ref depChainPos); - + SetValueToWorkbook(depChain, f, cr, options, ref depChainPos); //We are in a dirty cell recalculation and have a new position in the chain. //We should return to the caller and let it continue from the new position in the chain. @@ -755,7 +750,7 @@ private static void CheckAndClearRichData(RpnFormula f) } f._ws._metadataStore.Clear(f._row, f._column, 1, 1); } - private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, RpnFormula f, RangeHashset rd, CompileResult cr, ExcelCalculationOption options, ref int insertDepChainPos) + private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, RpnFormula f/*, RangeHashset rd*/, CompileResult cr, ExcelCalculationOption options, ref int insertDepChainPos) { if(cr.DataType == DataType.LambdaCalculation) { @@ -776,6 +771,7 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn } else { + var rd = AddOrGetRDFromWsIx(depChain, f._ws.IndexInList); if ((cr.DataType == DataType.ExcelRange && ((IRangeInfo)cr.Result).Address.IsSingleCell == false)) //A range. When we add support for dynamic array formulas we will alter this. { var ri = (IRangeInfo)cr.Result;