From 72eaea0704ee230dbe7c23e05a5bd0d32ad29ed0 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Tue, 8 Dec 2009 17:19:09 +0000 Subject: [PATCH] Bugzilla 48343 - added implementation of SUBTOTAL function (patch from Paul Tomlin) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@888490 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../record/formula/eval/FunctionEval.java | 1 + .../record/formula/functions/Subtotal.java | 83 +++++++++++++++++++ .../AllIndividualFunctionEvaluationTests.java | 1 + .../formula/functions/TestSubtotal.java | 55 ++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 src/java/org/apache/poi/hssf/record/formula/functions/Subtotal.java create mode 100644 src/testcases/org/apache/poi/hssf/record/formula/functions/TestSubtotal.java diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 8d36b3bfe..8155aad3b 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 48343 - added implementation of SUBTOTAL function 48332 - fixed XSSFSheet autoSizeColumn() to tolerate empty RichTextString diff --git a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java index 66855c07b..940b2820d 100644 --- a/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java @@ -205,6 +205,7 @@ public final class FunctionEval { retval[342] = NumericFunction.RADIANS; retval[343] = NumericFunction.DEGREES; + retval[344] = new Subtotal(); retval[345] = new Sumif(); retval[346] = new Countif(); retval[347] = new Countblank(); diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Subtotal.java b/src/java/org/apache/poi/hssf/record/formula/functions/Subtotal.java new file mode 100644 index 000000000..911be59ba --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Subtotal.java @@ -0,0 +1,83 @@ +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.ss.formula.eval.NotImplementedException; + +/** + * Implementation for the Excel function SUBTOTAL

+ * + * Syntax :
+ * SUBTOTAL ( functionCode, ref1, ref2 ... )
+ * + * + * + *
functionCode(1-11) Selects the underlying aggregate function to be used (see table below)
ref1, ref2 ...Arguments to be passed to the underlying aggregate function

+ *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
functionCodeAggregate Function
1AVERAGE
2COUNT
3COUNTA
4MAX
5MIN
6PRODUCT
7STDEV
8STDEVP *
9AVERAGE
10VAR *
11VARP *
101-111*

+ * * Not implemented in POI yet. Functions 101-111 are the same as functions 1-11 but with + * the option 'ignore hidden values'. + *

+ * + * @author Paul Tomlin < pault at bulk sms dot com > + */ +public class Subtotal implements Function { + + private static Function findFunction(int functionCode) throws EvaluationException { + switch (functionCode) { + case 1: return AggregateFunction.AVERAGE; + case 2: return new Count(); + case 3: return new Counta(); + case 4: return AggregateFunction.MAX; + case 5: return AggregateFunction.MIN; + case 6: return AggregateFunction.PRODUCT; + case 7: return AggregateFunction.STDEV; + case 8: throw new NotImplementedException("STDEVP"); + case 9: return AggregateFunction.SUM; + case 10: throw new NotImplementedException("VAR"); + case 11: throw new NotImplementedException("VARP"); + } + if (functionCode > 100 && functionCode < 112) { + throw new NotImplementedException("SUBTOTAL - with 'exclude hidden values' option"); + } + throw EvaluationException.invalidValue(); + } + + public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { + int nInnerArgs = args.length-1; // -1: first arg is used to select from a basic aggregate function + if (nInnerArgs < 1) { + return ErrorEval.VALUE_INVALID; + } + + Function innerFunc; + try { + ValueEval ve = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); + int functionCode = OperandResolver.coerceValueToInt(ve); + innerFunc = findFunction(functionCode); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + ValueEval[] innerArgs = new ValueEval[nInnerArgs]; + System.arraycopy(args, 1, innerArgs, 0, nInnerArgs); + + return innerFunc.evaluate(innerArgs, srcRowIndex, srcColumnIndex); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java index 3e2393050..4f50ad1d2 100644 --- a/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/AllIndividualFunctionEvaluationTests.java @@ -48,6 +48,7 @@ public final class AllIndividualFunctionEvaluationTests { result.addTestSuite(TestPmt.class); result.addTestSuite(TestOffset.class); result.addTestSuite(TestRowCol.class); + result.addTestSuite(TestSubtotal.class); result.addTestSuite(TestSumif.class); result.addTestSuite(TestSumproduct.class); result.addTestSuite(TestStatsLib.class); diff --git a/src/testcases/org/apache/poi/hssf/record/formula/functions/TestSubtotal.java b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestSubtotal.java new file mode 100644 index 000000000..108c310cb --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/record/formula/functions/TestSubtotal.java @@ -0,0 +1,55 @@ +package org.apache.poi.hssf.record.formula.functions; + +import org.apache.poi.hssf.record.formula.eval.AreaEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; + +import junit.framework.TestCase; + +/** + * Tests for {@link Subtotal} + * + * @author Paul Tomlin + */ +public final class TestSubtotal extends TestCase { + private static final int FUNCTION_AVERAGE = 1; + private static final int FUNCTION_COUNT = 2; + private static final int FUNCTION_MAX = 4; + private static final int FUNCTION_MIN = 5; + private static final int FUNCTION_PRODUCT = 6; + private static final int FUNCTION_STDEV = 7; + private static final int FUNCTION_SUM = 9; + + private static final double[] TEST_VALUES0 = { + 1, 2, + 3, 4, + 5, 6, + 7, 8, + 9, 10 + }; + + private static void confirmSubtotal(int function, double expected) { + ValueEval[] values = new ValueEval[TEST_VALUES0.length]; + for (int i = 0; i < TEST_VALUES0.length; i++) { + values[i] = new NumberEval(TEST_VALUES0[i]); + } + + AreaEval arg1 = EvalFactory.createAreaEval("C1:D5", values); + ValueEval args[] = { new NumberEval(function), arg1 }; + + ValueEval result = new Subtotal().evaluate(args, 0, 0); + + assertEquals(NumberEval.class, result.getClass()); + assertEquals(expected, ((NumberEval) result).getNumberValue(), 0.0); + } + + public void testBasics() { + confirmSubtotal(FUNCTION_SUM, 55.0); + confirmSubtotal(FUNCTION_AVERAGE, 5.5); + confirmSubtotal(FUNCTION_COUNT, 10.0); + confirmSubtotal(FUNCTION_MAX, 10.0); + confirmSubtotal(FUNCTION_MIN, 1.0); + confirmSubtotal(FUNCTION_PRODUCT, 3628800.0); + confirmSubtotal(FUNCTION_STDEV, 3.0276503540974917); + } +}