Skip to content

Commit 9a4a730

Browse files
authored
Bug/issue2234 (#2246)
* WIP:Fix for issue #2234 * Fix for issue 2234
1 parent aa0ad4a commit 9a4a730

9 files changed

Lines changed: 162 additions & 62 deletions

File tree

src/EPPlus/ExcelWorksheet.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ internal struct MetaDataReference
8686
{
8787
internal uint cm;
8888
internal uint vm;
89-
internal bool aca;
90-
internal bool ca;
89+
//internal bool aca; //Removed, is set as a flag instead in the _flags store.
90+
//internal bool ca; //Removed, is set as a flag instead in the _flags store.
9191
}
9292
/// <summary>
9393
/// Removes all formulas within the entire worksheet, but keeps the calculated values.

src/EPPlus/FormulaParsing/CalculateExtensions.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,34 @@ public static void Calculate(this ExcelWorkbook workbook, ExcelCalculationOption
8181
workbook.FormulaParser.Logger.Log(msg);
8282
}
8383
}
84+
internal static RpnOptimizedDependencyChain CalculateWithDC(this ExcelWorkbook workbook, Action<ExcelCalculationOption> configHandler)
85+
{
86+
var option = new ExcelCalculationOption();
87+
configHandler.Invoke(option);
88+
return CalculateWithDC(workbook, option);
89+
}
90+
internal static RpnOptimizedDependencyChain CalculateWithDC(this ExcelWorkbook workbook, ExcelCalculationOption options)
91+
{
92+
Init(workbook);
93+
94+
var filterInfo = new FilterInfo(workbook);
95+
workbook.FormulaParser.InitNewCalc(filterInfo);
96+
97+
if (workbook.FormulaParser.Logger != null)
98+
{
99+
var msg = string.Format("Starting formula calculation.");
100+
workbook.FormulaParser.Logger.Log(msg);
101+
}
102+
103+
//CalcChain(workbook, workbook.FormulaParser, dc, options);
104+
var dc = RpnFormulaExecution.Execute(workbook, options);
105+
if (workbook.FormulaParser.Logger != null)
106+
{
107+
var msg = string.Format("Calculation done...number of cells parsed: {0}", dc.processedCells.Count);
108+
workbook.FormulaParser.Logger.Log(msg);
109+
}
110+
return dc;
111+
}
84112
/// <summary>
85113
/// Calculate all formulas in the current worksheet
86114
/// </summary>

src/EPPlus/FormulaParsing/DependencyChain/ArrayFormulaOutput.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ internal static SimpleAddress[] FillDynamicArrayFromRangeInfo(RpnFormula f, IRan
151151
CleanupSharedFormulaValues(f, ws, sf, endRow, endCol);
152152
sf.EndRow = endRow;
153153
sf.EndCol = endCol;
154+
154155
}
155156
FillArrayFromRangeInfo(f, array, rd, depChain);
156157
return dirtyRange;
@@ -239,8 +240,10 @@ private static SimpleAddress[] GetDirtyRange(int fromRow, int fromCol, int toRow
239240

240241
private static void ClearDynamicFormulaIndex(ExcelWorksheet ws, int fromRow, int fromCol, int toRow, int toCol)
241242
{
242-
ws._formulas.Clear(fromRow, fromCol, toRow, toCol);
243-
ws._flags.Clear(fromRow, fromCol, toRow, toCol);
243+
var rows = toRow - fromRow + 1;
244+
var cols = toCol - fromCol + 1;
245+
ws._formulas.Clear(fromRow, fromCol, rows, cols);
246+
ws._flags.Clear(fromRow, fromCol, rows, cols);
244247

245248
for (int col = fromCol; col <= toCol; col++)
246249
{

src/EPPlus/FormulaParsing/DependencyChain/RpnFormula.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,28 @@ public override string ToString()
261261
}
262262
}
263263

264-
internal void ClearCache()
264+
internal void ClearCache(RpnOptimizedDependencyChain depChain)
265265
{
266266
foreach (var e in _expressions.Values)
267267
{
268268
if (e.ExpressionType == ExpressionType.CellAddress)
269+
{
269270
e._cachedCompileResult = null;
271+
}
272+
if(e.ExpressionType == ExpressionType.Function)
273+
{
274+
var funcExp= e as FunctionExpression;
275+
funcExp.Status = ExpressionStatus.NoSet;
276+
var key = funcExp.GetExpressionKey(this);
277+
if (key != null)
278+
{
279+
var cache = depChain.GetCache(_ws);
280+
if (cache.ContainsKey(key))
281+
{
282+
cache.Remove(key);
283+
}
284+
}
285+
}
270286
}
271287
}
272288

src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ internal static object ExecutePivotFieldFormula(RpnOptimizedDependencyChain depC
111111
{
112112
var formula = new RpnFormula(null, 0, 0);
113113
formula.SetFormula(tokens, depChain);
114-
return AddChainForFormula(depChain, formula, options, false).Result;
114+
return CalculateFormulaChain(depChain, formula, options, false).Result;
115115
}
116116

117117
private static void ExecuteChain(RpnOptimizedDependencyChain depChain, ExcelRangeBase range, ExcelCalculationOption options, bool writeToCell)
@@ -129,7 +129,7 @@ private static void ExecuteChain(RpnOptimizedDependencyChain depChain, ExcelRang
129129
{
130130
if (GetFormula(depChain, ws, fs.Row, fs.Column, fs.Value, ref f))
131131
{
132-
AddChainForFormula(depChain, f, options, writeToCell);
132+
CalculateFormulaChain(depChain, f, options, writeToCell);
133133
}
134134
}
135135
catch (CircularReferenceException)
@@ -263,7 +263,7 @@ private static void ExecuteName(RpnOptimizedDependencyChain depChain, ExcelNamed
263263
if (string.IsNullOrEmpty(name.NameFormula) == false)
264264
{
265265
var f = GetNameFormula(depChain, ws, depChain._parsingContext.ExcelDataProvider.GetName(name), 1, 1);
266-
AddChainForFormula(depChain, f, options, writeToCell);
266+
CalculateFormulaChain(depChain, f, options, writeToCell);
267267
}
268268
else if (ws != null && name.IsValidRowCol())
269269
{
@@ -287,7 +287,7 @@ private static object ExecuteChain(RpnOptimizedDependencyChain depChain, ExcelWo
287287
var f = new RpnFormula(ws, cell.Row, cell.Column);
288288
depChain._parsingContext.CurrentCell = new FormulaCellAddress(ws?.Index??-1, -1, 0);
289289
f.SetFormula(formula, depChain);
290-
return AddChainForFormula(depChain, f, options, writeToCell).Result;
290+
return CalculateFormulaChain(depChain, f, options, writeToCell).Result;
291291
}
292292
catch (CircularReferenceException)
293293
{
@@ -307,7 +307,7 @@ private static object ExecuteChain(RpnOptimizedDependencyChain depChain, ExcelWo
307307
var f = new RpnFormula(ws, 0, 0);
308308
f.SetFormula(formula, depChain);
309309
f._row = -1;
310-
return AddChainForFormula(depChain, f, options, writeToCell).Result;
310+
return CalculateFormulaChain(depChain, f, options, writeToCell).Result;
311311
}
312312
catch (CircularReferenceException)
313313
{
@@ -401,32 +401,29 @@ private static RpnFormula GetNameFormula(RpnOptimizedDependencyChain depChain, E
401401

402402
internal static CompileResult ExecutePartialFormula(RpnOptimizedDependencyChain depChain, RpnFormula f, ExcelCalculationOption options, bool writeToCell)
403403
{
404-
return AddChainForFormula(depChain, f, options, writeToCell);
404+
return CalculateFormulaChain(depChain, f, options, writeToCell);
405405
}
406406

407-
internal static CompileResult ExecutePartialFormula(RpnOptimizedDependencyChain depChain, RpnFormula f, ExcelCalculationOption options, bool writeToCell, VariableStorageScope scope)
408-
{
409-
return AddChainForFormula(depChain, f, options, writeToCell, scope);
410-
}
411-
412-
private static CompileResult AddChainForFormula(RpnOptimizedDependencyChain depChain, RpnFormula f, ExcelCalculationOption options, bool writeToCell, VariableStorageScope scope = null)
407+
private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain depChain, RpnFormula f, ExcelCalculationOption options, bool writeToCell, int depChainPos=-1)
413408
{
414409
FormulaRangeAddress[] addresses;
415-
//FormulaRangeAddress address = null;
416410
RangeHashset rd = AddOrGetRDFromWsIx(depChain, f._ws == null ? -1 : f._ws.IndexInList);
417411
object v = null;
418412
bool hasLogger = depChain._parsingContext.Parser.Logger != null;
419-
rd?.Merge(f._row, f._column);
420-
var followChain = options.FollowDependencyChain;
421-
depChain.StartOfChain();
413+
var followChain = options.FollowDependencyChain && depChainPos==-1;
414+
if (depChainPos == -1)
415+
{
416+
rd?.Merge(f._row, f._column);
417+
depChain.StartOfChain();
418+
}
422419
ExecuteFormula:
423420
try
424421
{
425422
SetCurrentCell(depChain, f);
426423
var ws = f._ws;
427424
if (f._tokenIndex < f._tokens.Count)
428425
{
429-
addresses = ExecuteNextToken(depChain, f, followChain);
426+
addresses = ExecuteNextToken(depChain, f, followChain || depChain._formulaStack.Count>0);
430427
if (f._tokenIndex < f._tokens.Count)
431428
{
432429
if (addresses == null && f._expressions.ContainsKey(f._tokenIndex) && f._expressions[f._tokenIndex].ExpressionType == ExpressionType.NameValue)
@@ -485,7 +482,7 @@ private static CompileResult AddChainForFormula(RpnOptimizedDependencyChain depC
485482

486483
if (cr != null && (writeToCell || depChain._formulaStack.Count > 0)) // If calculating single cell via the FormulaParser.Parse method we should not write to the cells
487484
{
488-
SetValueToWorkbook(depChain, f, rd, cr);
485+
SetValueToWorkbook(depChain, f, rd, cr, options, ref depChainPos);
489486
}
490487

491488
if (hasLogger)
@@ -690,7 +687,7 @@ private static void CheckAndClearRichData(RpnFormula f)
690687
}
691688
f._ws._metadataStore.Clear(f._row, f._column, 1, 1);
692689
}
693-
private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, RpnFormula f, RangeHashset rd, CompileResult cr)
690+
private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, RpnFormula f, RangeHashset rd, CompileResult cr, ExcelCalculationOption options, ref int insertDepChainPos)
694691
{
695692
if(cr.DataType == DataType.LambdaCalculation)
696693
{
@@ -727,8 +724,7 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn
727724
var dirtyRange = ArrayFormulaOutput.FillDynamicArrayFromRangeInfo(f, ri, rd, depChain);
728725
if (dirtyRange != null && dirtyRange.Length > 0)
729726
{
730-
731-
RecalculateDirtyCells(dirtyRange, depChain, rd);
727+
RecalculateDirtyCells(dirtyRange, depChain, rd, options);
732728
}
733729
}
734730
else //Set implicit intersection
@@ -746,7 +742,7 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn
746742
var dirtyRange = ArrayFormulaOutput.FillDynamicArraySingleValue(f, cr, rd, depChain);
747743
if (dirtyRange != null && dirtyRange.Length > 0)
748744
{
749-
RecalculateDirtyCells(dirtyRange, depChain, rd);
745+
RecalculateDirtyCells(dirtyRange, depChain, rd, options);
750746
}
751747
depChain.HasAnyArrayFormula = true;
752748
}
@@ -769,8 +765,21 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn
769765
{
770766
if (f._arrayIndex != -1)
771767
{
772-
var sf = f._ws._sharedFormulas[f._arrayIndex];
773-
f._ws.SetValueInner(sf.StartRow, sf.StartCol, sf.EndRow, sf.EndCol, cr.ResultValue ?? 0D);
768+
if((f._flags & FormulaFlags.IsDynamic)== FormulaFlags.IsDynamic)
769+
{
770+
var dirtyRange = ArrayFormulaOutput.FillDynamicArraySingleValue(f, cr, rd, depChain);
771+
if (dirtyRange != null && dirtyRange.Length > 0)
772+
{
773+
RecalculateDirtyCells(dirtyRange, depChain, rd, options);
774+
}
775+
depChain.HasAnyArrayFormula = true;
776+
777+
}
778+
else
779+
{
780+
var sf = f._ws._sharedFormulas[f._arrayIndex];
781+
f._ws.SetValueInner(sf.StartRow, sf.StartCol, sf.EndRow, sf.EndCol, cr.ResultValue ?? 0D);
782+
}
774783
}
775784
else
776785
{
@@ -785,74 +794,82 @@ private static void SetValueToWorkbook(RpnOptimizedDependencyChain depChain, Rpn
785794
}
786795
}
787796
}
788-
depChain.DependencyChain.Add(f.CellId);
797+
798+
if (insertDepChainPos<0)
799+
{
800+
depChain.DependencyChain.Add(f.CellId);
801+
}
802+
else if(depChain._formulaStack.Count > 0)
803+
{
804+
depChain.DependencyChain.Insert(++insertDepChainPos, f.CellId);
805+
}
789806
}
790807
}
791-
792-
private static void RecalculateDirtyCells(SimpleAddress[] dirtyRange, RpnOptimizedDependencyChain depChain, RangeHashset rd)
808+
private static void RecalculateDirtyCells(SimpleAddress[] dirtyRange, RpnOptimizedDependencyChain depChain, RangeHashset rd, ExcelCalculationOption options)
793809
{
810+
if(depChain._isInRecalculateDirtyCells) return; //EPPlus will not recalculate dirty cells while recalculating other dirty cells to avoid stack overflow.
794811
var dirtyCells = dirtyRange.ToList();
795812
if (depChain.FormulaRangeReferences.ContainsKey(depChain._parsingContext.CurrentWorksheet.IndexInList))
796813
{
797814
var qt = depChain.FormulaRangeReferences[depChain._parsingContext.CurrentWorksheet.IndexInList];
798815
int fromIx = int.MaxValue;
799-
int toIx = int.MinValue;
816+
//int toIx = int.MinValue;
800817
foreach (var a in dirtyRange)
801818
{
802819
var ir = qt.GetIntersectingRangeItems(new QuadRange(a.FromRow, a.FromCol, a.ToRow, a.ToCol));
803820
if (ir.Count > 0)
804821
{
805822
foreach (var r in ir.Select(x => x.Value).Distinct())
806823
{
824+
ExcelAddressBase.SplitCellId(r, out int sheet, out int row, out int col);
825+
807826
var ix = depChain.DependencyChain.IndexOf(r);
808827
if (ix < 0) continue;
809828
if (ix < fromIx)
810829
{
811830
fromIx = ix;
812831
}
813-
var ixEnd = depChain._startOfChain.BinarySearch(ix + 1);
814832

815-
if (ixEnd < 0)
833+
if(fromIx==0)
816834
{
817-
ixEnd = ~ixEnd;
818-
}
819-
820-
if (ixEnd - 1 > toIx)
821-
{
822-
toIx = ixEnd - 1;
835+
break;
823836
}
824837
}
825838
}
839+
if (fromIx == 0)
840+
{
841+
break;
842+
}
826843
}
827844

828845
if (fromIx != int.MaxValue)
829846
{
830-
831-
for (int i = fromIx; i <= toIx; i++)
847+
depChain._isInRecalculateDirtyCells = true;
848+
var dcCount = depChain.DependencyChain.Count;
849+
var cc = depChain._parsingContext.CurrentCell;
850+
for (int i = fromIx; i < dcCount; i++)
832851
{
833852
ExcelCellBase.SplitCellId(depChain.DependencyChain[i], out int sheetId, out int row, out int col);
834-
835853
RpnFormula f = null;
836854
var ws = depChain._parsingContext.Package.Workbook.GetWorksheetByIndexInList(sheetId);
837855
var v = ws._formulas.GetValue(row, col);
838856

839857
if (GetFormula(depChain, ws, row, col, v, ref f))
840858
{
841-
ReCalculateFormula(f, depChain, rd);
859+
f.ClearCache(depChain);
860+
CalculateFormulaChain(depChain, f, options, true, i);
861+
if (depChain.DependencyChain.Count > dcCount)
862+
{
863+
i += depChain.DependencyChain.Count - dcCount;
864+
dcCount = depChain.DependencyChain.Count;
865+
}
842866
}
843867
}
868+
depChain._isInRecalculateDirtyCells = false;
844869
}
845870

846871
}
847872
}
848-
private static void ReCalculateFormula(RpnFormula f, RpnOptimizedDependencyChain depChain, RangeHashset rd)
849-
{
850-
f._tokenIndex = 0;
851-
f.ClearCache();
852-
ExecuteNextToken(depChain, f, false);
853-
var e = f._expressionStack.Pop();
854-
SetValueToWorkbook(depChain, f, rd, e.Compile());
855-
}
856873
private static void MergeToRd(RangeHashset rd, int fromRow, int fromCol, int rangePos, CellStoreEnumerator<object> fe, bool atEnd)
857874
{
858875

@@ -1161,7 +1178,7 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai
11611178
var nameAddress = ne.GetAddress();
11621179
if (nameAddress == null)
11631180
{
1164-
if (string.IsNullOrEmpty(ne._name?.Formula) == false)
1181+
if (returnAddresses && string.IsNullOrEmpty(ne._name?.Formula) == false)
11651182
{
11661183
return null;
11671184
}
@@ -1273,7 +1290,7 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai
12731290
f.LambdaSettings.LambdaStackNumbers.Push(s.Count);
12741291
f.LambdaSettings.NumberOfLambdaVariables.Push(clc.NumberOfVariables);
12751292
}
1276-
if (r.Address != null && returnAddresses)
1293+
if (r.Address != null)
12771294
{
12781295
if ((f._funcStack.Count == 0 || ShouldIgnoreAddress(f._funcStack.Peek()) == false) && r.Address != null)
12791296
{
@@ -1303,7 +1320,7 @@ private static FormulaRangeAddress[] ExecuteNextToken(RpnOptimizedDependencyChai
13031320
case TokenType.Operator:
13041321
ApplyOperator(depChain._parsingContext, t, f);
13051322

1306-
if (s.Count > 0 && s.Peek().Status == ExpressionStatus.IsAddress && (f._funcStack.Count == 0 || ShouldIgnoreAddress(f._funcStack.Peek()) == false))
1323+
if (returnAddresses && s.Count > 0 && s.Peek().Status == ExpressionStatus.IsAddress && (f._funcStack.Count == 0 || ShouldIgnoreAddress(f._funcStack.Peek()) == false))
13071324
{
13081325
var cr = s.Peek().Compile();
13091326
if (cr.Address != null)

0 commit comments

Comments
 (0)