Skip to content

Commit 1c1716a

Browse files
authored
Behaviour-fixes for COUNTA, SUBTOTAL and AGGREGATE (#1054)
1 parent d8f8ac7 commit 1c1716a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+356
-143
lines changed

src/EPPlus/FormulaParsing/Excel/Functions/ArgumentCollectionUtil.cs

+13-3
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,26 @@ public ArgumentCollectionUtil(
3636
_objectEnumerableArgConverter = objectEnumerableArgConverter;
3737
}
3838

39-
public virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHidden, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric = false)
39+
public virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHidden, bool ignoreErrors, bool ignoreSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric = false)
4040
{
41-
return _doubleEnumerableArgConverter.ConvertArgs(ignoreHidden, ignoreErrors, arguments, context, ignoreNonNumeric);
41+
return _doubleEnumerableArgConverter.ConvertArgs(ignoreHidden, ignoreErrors, ignoreSubtotalAggregate, arguments, context, ignoreNonNumeric);
4242
}
4343

4444
public virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHidden,
45+
bool ignoreErrors,
46+
bool ignoreNestedSubtotalAggregate,
4547
IEnumerable<FunctionArgument> arguments,
4648
ParsingContext context)
4749
{
48-
return _objectEnumerableArgConverter.ConvertArgs(ignoreHidden, arguments, context);
50+
return _objectEnumerableArgConverter.ConvertArgs(ignoreHidden, ignoreErrors, ignoreNestedSubtotalAggregate, arguments, context);
51+
}
52+
53+
public virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHidden,
54+
bool ignoreErrors,
55+
IEnumerable<FunctionArgument> arguments,
56+
ParsingContext context)
57+
{
58+
return _objectEnumerableArgConverter.ConvertArgs(ignoreHidden, ignoreErrors, arguments, context);
4959
}
5060
}
5161
}

src/EPPlus/FormulaParsing/Excel/Functions/CellStateHelper.cs

+18-6
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,23 @@ namespace OfficeOpenXml.FormulaParsing.Excel.Functions
1919
{
2020
internal static class CellStateHelper
2121
{
22-
private static bool IsSubTotal(ICellInfo c, ParsingContext context)
22+
private static bool ShouldIgnoreNestedSubtotal(bool ignoreNestedSubtotalAndAggregate, ulong cellId, ParsingContext context)
2323
{
24-
return context.IsSubtotal && context.SubtotalAddresses.Contains(c.Id);
24+
if (!ignoreNestedSubtotalAndAggregate) return false;
25+
return context.SubtotalAddresses.Contains(cellId);
26+
}
27+
28+
internal static bool ShouldIgnore(bool ignoreHiddenValues, bool ignoreNestedSubtotalAndAggregate, ICellInfo c, ParsingContext context)
29+
{
30+
return ShouldIgnore(ignoreHiddenValues, false, c, context, ignoreNestedSubtotalAndAggregate);
2531
}
2632

2733
internal static bool ShouldIgnore(bool ignoreHiddenValues, ICellInfo c, ParsingContext context)
2834
{
2935
return ShouldIgnore(ignoreHiddenValues, false, c, context);
3036
}
3137

32-
internal static bool ShouldIgnore(bool ignoreHiddenValues, bool ignoreNonNumeric, ICellInfo c, ParsingContext context)
38+
internal static bool ShouldIgnore(bool ignoreHiddenValues, bool ignoreNonNumeric, ICellInfo c, ParsingContext context, bool ignoreNestedSubtotalAndAggregate = true)
3339
{
3440
if(c.Address==null) return false;
3541
if (ignoreNonNumeric && !ConvertUtil.IsNumericOrDate(c.Value)) return true;
@@ -38,17 +44,23 @@ internal static bool ShouldIgnore(bool ignoreHiddenValues, bool ignoreNonNumeric
3844
{
3945
hasFilter = context.Parser.FilterInfo.WorksheetHasFilter(context.Package.Workbook.Worksheets[c.WorksheetName].IndexInList);
4046
}
41-
return ((ignoreHiddenValues || hasFilter) && c.IsHiddenRow) || IsSubTotal(c, context);
47+
return ((ignoreHiddenValues || hasFilter) && c.IsHiddenRow) || ShouldIgnoreNestedSubtotal(ignoreNestedSubtotalAndAggregate, c.Id, context);
4248
}
4349

44-
internal static bool ShouldIgnore(bool ignoreHiddenValues, FunctionArgument arg, ParsingContext context)
50+
internal static bool ShouldIgnore(bool ignoreHiddenValues, bool ignoreNestedSubtotalAndAggregate, FunctionArgument arg, ParsingContext context)
4551
{
4652
var hasFilter = false;
4753
if (context.Parser != null && context.Parser.FilterInfo != null && context.Parser.FilterInfo.WorksheetHasFilter(context.CurrentCell.WorksheetIx))
4854
{
4955
hasFilter = true;
5056
}
51-
return (ignoreHiddenValues || hasFilter) && arg.ExcelStateFlagIsSet(ExcelCellState.HiddenCell);
57+
var include = true;
58+
if(ignoreNestedSubtotalAndAggregate && arg.Address != null)
59+
{
60+
var cellId = arg.Address.GetTopLeftCellId();
61+
include = !ShouldIgnoreNestedSubtotal(ignoreNestedSubtotalAndAggregate, cellId, context);
62+
}
63+
return (ignoreHiddenValues || hasFilter) && arg.ExcelStateFlagIsSet(ExcelCellState.HiddenCell) && include;
5264
}
5365
}
5466
}

src/EPPlus/FormulaParsing/Excel/Functions/DoubleEnumerableArgConverter.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace OfficeOpenXml.FormulaParsing.Excel.Functions
2222
{
2323
public class DoubleEnumerableArgConverter : CollectionFlattener<ExcelDoubleCellValue>
2424
{
25-
public virtual IEnumerable<ExcelDoubleCellValue> ConvertArgs(bool ignoreHidden, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric = false)
25+
public virtual IEnumerable<ExcelDoubleCellValue> ConvertArgs(bool ignoreHidden, bool ignoreErrors, bool ignoreSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric = false)
2626
{
2727
return base.FuncArgsToFlatEnumerable(arguments, (arg, argList) =>
2828
{
@@ -31,7 +31,7 @@ public virtual IEnumerable<ExcelDoubleCellValue> ConvertArgs(bool ignoreHidden,
3131
foreach (var cell in arg.ValueAsRangeInfo)
3232
{
3333
if(!ignoreErrors && cell.IsExcelError) throw new ExcelErrorValueException(ExcelErrorValue.Parse(cell.Value.ToString()));
34-
if (!CellStateHelper.ShouldIgnore(ignoreHidden, ignoreNonNumeric, cell, context) && ConvertUtil.IsExcelNumeric(cell.Value))
34+
if (!CellStateHelper.ShouldIgnore(ignoreHidden, ignoreNonNumeric, cell, context, ignoreSubtotalAggregate) && ConvertUtil.IsExcelNumeric(cell.Value))
3535
{
3636
var val = new ExcelDoubleCellValue(cell.ValueDouble, cell.Row, cell.Column);
3737
argList.Add(val);
@@ -41,7 +41,7 @@ public virtual IEnumerable<ExcelDoubleCellValue> ConvertArgs(bool ignoreHidden,
4141
else
4242
{
4343
if(!ignoreErrors && arg.ValueIsExcelError) throw new ExcelErrorValueException(arg.ValueAsExcelErrorValue);
44-
if (ConvertUtil.IsExcelNumeric(arg.Value) && !CellStateHelper.ShouldIgnore(ignoreHidden, arg, context))
44+
if (ConvertUtil.IsExcelNumeric(arg.Value) && !CellStateHelper.ShouldIgnore(ignoreHidden, ignoreSubtotalAggregate, arg, context))
4545
{
4646
var val = new ExcelDoubleCellValue(ConvertUtil.GetValueDouble(arg.Value));
4747
argList.Add(val);

src/EPPlus/FormulaParsing/Excel/Functions/ExcelFunction.cs

+44-11
Original file line numberDiff line numberDiff line change
@@ -613,21 +613,51 @@ protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(IEnum
613613
/// <returns></returns>
614614
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context)
615615
{
616-
return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, arguments, context, false);
616+
return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, false, arguments, context, false);
617617
}
618618

619619
/// <summary>
620620
/// Will return the arguments as an enumerable of doubles.
621621
/// </summary>
622622
/// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
623623
/// <param name="ignoreErrors">If a cell contains an error, that error will be ignored if this method is set to true</param>
624+
/// <param name="ignoreNestedSubtotalAggregate">If cells which value comes from the calculation of a SUBTOTAL or an AGGREGATE function should be ignored, set this to true</param>
625+
/// <param name="arguments"></param>
626+
/// <param name="context"></param>
627+
/// <returns></returns>
628+
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, bool ignoreNestedSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context)
629+
{
630+
return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, ignoreNestedSubtotalAggregate, arguments, context, false);
631+
}
632+
633+
634+
/// <summary>
635+
/// Will return the arguments as an enumerable of doubles.
636+
/// </summary>
637+
/// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
638+
/// <param name="ignoreErrors">If a cell contains an error, that error will be ignored if this method is set to true</param>
639+
/// <param name="ignoreNestedSubtotalAggregate">If cells which value comes from the calculation of a SUBTOTAL or an AGGREGATE function should be ignored, set this to true</param>
624640
/// <param name="arguments"></param>
625641
/// <param name="context"></param>
626642
/// <param name="ignoreNonNumeric"></param>
627643
/// <returns></returns>
628-
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric)
644+
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreErrors, bool ignoreNestedSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric)
629645
{
630-
return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, arguments, context, ignoreNonNumeric);
646+
return _argumentCollectionUtil.ArgsToDoubleEnumerable(ignoreHiddenCells, ignoreErrors, ignoreNestedSubtotalAggregate, arguments, context, ignoreNonNumeric);
647+
}
648+
649+
/// <summary>
650+
/// Will return the arguments as an enumerable of doubles.
651+
/// </summary>
652+
/// <param name="ignoreHiddenCells">If a cell is hidden and this value is true the value of that cell will be ignored</param>
653+
/// <param name="ignoreNestedSubtotalAggregate">If cells which value comes from the calculation of a SUBTOTAL or an AGGREGATE function should be ignored, set this to true</param>
654+
/// <param name="arguments"></param>
655+
/// <param name="context"></param>
656+
/// <param name="ignoreNonNumeric"></param>
657+
/// <returns></returns>
658+
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, bool ignoreNestedSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric)
659+
{
660+
return ArgsToDoubleEnumerable(ignoreHiddenCells, true, ignoreNestedSubtotalAggregate, arguments, context, ignoreNonNumeric);
631661
}
632662

633663
/// <summary>
@@ -640,7 +670,7 @@ protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool
640670
/// <returns></returns>
641671
protected virtual IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context, bool ignoreNonNumeric)
642672
{
643-
return ArgsToDoubleEnumerable(ignoreHiddenCells, true, arguments, context, ignoreNonNumeric);
673+
return ArgsToDoubleEnumerable(ignoreHiddenCells, true, false, arguments, context, ignoreNonNumeric);
644674
}
645675

646676

@@ -691,9 +721,9 @@ protected virtual IEnumerable<double> ArgsToDoubleEnumerableZeroPadded(bool igno
691721
/// <param name="arguments"></param>
692722
/// <param name="context"></param>
693723
/// <returns></returns>
694-
protected virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHiddenCells, IEnumerable<FunctionArgument> arguments, ParsingContext context)
724+
protected virtual IEnumerable<object> ArgsToObjectEnumerable(bool ignoreHiddenCells, bool ignoreErrors, bool ignoreNestedSubtotalAggregate, IEnumerable<FunctionArgument> arguments, ParsingContext context)
695725
{
696-
return _argumentCollectionUtil.ArgsToObjectEnumerable(ignoreHiddenCells, arguments, context);
726+
return _argumentCollectionUtil.ArgsToObjectEnumerable(ignoreHiddenCells, ignoreErrors, ignoreNestedSubtotalAggregate, arguments, context);
697727
}
698728

699729
/// <summary>
@@ -755,12 +785,13 @@ protected CompileResult CreateResult(eErrorType errorType)
755785
/// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown
756786
/// </summary>
757787
/// <param name="arg"></param>
758-
/// <exception cref="ExcelErrorValueException"></exception>
759-
protected void CheckForAndHandleExcelError(FunctionArgument arg)
788+
/// <param name="err">If the cell contains an error the error will be assigned to this variable</param>
789+
protected void CheckForAndHandleExcelError(FunctionArgument arg, out ExcelErrorValue err)
760790
{
791+
err = default;
761792
if (arg.ValueIsExcelError)
762793
{
763-
throw (new ExcelErrorValueException(arg.ValueAsExcelErrorValue));
794+
err = arg.ValueAsExcelErrorValue;
764795
}
765796
}
766797

@@ -769,11 +800,13 @@ protected void CheckForAndHandleExcelError(FunctionArgument arg)
769800
/// an <see cref="ExcelErrorValueException"/> with that errorcode will be thrown
770801
/// </summary>
771802
/// <param name="cell"></param>
772-
protected void CheckForAndHandleExcelError(ICellInfo cell)
803+
/// <param name="err">If the cell contains an error the error will be assigned to this variable</param>
804+
protected void CheckForAndHandleExcelError(ICellInfo cell, out ExcelErrorValue err)
773805
{
806+
err = default;
774807
if (cell.IsExcelError)
775808
{
776-
throw (new ExcelErrorValueException(ExcelErrorValue.Parse(cell.Value.ToString())));
809+
err = ExcelErrorValue.Parse(cell.Value.ToString());
777810
}
778811
}
779812

src/EPPlus/FormulaParsing/Excel/Functions/HiddenValuesHandlingFunction.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public abstract class HiddenValuesHandlingFunction : ExcelFunction
2727
public HiddenValuesHandlingFunction()
2828
{
2929
IgnoreErrors = true;
30+
IgnoreNestedSubtotalsAndAggregates = true;
3031
}
3132
/// <summary>
3233
/// Set to true or false to indicate whether the function should ignore hidden values.
@@ -45,6 +46,11 @@ public bool IgnoreErrors
4546
get; set;
4647
}
4748

49+
/// <summary>
50+
/// Set to true to indicate whether the function should ignore nested SUBTOTAL and AGGREGATE functions
51+
/// </summary>
52+
public bool IgnoreNestedSubtotalsAndAggregates { get; set; }
53+
4854
protected override IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(IEnumerable<FunctionArgument> arguments, ParsingContext context)
4955
{
5056
return ArgsToDoubleEnumerable(arguments, context, IgnoreErrors, false);
@@ -61,12 +67,12 @@ protected IEnumerable<ExcelDoubleCellValue> ArgsToDoubleEnumerable(IEnumerable<F
6167
var nonHidden = arguments.Where(x => !x.ExcelStateFlagIsSet(ExcelCellState.HiddenCell));
6268
return base.ArgsToDoubleEnumerable(IgnoreHiddenValues, nonHidden, context);
6369
}
64-
return base.ArgsToDoubleEnumerable(IgnoreHiddenValues, ignoreErrors, arguments, context, ignoreNonNumeric);
70+
return base.ArgsToDoubleEnumerable(IgnoreHiddenValues, ignoreErrors, IgnoreNestedSubtotalsAndAggregates, arguments, context, ignoreNonNumeric);
6571
}
6672

6773
protected bool ShouldIgnore(ICellInfo c, ParsingContext context)
6874
{
69-
if(CellStateHelper.ShouldIgnore(IgnoreHiddenValues, c, context))
75+
if(CellStateHelper.ShouldIgnore(IgnoreHiddenValues, IgnoreNestedSubtotalsAndAggregates, c, context))
7076
{
7177
return true;
7278
}
@@ -78,7 +84,7 @@ protected bool ShouldIgnore(ICellInfo c, ParsingContext context)
7884
}
7985
protected bool ShouldIgnore(FunctionArgument arg, ParsingContext context)
8086
{
81-
if (CellStateHelper.ShouldIgnore(IgnoreHiddenValues, arg, context))
87+
if (CellStateHelper.ShouldIgnore(IgnoreHiddenValues, IgnoreNestedSubtotalsAndAggregates, arg, context))
8288
{
8389
return true;
8490
}

0 commit comments

Comments
 (0)