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 |
+ *
+ *
+ *
+ *
+ * functionCode | Aggregate Function |
+ * 1 | AVERAGE |
+ * 2 | COUNT |
+ * 3 | COUNTA |
+ * 4 | MAX |
+ * 5 | MIN |
+ * 6 | PRODUCT |
+ * 7 | STDEV |
+ * 8 | STDEVP * |
+ * 9 | AVERAGE |
+ * 10 | VAR * |
+ * 11 | VARP * |
+ * 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);
+ }
+}