--- src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java (revision 829277) +++ src/java/org/apache/poi/hssf/record/formula/eval/FunctionEval.java (working copy) @@ -203,6 +203,7 @@ retval[345] = new Sumif(); retval[346] = new Countif(); + retval[347] = new Countblank(); retval[359] = new Hyperlink(); --- src/java/org/apache/poi/hssf/record/formula/functions/Countblank.java (revision 0) +++ src/java/org/apache/poi/hssf/record/formula/functions/Countblank.java (revision 0) @@ -0,0 +1,73 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +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.BlankEval; +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.RefEval; +import org.apache.poi.hssf.record.formula.eval.ValueEval; +import org.apache.poi.hssf.record.formula.functions.CountUtils.I_MatchPredicate; + +/** + * Implementation for the function COUNTBLANK + *

+ * Syntax: COUNTBLANK ( range ) + * + * + *
range   is the range of cells to count blanks
+ *

+ * + * @author Mads Mohr Christensen + */ +public final class Countblank implements Function { + + public ValueEval evaluate(ValueEval[] args, int srcRowIndex, short srcColumnIndex) { + switch(args.length) { + case 1: + // expected + break; + default: + // TODO - it doesn't seem to be possible to enter COUNTBLANK() into Excel with the wrong arg count + // perhaps this should be an exception + return ErrorEval.VALUE_INVALID; + } + + double result; + if (args[0] instanceof RefEval) { + result = CountUtils.countMatchingCell((RefEval) args[0], predicate); + } else if (args[0] instanceof AreaEval) { + result = CountUtils.countMatchingCellsInArea((AreaEval) args[0], predicate); + } else { + throw new IllegalArgumentException("Bad range arg type (" + args[0].getClass().getName() + ")"); + } + return new NumberEval(result); + } + + private static final I_MatchPredicate predicate = new I_MatchPredicate() { + + public boolean matches(ValueEval valueEval) { + if(valueEval == BlankEval.INSTANCE) { + return true; + } + // Note - only BlankEval counts + return false; + } + }; +} --- src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java (revision 829277) +++ src/testcases/org/apache/poi/hssf/record/formula/functions/TestCountFuncs.java (working copy) @@ -45,7 +45,35 @@ public final class TestCountFuncs extends TestCase { private static final String NULL = null; + + public void testCountBlank() { + AreaEval range; + ValueEval[] values; + + values = new ValueEval[] { + new NumberEval(0), + new StringEval(""), // note - does not match blank + BoolEval.TRUE, + BoolEval.FALSE, + ErrorEval.DIV_ZERO, + BlankEval.INSTANCE, + }; + range = EvalFactory.createAreaEval("A1:B3", values); + confirmCountBlank(1, range); + + values = new ValueEval[] { + new NumberEval(0), + new StringEval(""), // note - does not match blank + BlankEval.INSTANCE, + BoolEval.FALSE, + BoolEval.TRUE, + BlankEval.INSTANCE, + }; + range = EvalFactory.createAreaEval("A1:B3", values); + confirmCountBlank(2, range); + } + public void testCountA() { ValueEval[] args; @@ -196,6 +224,12 @@ double result = NumericFunctionInvoker.invoke(new Countif(), args); assertEquals(expected, result, 0); } + private static void confirmCountBlank(int expected, AreaEval range) { + + ValueEval[] args = { range }; + double result = NumericFunctionInvoker.invoke(new Countblank(), args); + assertEquals(expected, result, 0); + } private static I_MatchPredicate createCriteriaPredicate(ValueEval ev) { return Countif.createCriteriaPredicate(ev, 0, 0); @@ -418,4 +452,36 @@ throw new AssertionFailedError(failureCount + " countif evaluations failed. See stderr for more details"); } } + + public void testCounBlankFromSpreadsheet() { + final String FILE_NAME = "countblankExamples.xls"; + final int START_ROW_IX = 1; + final int COL_IX_ACTUAL = 3; + final int COL_IX_EXPECTED = 4; + + int failureCount = 0; + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(FILE_NAME); + HSSFSheet sheet = wb.getSheetAt(0); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + int maxRow = sheet.getLastRowNum(); + for (int rowIx=START_ROW_IX; rowIx 0) { + throw new AssertionFailedError(failureCount + " countblank evaluations failed. See stderr for more details"); + } + } }