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); diff --git a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs index 08ebc5aa80..7e2c8c54de 100644 --- a/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs +++ b/src/EPPlus/Core/Worksheet/WorksheetCopyHelper.cs @@ -111,13 +111,11 @@ 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); } - Dictionary copiedPivotTableNames = null; if (sourceWorksheet.PivotTables.Count > 0) { copiedPivotTableNames = CopyPivotTable(sourceWorksheet, targetWorksheet); @@ -143,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 @@ -193,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) { @@ -1022,6 +1020,7 @@ private static void CopyDefinedNames(ExcelWorksheet Copy, ExcelWorksheet added) wbName.IsNameHidden = name.IsNameHidden; } } + //Copy names from formulas. if (sameWorkbook == false) { @@ -1101,9 +1100,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) @@ -1137,7 +1136,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); @@ -1310,6 +1309,7 @@ private static Dictionary CopyPivotTable(ExcelWorksheet sourceWs } //Can't have a cell selected when "group editing" avoids pop-up by not selecting sheet. destWs.View.SetTabSelected(false); + return nameMap; } @@ -1381,32 +1381,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..f91b4574bf 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,8 @@ 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 { - 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. //We use this technique to avoid stack overflow exceptions when recalculating dirty cells with long dependency chains. @@ -543,6 +545,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d f._tokenIndex++; goto ExecuteFormula; } + rd = AddOrGetRDFromWsIx(depChain, f._enumeratorWorksheetIx); goto NextFormula; } @@ -565,7 +568,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 +579,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d } } f._tokenIndex++; + goto ExecuteFormula; } else @@ -605,7 +611,6 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d goto NextFormula; } } - MergeToRd(rd, row, col, rPos, fe, true); f._formulaEnumerator = null; @@ -655,6 +660,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. { @@ -739,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) { @@ -760,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; @@ -774,6 +786,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 +804,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) @@ -1259,9 +1275,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 +1299,10 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai { if (IsSingleAddress(f)) { - return nameAddress; + foreach(var a in nameAddress) + { + return GetCriteriaRange(depChain._parsingContext, f, a); + } } } } @@ -1388,7 +1409,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 +1440,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 +1509,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..979246d25a 100644 --- a/src/EPPlusTest/Issues/FormulaCalculationIssues.cs +++ b/src/EPPlusTest/Issues/FormulaCalculationIssues.cs @@ -1749,7 +1749,28 @@ 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); + } + } + [TestMethod] + public void s1066() + { + using (var p = OpenTemplatePackage("s1066.xlsx")) + { + p.Workbook.Calculate(); + var ws = p.Workbook.Worksheets["Tax All"]; + var result = ws.Cells["G15"].Value; + Assert.AreEqual("CH-0% output tax foreign/foreign", result); + } + } } }