From 712b7463a720af9d9c41d31dfc85e12c0d85fc0b Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Fri, 18 Feb 2011 20:04:50 +0000 Subject: [PATCH] initial import of ExcelAnt tasks, see Bugzilla 50610 git-svn-id: https://svn.apache.org/repos/asf/poi/branches/excelant@1072119 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 133 +++- maven/poi-excelant.pom | 73 +++ .../content/xdocs/spreadsheet/book.xml | 1 + .../content/xdocs/spreadsheet/excelant.xml | 319 ++++++++++ .../poi/ss/excelant/ExcelAntEvaluateCell.java | 146 +++++ .../poi/ss/excelant/ExcelAntHandlerTask.java | 60 ++ .../poi/ss/excelant/ExcelAntPrecision.java | 39 ++ .../apache/poi/ss/excelant/ExcelAntSet.java | 48 ++ .../ss/excelant/ExcelAntSetDoubleCell.java | 60 ++ .../ss/excelant/ExcelAntSetFormulaCell.java | 52 ++ .../ss/excelant/ExcelAntSetStringCell.java | 62 ++ .../apache/poi/ss/excelant/ExcelAntTask.java | 193 ++++++ .../apache/poi/ss/excelant/ExcelAntTest.java | 224 +++++++ .../excelant/ExcelAntUserDefinedFunction.java | 55 ++ .../ss/excelant/IExcelAntWorkbookHandler.java | 46 ++ .../util/ExcelAntEvaluationResult.java | 114 ++++ .../excelant/util/ExcelAntWorkbookUtil.java | 384 ++++++++++++ .../util/ExcelAntWorkbookUtilFactory.java | 63 ++ .../org/apache/poi/ss/excelant/antlib.xml | 30 + .../examples/formula/CalculateMortgage.java | 93 +++ ...ExcelAntUserDefinedFunctionTestHelper.java | 36 ++ .../TestExcelAntUserDefinedFunction.java | 51 ++ .../apache/poi/ss/excelant/BuildFileTest.java | 583 ++++++++++++++++++ .../apache/poi/ss/excelant/TestBuildFile.java | 74 +++ .../ss/excelant/TestExcelAntPrecision.java | 48 ++ .../poi/ss/excelant/TestExcelAntSet.java | 64 ++ .../excelant/TestExcelAntSetDoubleCell.java | 66 ++ .../util/ExcelAntWorkbookUtilTestHelper.java | 55 ++ .../util/TestExcelAntEvaluationResult.java | 67 ++ .../util/TestExcelAntWorkbookUtil.java | 140 +++++ .../util/TestExcelAntWorkbookUtilFactory.java | 69 +++ test-data/spreadsheet/excelant.xls | Bin 0 -> 37888 bytes .../spreadsheet/mortgage-calculation.xls | Bin 0 -> 37376 bytes 33 files changed, 3441 insertions(+), 7 deletions(-) create mode 100755 maven/poi-excelant.pom create mode 100755 src/documentation/content/xdocs/spreadsheet/excelant.xml create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java create mode 100755 src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java create mode 100755 src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml create mode 100755 src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgage.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java create mode 100644 src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java create mode 100755 src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java create mode 100644 test-data/spreadsheet/excelant.xls create mode 100644 test-data/spreadsheet/mortgage-calculation.xls diff --git a/build.xml b/build.xml index 1f7e757eaae..50e17a6e5e3 100644 --- a/build.xml +++ b/build.xml @@ -109,6 +109,15 @@ under the License. + + + + + + + + + + + @@ -155,9 +166,9 @@ under the License. - - - + + + @@ -216,6 +227,18 @@ under the License. + + + + + + + + + + + + @@ -248,6 +271,9 @@ under the License. + + + @@ -272,6 +298,7 @@ under the License. + @@ -293,6 +320,10 @@ under the License. + + + + @@ -397,7 +428,7 @@ under the License. @@ -509,6 +540,36 @@ under the License. + + + + + + + + + + + + + + + + @@ -533,7 +594,7 @@ under the License. - @@ -655,6 +716,37 @@ under the License. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -801,6 +893,13 @@ under the License. + + + + + + + @@ -849,6 +948,11 @@ under the License. + + + + @@ -872,6 +976,11 @@ under the License. + + + + @@ -882,6 +991,7 @@ under the License. + @@ -907,7 +1017,11 @@ under the License. - + + + + + @@ -931,7 +1045,11 @@ under the License. longfile="gnu" compression="gzip"> - + + + + + @@ -1000,6 +1118,7 @@ under the License. + diff --git a/maven/poi-excelant.pom b/maven/poi-excelant.pom new file mode 100755 index 00000000000..12b2b15a11a --- /dev/null +++ b/maven/poi-excelant.pom @@ -0,0 +1,73 @@ + + + + + + 4.0.0 + org.apache.poi + poi-excelant + @VERSION@ + jar + Apache POI + http://poi.apache.org/ + Apache POI Excel Ant Tasks + + + + POI Users List + user-subscribe@poi.apache.org + user-unsubscribe@poi.apache.org + http://mail-archives.apache.org/mod_mbox/poi-user/ + + + POI Developer List + dev-subscribe@poi.apache.org + dev-unsubscribe@poi.apache.org + http://mail-archives.apache.org/mod_mbox/poi-dev/ + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + Apache Software Foundation + http://www.apache.org/ + + + + + org.apache.poi + poi + @VERSION@ + + + org.apache.poi + poi-ooxml + @VERSION@ + + + diff --git a/src/documentation/content/xdocs/spreadsheet/book.xml b/src/documentation/content/xdocs/spreadsheet/book.xml index 09743224c6d..15ac2a26f4c 100644 --- a/src/documentation/content/xdocs/spreadsheet/book.xml +++ b/src/documentation/content/xdocs/spreadsheet/book.xml @@ -40,6 +40,7 @@ + diff --git a/src/documentation/content/xdocs/spreadsheet/excelant.xml b/src/documentation/content/xdocs/spreadsheet/excelant.xml new file mode 100755 index 00000000000..6594bc1b87a --- /dev/null +++ b/src/documentation/content/xdocs/spreadsheet/excelant.xml @@ -0,0 +1,319 @@ + + + + + +
+ ExcelAnt - Ant Tasks for Validating Excel Spreadsheets + + + + +
+ +
ExcelAnt - Ant Tasks for Validating Excel Spreadsheets + +
Introduction +

ExcelAnt is a set of Ant tasks that make it possible to verify or test + a workbook without having to write Java code. Of course, the tasks themselves + are written in Java, but to use this frame work you only need to know a little + bit about Ant.

+

This document covers the basic usage and set up of ExcelAnt.

+

This document will assume basic familiarity with Ant and Ant build files.

+
+
Setup +

To start with, you'll need to have the POI 3.8 or higher jar files. If you test only .xls +workbooks then you need to have the following jars in your path:

+
    +
  • poi-excelant-$version-YYYYDDMM.jar
  • +
  • poi-$version-YYYYDDMM.jar
  • +
  • poi-ooxml-$version-YYYYDDMM.jar
  • +
+

If you evaluate .xlsx workbooks then you need to add these:

+
    +
  • poi-ooxml-schemas-$version-YYYYDDMM.jar
  • +
  • xmlbeans.jar
  • +
  • dom4j.jar
  • +
+

For example, if you have these jars in a lib/ dir in your project, your build.xml + might look like this:

+ + + + + + + +]]> +

Next, you'll need to define the Ant tasks. There are several ways to use ExcelAnt:

+ +
  • The traditional way:
+ +]]> +

+ Where excelant.path referes to the classpath with POI jars. + Using this approach the provided extensions will live in the default namespace. Note that the default task/typenames (evaluate, test) may be too generic and should either be explicitly overridden or used with a namespace. +

+
  • Similar, but assigning a namespace URI:
+ + + + + + + + + + +]]> +
+ +
A Simple Example +

The simplest example of using Excel is the ability to validate that POI is giving you back + the value you expect it to. Does this mean that POI is inaccurate? Hardly. There are cases + where POI is unable to evaluate cells for a variety of reasons. If you need to write code + to integrate a worksheet into an app, you may want to know that it's going to work before + you actually try to write that code. ExcelAnt helps with that.

+ +

Consider the mortgage-calculation.xls file found in the Examples + (/examples/src/org/apache/poi/ss/examples/excelant/simple-mortgage-calculation.xls). This sheet + is shown below:

+ + + +

This sheet calculates the principal and interest payment for a mortgage based + on the amount of the loan, term and rate. To write a simple ExcelAnt test you + need to tell ExcelAnt about the file like this:

+ + + + + + + + + +]]> + + +

This code sets up ExcelAnt to access the file defined in the ant property + xls.file. Then it creates a 'test' named 'checkValue'. Finally it tries + to evaluate the B4 on the sheet named 'MortgageCalculator'. There are some assumptions + here that are worth explaining. For starters, ExcelAnt is focused on the testing + numerically oriented sheets. The <evaluate> task is actually evaluating the + cell as a formula using a FormulaEvaluator instance from POI. Therefore it will fail + if you point it to a cell that doesn't contain a formula or a test a plain old number.

+ +

Having said all that, here is what the output looks like:

+ + + +
+ +
Setting Values into a Cell +

So now we know that at a minimum POI can use our sheet to calculate the existing value. + This is an important point: in many cases sheets have dependencies, i.e., cells they reference. + As is often the case, these cells may have dependencies, which may have dependencies, etc. + The point is that sometimes a dependent cell may get adjusted by a macro or a function + and it may be that POI doesn't have the capabilities to do the same thing. This test + verifies that we can rely on POI to retrieve the default value, based on the stored values + of the sheet. Now we want to know if we can manipulate those dependencies and verify + the output.

+ +

To verify that we can manipulate cell values, we need a way in ExcelAnt to set a value. + This is provided by the following task types:

+
    +
  • setDouble() - sets the specified cell as a double.
  • +
  • setFormula() - sets the specified cell as a formula.
  • +
  • setString() = sets the specified cell as a String.
  • +
+ +

For the purposes of this example we'll use the <setDouble> task. Let's + start with a $240,000, 30 year loan at 11% (let's pretend it's like 1984). Here + is how we will set that up:

+ + + + + +]]> + +

Don't forget that we're verifying the behavior so you need to put all this + into the sheet. That is how I got the result of $2,285 and change. So save your + changes and run it; you should get the following:

+ + + +
+ +
Getting More Details + +

This is great, it's working! However, suppose you want to see a little more detail. The + ExcelAnt tasks leverage the Ant logging so you can add the -verbose and -debug flags to + the Ant command line to get more detail. Try adding -verbose. Here is what + you should see:

+ + + + +

We see a little more detail. Notice that we see that there is a setting for global precision. + Up until now we've been setting the precision on each evaluate that we call. This + is obviously useful but it gets cumbersome. It would be better if there were a way + that we could specify a global precision - and there is. There is a <precision> + tag that you can specify as a child of the <excelant> tag. Let's go back to + our original task we set up earlier and modify it:

+ + + + + + + + + + + +]]> + +

In this example we have set the global precision to 1.0e-3. This means that + in the absence of something more stringent, all tests in the task will use + the global precision. We can still override this by specifying the + precision attribute of all of our <evaluate> task. Let's first run + this task with the global precision and the -verbose flag:

+ + + + +

As the output clearly shows, the test itself has no precision but there is + the global precision. Additionally, it tells us we're going to use that + more stringent global value. Now suppose that for this test we want + to use a more stringent precision, say 1.0e-4. We can do that by adding + the precision attribute back to the <evaluate> task:

+ + + + + + + + + + +]]> + + +

Now when you re-run this test with the verbose flag you will see that + your test ran and passed with the higher precision:

+ +
+ +
Leveraging User Defined Functions +

POI has an excellent feature (besides ExcelAnt) called User Defined Functions, + that allows you to write Java code that will be used in place of custom VB + code or macros is a spreadsheet. If you have read the documentation and written + your own FreeRefFunction implmentations, ExcelAnt can make use of this code. + For each <excelant> task you define you can nest a <udf> tag + which allows you to specify the function alias and the class name.

+ +

Consider the previous example of the mortgage calculator. What if, instead + of being a formula in a cell, it was a function defined in a VB macro? As luck + would have it, we already have an example of this in the examples from the + User Defined Functions example, so let's use that. In the example spreadsheet + there is a tab for MortgageCalculatorFunction, which will use. If you look in + cell B4, you see that rather than a messy cell based formula, there is only the function + call. Let's not get bogged down in the function/Java implementation, as these + are covered in the User Defined Function documentation. Let's just add + a new target and test to our existing build file:

+ + + + + + + + + + + + +]]> + +

So if you look at this carefully it looks the same as the previous examples. We + still use the global precision, we're still setting values, and we still want + to evaluate a cell. The only real differences are the sheet name and the + addition of the function.

+
+
+ +
diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java new file mode 100755 index 00000000000..d35cc6a7d8d --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntEvaluateCell.java @@ -0,0 +1,146 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.poi.ss.excelant.util.ExcelAntEvaluationResult; +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Instances of this class are used to evaluate a single cell. This is usually + * after some values have been set. The evaluation is actually performed + * by a WorkbookUtil instance. The evaluate() method of the WorkbookUtil + * class returns an EvaluationResult which encapsulates the results and + * information from the evaluation. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + + * + */ +public class ExcelAntEvaluateCell extends Task { + + private String cell ; + private double expectedValue ; + private double precision ; + private double precisionToUse ; + private double globalPrecision ; + private boolean requiredToPass = false ; + + + private ExcelAntEvaluationResult result ; + + private ExcelAntWorkbookUtil wbUtil ; + + private boolean showDelta = false ; + + + public ExcelAntEvaluateCell() {} + + protected void setWorkbookUtil( ExcelAntWorkbookUtil wb ) { + wbUtil = wb ; + } + + public void setShowDelta( boolean value ) { + showDelta = value ; + } + + protected boolean showDelta() { + return showDelta ; + } + + public void setCell(String cell) { + this.cell = cell; + } + + public void setRequiredToPass( boolean val ) { + requiredToPass = val ; + } + + protected boolean requiredToPass() { + return requiredToPass ; + } + + public void setExpectedValue(double expectedValue) { + this.expectedValue = expectedValue; + } + + public void setPrecision(double precision) { + this.precision = precision; + } + + protected void setGlobalPrecision( double prec ) { + globalPrecision = prec ; + } + + protected String getCell() { + return cell; + } + + protected double getExpectedValue() { + return expectedValue; + } + + protected double getPrecision() { + return precisionToUse; + } + + public void execute() throws BuildException { + + precisionToUse = 0 ; + + // if there is a globalPrecision we will use it unless there is also + // precision set at the evaluate level, then we use that. If there + // is not a globalPrecision, we will use the local precision. + log( "test precision = " + precision + "\tglobal precision = " + globalPrecision, Project.MSG_VERBOSE ) ; + if( globalPrecision > 0 ) { + if( precision > 0 ) { + precisionToUse = precision ; + log( "Using evaluate precision of " + precision + " over the " + + "global precision of " + globalPrecision, Project.MSG_VERBOSE ) ; + } else { + precisionToUse = globalPrecision ; + log( "Using global precision of " + globalPrecision, Project.MSG_VERBOSE ) ; + } + } else { + precisionToUse = precision ; + log( "Using evaluate precision of " + precision, Project.MSG_VERBOSE ) ; + } + result = wbUtil.evaluateCell(cell, expectedValue, precisionToUse ) ; + + StringBuffer sb = new StringBuffer() ; + sb.append( "evaluation of cell " ) ; + sb.append( cell ) ; + sb.append( " resulted in " ) ; + sb.append( result.getReturnValue() ) ; + if( showDelta == true ) { + sb.append( " with a delta of " + result.getDelta() ) ; + } + + log( sb.toString(), Project.MSG_DEBUG) ; + + } + + public ExcelAntEvaluationResult getResult() { + return result ; + } + + +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java new file mode 100755 index 00000000000..af210f63ef3 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntHandlerTask.java @@ -0,0 +1,60 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This is the class that backs the tag in the Ant task. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntHandlerTask extends Task { + + private String className ; + + private ExcelAntWorkbookUtil wbUtil ; + + public void setClassName( String cName ) { + className = cName ; + } + + protected void setEAWorkbookUtil( ExcelAntWorkbookUtil wkbkUtil ) { + wbUtil = wkbkUtil ; + } + + public void execute() throws BuildException { + log( "handling the workbook with class " + className, Project.MSG_INFO ) ; + try { + Class clazz = Class.forName( className ) ; + Object handlerObj = clazz.newInstance() ; + if( handlerObj instanceof IExcelAntWorkbookHandler ) { + IExcelAntWorkbookHandler iHandler = (IExcelAntWorkbookHandler)handlerObj ; + iHandler.setWorkbook( wbUtil.getWorkbook() ) ; + iHandler.execute() ; + } + } catch( Exception e ) { + throw new BuildException( e.getMessage(), e ) ; + } + } + } diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java new file mode 100755 index 00000000000..0739ef58332 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntPrecision.java @@ -0,0 +1,39 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.tools.ant.taskdefs.Typedef; + +/** + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntPrecision extends Typedef { + + private double value ; + + public void setValue( double precision ) { + value = precision ; + } + + public double getValue() { + return value ; + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java new file mode 100755 index 00000000000..974b1b9e4c8 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSet.java @@ -0,0 +1,48 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.tools.ant.Task; + +/** + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public abstract class ExcelAntSet extends Task { + + protected String cellStr ; + + protected ExcelAntWorkbookUtil wbUtil ; + + public void setCell( String cellName ) { + cellStr = cellName ; + } + + public String getCell() { + return cellStr ; + } + + + public void setWorkbookUtil( ExcelAntWorkbookUtil wb ) { + wbUtil = wb ; + } + +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java new file mode 100755 index 00000000000..a1113509a23 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetDoubleCell.java @@ -0,0 +1,60 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Class for use in an Ant build script that sets the value of an Excel + * sheet cell using the cell id ('Sheet Name'!cellId). + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntSetDoubleCell extends ExcelAntSet { + + + private double cellValue ; + + public ExcelAntSetDoubleCell() {} + + /** + * Set the value of the specified cell as the double passed in. + * @param value + */ + public void setValue( double value ) { + cellValue = value ; + } + + /** + * Return the cell value as a double. + * @return + */ + public double getCellValue() { + return cellValue; + } + + public void execute() throws BuildException { + + wbUtil.setDoubleValue(cellStr, cellValue ) ; + + log( "set cell " + cellStr + " to value " + cellValue + " as double.", Project.MSG_DEBUG ) ; + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java new file mode 100755 index 00000000000..1b9d5f0539c --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetFormulaCell.java @@ -0,0 +1,52 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Class for use in an Ant build script that sets the formula of an Excel + * sheet cell using the cell id ('Sheet Name'!cellId). + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntSetFormulaCell extends ExcelAntSet { + + + private String cellValue ; + + public ExcelAntSetFormulaCell() {} + + public void setValue( String value ) { + cellValue = value ; + } + + protected String getCellValue() { + return cellValue; + } + + public void execute() throws BuildException { + + wbUtil.setFormulaValue( cellStr, cellValue ) ; + + log( "set cell " + cellStr + " to formula " + cellValue, Project.MSG_DEBUG ) ; + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java new file mode 100755 index 00000000000..12221ee1703 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntSetStringCell.java @@ -0,0 +1,62 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Class for use in an Ant build script that sets the value of an Excel + * sheet cell using the cell id ('Sheet Name'!cellId). + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntSetStringCell extends ExcelAntSet { + + + private String stringValue ; + + + public ExcelAntSetStringCell() {} + + + /** + * Set the value of the cell to the String passed in. + * @param value + */ + public void setValue(String value ) { + stringValue = value ; + } + + /** + * Return the value that will be set into the cell. + * @return + */ + public String getCellValue() { + return stringValue; + } + + public void execute() throws BuildException { + + wbUtil.setStringValue(cellStr, stringValue ) ; + + log( "set cell " + cellStr + " to value " + stringValue + " as String.", Project.MSG_DEBUG ) ; + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java new file mode 100755 index 00000000000..1bbbb7d01f9 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTask.java @@ -0,0 +1,193 @@ +/* ==================================================================== + 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.ss.excelant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; + +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Ant task class for testing Excel workbook cells. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntTask extends Task { + + public static final String VERSION = "0.5.0" ; + + private String excelFileName ; + + private boolean failOnError = false ; + + private ExcelAntWorkbookUtil workbookUtil ; + + private ExcelAntPrecision precision ; + + private LinkedList tests ; + private LinkedList functions ; + + public ExcelAntTask() { + tests = new LinkedList() ; + functions = new LinkedList() ; + } + + public void addPrecision( ExcelAntPrecision prec ) { + precision = prec ; + } + + public void setFailOnError( boolean value ) { + failOnError = value ; + } + public void setFileName( String fileName ) { + excelFileName = fileName ; + } + + public void addTest( ExcelAntTest testElement ) { + tests.add( testElement ) ; + } + + public void addUdf( ExcelAntUserDefinedFunction def ) { + functions.add( def ) ; + } + + public void execute() throws BuildException { + checkClassPath(); + + int totalCount = 0 ; + int successCount = 0 ; + + StringBuffer versionBffr = new StringBuffer() ; + versionBffr.append( "ExcelAnt version " ) ; + versionBffr.append( VERSION ) ; + versionBffr.append( " Copyright 2011" ) ; + SimpleDateFormat sdf = new SimpleDateFormat( "yyyy" ) ; + double currYear = Double.parseDouble( sdf.format( new Date() ) ); + if( currYear > 2011 ) { + versionBffr.append( "-" ) ; + versionBffr.append( currYear ) ; + } + log( versionBffr.toString(), Project.MSG_INFO ) ; + + log( "Using input file: " + excelFileName, Project.MSG_INFO ) ; + + Workbook targetWorkbook = loadWorkbook() ; + if( targetWorkbook == null ) { + log( "Unable to load " + excelFileName + + ". Verify the file exists and can be read.", + Project.MSG_ERR ) ; + return ; + } + if( tests != null && tests.size() > 0 ) { + + Iterator testsIt = tests.iterator() ; + while( testsIt.hasNext() ) { + ExcelAntTest test = testsIt.next(); + + log( "executing test: " + test.getName(), Project.MSG_DEBUG ) ; + + workbookUtil = ExcelAntWorkbookUtilFactory.getInstance( excelFileName ) ; + + if( functions != null ) { + Iterator functionsIt = functions.iterator() ; + while( functionsIt.hasNext() ) { + ExcelAntUserDefinedFunction eaUdf = functionsIt.next() ; + try { + workbookUtil.addFunction(eaUdf.getFunctionAlias(), eaUdf.getClassName() ) ; + } catch ( Exception e) { + throw new BuildException( e.getMessage(), e ); + } + } + } + test.setWorkbookUtil( workbookUtil ) ; + + if( precision != null && precision.getValue() > 0 ) { + log( "setting precision for the test " + test.getName(), Project.MSG_VERBOSE ) ; + test.setPrecision( precision.getValue() ) ; + } + + test.execute() ; + + if( test.didTestPass() ) { + successCount++ ; + } else { + if( failOnError == true ) { + throw new BuildException( "Test " + test.getName() + " failed." ) ; + } + } + totalCount++ ; + + workbookUtil = null ; + } + log( successCount + "/" + totalCount + " tests passed.", Project.MSG_INFO ) ; + workbookUtil = null ; + } + } + + + private Workbook loadWorkbook() { + if (excelFileName == null) { + throw new BuildException("fileName attribute must be set!", + getLocation()); + } + + Workbook workbook; + File workbookFile = new File( excelFileName ) ; + try { + FileInputStream fis = new FileInputStream( workbookFile ) ; + workbook = WorkbookFactory.create( fis ) ; + } catch (Exception e) { + throw new BuildException("Cannot load file " + excelFileName + + ". Make sure the path and file permissions are correct.", e, getLocation()); + } + return workbook ; + } + + + /** + * ExcelAnt depends on external libraries not included in the Ant distribution. + * Give user a sensible message if any if the required jars are missing. + */ + private void checkClassPath(){ + try { + Class.forName("org.apache.poi.hssf.usermodel.HSSFWorkbook"); + Class.forName("org.apache.poi.ss.usermodel.WorkbookFactory"); + } catch (Throwable e) { + throw new BuildException( + "The for must include poi.jar and poi-ooxml.jar " + + "if not in Ant's own classpath. Processing .xlsx spreadsheets requires " + + "additional poi-ooxml-schemas.jar, xmlbeans.jar and dom4j.jar" , + e, getLocation()); + } + + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java new file mode 100755 index 00000000000..b97d1ae917f --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntTest.java @@ -0,0 +1,224 @@ +/* ==================================================================== + 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.ss.excelant; + +import java.util.Iterator; +import java.util.LinkedList; + +import org.apache.poi.ss.excelant.util.ExcelAntEvaluationResult; +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This class represents a single test. In order for the test any and all + * ExcelAntEvaluateCell evaluations must pass. Therefore it is recommended + * that you use only 1 evaluator but you can use more if you choose. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntTest extends Task{ + + private LinkedList setters ; + private LinkedListevaluators ; + + private LinkedList testTasks ; + + private String name ; + + private double globalPrecision ; + + private boolean showSuccessDetails = false ; + + private boolean showFailureDetail = false ; + LinkedList failureMessages ; + + + private ExcelAntWorkbookUtil workbookUtil ; + + private boolean passed = true ; + + + public ExcelAntTest() { + setters = new LinkedList() ; + evaluators = new LinkedList() ; + failureMessages = new LinkedList() ; + testTasks = new LinkedList() ; + } + + public void setPrecision( double precision ) { + globalPrecision = precision ; + } + + public void setWorkbookUtil( ExcelAntWorkbookUtil wbUtil ) { + workbookUtil = wbUtil ; + } + + + public void setShowFailureDetail( boolean value ) { + showFailureDetail = value ; + } + + public void setName( String nm ) { + name = nm ; + } + + public String getName() { + return name ; + } + + public void setShowSuccessDetails( boolean details ) { + showSuccessDetails = details ; + } + + public boolean showSuccessDetails() { + return showSuccessDetails ; + } + + public void addSetDouble( ExcelAntSetDoubleCell setter ) { + addSetter( setter ) ; + } + + public void addSetString( ExcelAntSetStringCell setter ){ + addSetter( setter ) ; + } + + public void addSetFormula( ExcelAntSetFormulaCell setter ) { + addSetter( setter ) ; + } + + public void addHandler( ExcelAntHandlerTask handler ) { + testTasks.add( handler ) ; + } + + private void addSetter( ExcelAntSet setter ) { +// setters.add( setter ); + testTasks.add( setter ) ; + } + + public void addEvaluate( ExcelAntEvaluateCell evaluator ) { +// evaluators.add( evaluator ) ; + testTasks.add( evaluator ) ; + } + +// public LinkedList getSetters() { +// return setters; +// } + + protected LinkedList getEvaluators() { + return evaluators; + } + + public void execute() throws BuildException { + + Iterator taskIt = testTasks.iterator() ; + + int testCount = evaluators.size() ; + int failureCount = 0 ; + + // roll over all sub task elements in one loop. This allows the + // ordering of the sub elements to be considered. + while( taskIt.hasNext() ) { + Task task = taskIt.next() ; + + // log( task.getClass().getName(), Project.MSG_INFO ) ; + + if( task instanceof ExcelAntSet ) { + ExcelAntSet set = (ExcelAntSet) task ; + set.setWorkbookUtil(workbookUtil) ; + set.execute() ; + } + + if( task instanceof ExcelAntHandlerTask ) { + ExcelAntHandlerTask handler = (ExcelAntHandlerTask)task ; + handler.setEAWorkbookUtil(workbookUtil ) ; + handler.execute() ; + } + + if (task instanceof ExcelAntEvaluateCell ) { + ExcelAntEvaluateCell eval = (ExcelAntEvaluateCell)task ; + eval.setWorkbookUtil( workbookUtil ) ; + + if( globalPrecision > 0 ) { + log( "setting globalPrecision to " + globalPrecision + " in the evaluator", Project.MSG_VERBOSE ) ; + eval.setGlobalPrecision( globalPrecision ) ; + } + + try { + eval.execute() ; + ExcelAntEvaluationResult result = eval.getResult() ; + if( result.didTestPass() && + result.evaluationCompleteWithError() == false ) { + if( showSuccessDetails == true ) { + log("Succeeded when evaluating " + + result.getCellName() + ". It evaluated to " + + result.getReturnValue() + " when the value of " + + eval.getExpectedValue() + " with precision of " + + eval.getPrecision(), Project.MSG_INFO ) ; + } + } else { + if( showFailureDetail == true ) { + failureMessages.add( "\tFailed to evaluate cell " + + result.getCellName() + ". It evaluated to " + + result.getReturnValue() + " when the value of " + + eval.getExpectedValue() + " with precision of " + + eval.getPrecision() + " was expected." ) ; + + } + passed = false ; + failureCount++ ; + + if( eval.requiredToPass() == true ) { + throw new BuildException( "\tFailed to evaluate cell " + + result.getCellName() + ". It evaluated to " + + result.getReturnValue() + " when the value of " + + eval.getExpectedValue() + " with precision of " + + eval.getPrecision() + " was expected." ) ; + } + } + } catch( NullPointerException npe ) { + // this means the cell reference in the test is bad. + log( "Cell assignment " + eval.getCell() + " in test " + getName() + + " appears to point to an empy cell. Please check the " + + " reference in the ant script.", Project.MSG_ERR ) ; + } + } + } + + if( passed == false ) { + log( "Test named " + name + " failed because " + failureCount + + " of " + testCount + " evaluations failed to " + + "evaluate correctly.", + Project.MSG_ERR ) ; + if( showFailureDetail == true && failureMessages.size() > 0 ) { + Iterator failures = failureMessages.iterator() ; + while( failures.hasNext() ) { + log( failures.next(), Project.MSG_ERR ) ; + } + } + } + } + + public boolean didTestPass() { + + return passed ; + } + } diff --git a/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java new file mode 100755 index 00000000000..f69e9b6366d --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/ExcelAntUserDefinedFunction.java @@ -0,0 +1,55 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.tools.ant.taskdefs.Typedef; + +/** + * This class encapsulates the Strings necessary to create the User Defined + * Function instances that will be passed to POI's Evaluator instance. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntUserDefinedFunction extends Typedef { + + + public String functionAlias ; + + public String className ; + + + public ExcelAntUserDefinedFunction() {} + + protected String getFunctionAlias() { + return functionAlias; + } + + public void setFunctionAlias(String functionAlias) { + this.functionAlias = functionAlias; + } + + protected String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java b/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java new file mode 100755 index 00000000000..3cd477abc86 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/IExcelAntWorkbookHandler.java @@ -0,0 +1,46 @@ +/* ==================================================================== + 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.ss.excelant; + +import org.apache.poi.ss.usermodel.Workbook; + + +/** + * In Excel there are many ways to handle manipulating a workbook based + * on some arbitrary user action (onChange, etc). You use this interface + * to create classes that will handle the workbook in whatever manner is needed + * that cannot be handled by POI. + *

+ * For example, suppose that in Excel when you update a cell the workbook + * does some calculations and updates other cells based on that change. In + * ExcelAnt you would set the value of the cell then write your own handler + * then call that from your Ant task after the set task. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public interface IExcelAntWorkbookHandler { + + + public void setWorkbook( Workbook workbook ) ; + + public void execute() ; + + +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java new file mode 100755 index 00000000000..66333571576 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntEvaluationResult.java @@ -0,0 +1,114 @@ +/* ==================================================================== + 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.ss.excelant.util; + +/** + * A simple class that encapsulates information about a cell evaluation + * from POI. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntEvaluationResult { + + /** + * This boolean flag is used to determine if the evaluation completed + * without error. This alone doesn't ensure that the evaluation was + * sucessful. + */ + private boolean evaluationCompletedWithError ; + + /** + * This boolean flag is used to determine if the result was within + * the specified precision. + */ + private boolean didPass ; + + /** + * This is the actual value returned from the evaluation. + */ + private double returnValue ; + + /** + * Any error message String values that need to be returned. + */ + private String errorMessage ; + + /** + * Stores the absolute value of the delta for this evaluation. + */ + private double actualDelta ; + + /** + * This stores the fully qualified cell name (sheetName!cellId). + */ + private String cellName ; + + + + public ExcelAntEvaluationResult( boolean completedWithError, + boolean passed, + double retValue, + String errMessage, + double delta, + String cellId ) { + + evaluationCompletedWithError = completedWithError; + didPass = passed; + returnValue = retValue; + errorMessage = errMessage; + actualDelta = delta ; + cellName = cellId ; + } + + public double getReturnValue() { + return returnValue; + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean didTestPass() { + return didPass ; + } + + public boolean evaluationCompleteWithError() { + return evaluationCompletedWithError ; + } + + public double getDelta() { + return actualDelta ; + } + + public String getCellName() { + return cellName ; + } + + @Override + public String toString() { + return "ExcelAntEvaluationResult [evaluationCompletedWithError=" + + evaluationCompletedWithError + ", didPass=" + didPass + + ", returnValue=" + returnValue + ", errorMessage=" + + errorMessage + ", actualDelta=" + actualDelta + ", cellName=" + + cellName + "]"; + } + + +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java new file mode 100755 index 00000000000..af23d69c0c0 --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtil.java @@ -0,0 +1,384 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.functions.FreeRefFunction; +import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; +import org.apache.poi.ss.formula.udf.DefaultUDFFinder; +import org.apache.poi.ss.formula.udf.UDFFinder; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Typedef; + +import java.io.File; +import java.io.FileInputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; + +/** + * A general utility class that abstracts the POI details of loading the + * workbook, accessing and updating cells. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntWorkbookUtil extends Typedef { + + private String excelFileName; + + private Workbook workbook; + + private HashMap xlsMacroList; + + /** + * Constructs an instance using a String that contains the fully qualified + * path of the Excel file. This constructor initializes a Workbook instance + * based on that file name. + * + * @param fName + */ + protected ExcelAntWorkbookUtil(String fName) { + excelFileName = fName; + xlsMacroList = new HashMap() ; + loadWorkbook(); + + } + + /** + * Constructs an instance based on a Workbook instance. + * + * @param wb + */ + protected ExcelAntWorkbookUtil(Workbook wb) { + workbook = wb; + xlsMacroList = new HashMap() ; + } + + /** + * Loads the member variable workbook based on the fileName variable. + * @return + */ + private Workbook loadWorkbook() { + + File workbookFile = new File(excelFileName); + try { + FileInputStream fis = new FileInputStream(workbookFile); + workbook = WorkbookFactory.create(fis); + } catch(Exception e) { + throw new BuildException("Cannot load file " + excelFileName + + ". Make sure the path and file permissions are correct.", e); + } + + return workbook ; + } + + /** + * Used to add a UDF to the evaluator. + * @param name + * @param clazzName + * @throws ClassNotFoundException + * @throws InstantiationException + * @throws IllegalAccessException + */ + public void addFunction( String name, String clazzName ) throws ClassNotFoundException, InstantiationException, IllegalAccessException { + Class clazzInst = Class.forName( clazzName ) ; + Object newInst = clazzInst.newInstance() ; + if( newInst instanceof FreeRefFunction ) { + addFunction( name, (FreeRefFunction)newInst ) ; + } + + } + + /** + * Updates the internal HashMap of functions with instance and alias passed + * in. + * + * @param name + * @param func + */ + protected void addFunction(String name, FreeRefFunction func) { + xlsMacroList.put(name, func); + } + + /** + * returns a UDFFinder that contains all of the functions added. + * + * @return + */ + protected UDFFinder getFunctions() { + + String[] names = new String[xlsMacroList.size()]; + FreeRefFunction[] functions = new FreeRefFunction[xlsMacroList.size()]; + + Iterator keysIt = xlsMacroList.keySet().iterator(); + int x = 0; + while (keysIt.hasNext()) { + String name = keysIt.next(); + FreeRefFunction function = xlsMacroList.get(name); + names[x] = name; + functions[x] = function; + } + + UDFFinder udff1 = new DefaultUDFFinder(names, functions); + UDFFinder udff = new AggregatingUDFFinder(udff1); + + return udff; + + } + + /** + * Returns a formula evaluator that is loaded with the functions that + * have been supplied. + * + * @param excelFileName + * @return + */ + protected FormulaEvaluator getEvaluator( String excelFileName ) { + FormulaEvaluator evaluator ; + if (excelFileName.endsWith(".xlsx")) { + if( xlsMacroList != null && xlsMacroList.size() > 0 ) { + evaluator = XSSFFormulaEvaluator.create( (XSSFWorkbook) workbook, + null, + getFunctions() ) ; + } + evaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook); + } else { + if( xlsMacroList != null && xlsMacroList.size() > 0 ) { + evaluator = HSSFFormulaEvaluator.create( (HSSFWorkbook)workbook, + null, + getFunctions() ) ; + } + + evaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook); + } + + return evaluator ; + + } + + /** + * Returns the Workbook instance associated with this WorkbookUtil. + * + * @return + */ + public Workbook getWorkbook() { + return workbook; + } + + /** + * Returns the fileName that was used to initialize this instance. May + * return null if the instance was constructed from a Workbook object. + * + * @return + */ + public String getFileName() { + return excelFileName; + } + + /** + * Returns the list of sheet names. + * + * @return + */ + public ArrayList getSheets() { + ArrayList sheets = new ArrayList() ; + + int sheetCount = workbook.getNumberOfSheets() ; + + for( int x=0; x precision) { + evalResults = new ExcelAntEvaluationResult(false, false, + resultOfEval.getNumberValue(), + "Results was out of range based on precision " + " of " + + precision + ". Delta was actually " + delta, delta, cellName ); + } else { + evalResults = new ExcelAntEvaluationResult(false, true, + resultOfEval.getNumberValue(), + "Evaluation passed without error within in range.", delta, cellName ); + } + } else { + String errorMeaning = null ; + try { + errorMeaning = ErrorConstants.getText( resultOfEval + .getErrorValue() ) ; + } catch( IllegalArgumentException iae ) { + errorMeaning = "unknown error code: " + + Byte.toString( resultOfEval.getErrorValue() ) ; + } + + evalResults = new ExcelAntEvaluationResult(false, false, + resultOfEval.getNumberValue(), + "Evaluation failed due to an evaluation error of " + + resultOfEval.getErrorValue() + + " which is " + + errorMeaning, 0, cellName ); + } + + return evalResults; + } + + /** + * Returns a Cell as a String value. + * + * @param cellName + * @return + */ + public String getCellAsString( String cellName ) { + Cell cell = getCell( cellName ) ; + if( cell != null ) { + return cell.getStringCellValue() ; + } + return "" ; + } + + + /** + * Returns the value of the Cell as a double. + * + * @param cellName + * @return + */ + public double getCellAsDouble( String cellName ) { + Cell cell = getCell( cellName ) ; + if( cell != null ) { + return cell.getNumericCellValue() ; + } + return 0.0 ; + } + /** + * Returns a cell reference based on a String in standard Excel format + * (SheetName!CellId). This method will create a new cell if the + * requested cell isn't initialized yet. + * + * @param cellName + * @return + */ + private Cell getCell(String cellName) { + + CellReference cellRef = new CellReference(cellName); + String sheetName = cellRef.getSheetName(); + Sheet sheet = workbook.getSheet(sheetName); + if(sheet == null) { + throw new BuildException("Sheet not found: " + sheetName); + } + + int rowIdx = cellRef.getRow(); + int colIdx = cellRef.getCol(); + Row row = sheet.getRow(rowIdx); + + if( row == null ) { + row = sheet.createRow( rowIdx ) ; + } + + Cell cell = row.getCell(colIdx); + + if( cell == null ) { + cell = row.createCell( colIdx ) ; + } + + return cell; + } + +} diff --git a/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java new file mode 100755 index 00000000000..6c29eeba74c --- /dev/null +++ b/src/excelant/java/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilFactory.java @@ -0,0 +1,63 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import java.util.HashMap; + + +/** + * This is a factory class maps file names to WorkbookUtil instances. This + * helps ExcelAnt be more efficient when being run many times in an Ant build. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class ExcelAntWorkbookUtilFactory { + + private static HashMap workbookUtilMap ; + + private static ExcelAntWorkbookUtilFactory factory ; + + private ExcelAntWorkbookUtilFactory() { + workbookUtilMap = new HashMap() ; + } + + /** + * Using the fileName, check the internal map to see if an instance + * of the WorkbookUtil exists. If not, then add an instance to the map. + * + * @param fileName + * @return + */ + public static ExcelAntWorkbookUtil getInstance( String fileName ) { + + if( factory == null ) { + factory = new ExcelAntWorkbookUtilFactory() ; + } + if( workbookUtilMap != null && + workbookUtilMap.containsKey( fileName ) ) { + return workbookUtilMap.get( fileName ) ; + } else { + ExcelAntWorkbookUtil wbu = new ExcelAntWorkbookUtil( fileName ) ; + workbookUtilMap.put( fileName, wbu ) ; + return wbu ; + } + } + +} diff --git a/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml b/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml new file mode 100755 index 00000000000..31dadfaf607 --- /dev/null +++ b/src/excelant/resources/org/apache/poi/ss/excelant/antlib.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgage.java b/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgage.java new file mode 100755 index 00000000000..4b9a325cdf8 --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/examples/formula/CalculateMortgage.java @@ -0,0 +1,93 @@ +/* ==================================================================== + 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.ss.examples.formula; + +import org.apache.poi.ss.formula.OperationEvaluationContext ; +import org.apache.poi.ss.formula.eval.ErrorEval ; +import org.apache.poi.ss.formula.eval.EvaluationException ; +import org.apache.poi.ss.formula.eval.NumberEval ; +import org.apache.poi.ss.formula.eval.OperandResolver ; +import org.apache.poi.ss.formula.eval.ValueEval ; +import org.apache.poi.ss.formula.functions.FreeRefFunction ; + +/** + * A simple user-defined function to calculate principal and interest. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class CalculateMortgage implements FreeRefFunction { + + public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) { + + // verify that we have enough data + if (args.length != 3) { + return ErrorEval.VALUE_INVALID; + } + + // declare doubles for values + double principal, rate, years, result; + try { + // extract values as ValueEval + ValueEval v1 = OperandResolver.getSingleValue( args[0], + ec.getRowIndex(), + ec.getColumnIndex() ) ; + ValueEval v2 = OperandResolver.getSingleValue( args[1], + ec.getRowIndex(), + ec.getColumnIndex() ) ; + ValueEval v3 = OperandResolver.getSingleValue( args[2], + ec.getRowIndex(), + ec.getColumnIndex() ) ; + + // get data as doubles + principal = OperandResolver.coerceValueToDouble( v1 ) ; + rate = OperandResolver.coerceValueToDouble( v2 ) ; + years = OperandResolver.coerceValueToDouble( v3 ) ; + + result = calculateMortgagePayment( principal, rate, years ) ; + System.out.println( "Result = " + result ) ; + + checkValue(result); + + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + return new NumberEval( result ) ; + } + + public double calculateMortgagePayment( double p, double r, double y ) { + double i = r / 12 ; + double n = y * 12 ; + + double principalAndInterest = + p * (( i * Math.pow((1 + i),n ) ) / ( Math.pow((1 + i),n) - 1)) ; + + return principalAndInterest ; + } + /** + * Excel does not support infinities and NaNs, rather, it gives a #NUM! error in these cases + * + * @throws EvaluationException (#NUM!) if result is NaN or Infinity + */ + private void checkValue(double result) throws EvaluationException { + if (Double.isNaN(result) || Double.isInfinite(result)) { + throw new EvaluationException(ErrorEval.NUM_ERROR); + } + } +} diff --git a/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java b/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java new file mode 100755 index 00000000000..68e4b6957ff --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/examples/formula/ExcelAntUserDefinedFunctionTestHelper.java @@ -0,0 +1,36 @@ +/* ==================================================================== + 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.ss.examples.formula; + +import org.apache.poi.ss.excelant.ExcelAntUserDefinedFunction; + +public class ExcelAntUserDefinedFunctionTestHelper extends + ExcelAntUserDefinedFunction { + + @Override + protected String getFunctionAlias() { + // TODO Auto-generated method stub + return super.getFunctionAlias(); + } + + @Override + protected String getClassName() { + // TODO Auto-generated method stub + return super.getClassName(); + } + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java b/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java new file mode 100755 index 00000000000..ad9851ae8f9 --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/examples/formula/TestExcelAntUserDefinedFunction.java @@ -0,0 +1,51 @@ +/* ==================================================================== + 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.ss.examples.formula; + +import junit.framework.TestCase; + +public class TestExcelAntUserDefinedFunction extends TestCase { + + private ExcelAntUserDefinedFunctionTestHelper fixture ; + + @Override + public void setUp() { + fixture = new ExcelAntUserDefinedFunctionTestHelper() ; + } + + public void testSetClassName() { + String className = "simple.class.name" ; + + fixture.setClassName( className ) ; + String value = fixture.getClassName() ; + + assertNotNull( value ) ; + assertEquals( className, value ) ; + } + + public void testSetFunction() { + String functionAlias = "alias" ; + + fixture.setFunctionAlias( functionAlias ) ; + + String alias = fixture.getFunctionAlias() ; + + assertNotNull( alias ) ; + assertEquals( functionAlias, alias ) ; + } + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java b/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java new file mode 100755 index 00000000000..5a30744cd0c --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/BuildFileTest.java @@ -0,0 +1,583 @@ +/* + * 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.ss.excelant; + +import junit.framework.TestCase; +import org.apache.tools.ant.*; + +import java.io.File; +import java.io.PrintStream; +import java.net.URL; + +/** + * A BuildFileTest is a TestCase which executes targets from an Ant buildfile + * for testing. + *

+ * This class provides a number of utility methods for particular build file + * tests which extend this class. + * + * @see + * http://svn.apache.org/repos/asf/ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java + */ +public abstract class BuildFileTest extends TestCase { + + protected Project project; + + private StringBuffer logBuffer; + private StringBuffer fullLogBuffer; + private StringBuffer outBuffer; + private StringBuffer errBuffer; + private BuildException buildException; + + /** + * Default constructor for the BuildFileTest object. + */ + public BuildFileTest() { + super(); + } + + /** + * Constructor for the BuildFileTest object. + * + * @param name string to pass up to TestCase constructor + */ + public BuildFileTest(String name) { + super(name); + } + + /** + * Automatically calls the target called "tearDown" + * from the build file tested if it exits. + *

+ * This allows to use Ant tasks directly in the build file + * to clean up after each test. Note that no "setUp" target + * is automatically called, since it's trivial to have a + * test target depend on it. + */ + protected void tearDown() throws Exception { + if (project == null) { + /* + * Maybe the BuildFileTest was subclassed and there is + * no initialized project. So we could avoid getting a + * NPE. + * If there is an initialized project getTargets() does + * not return null as it is initialized by an empty + * HashSet. + */ + return; + } + final String tearDown = "tearDown"; + if (project.getTargets().containsKey(tearDown)) { + project.executeTarget(tearDown); + } + } + + /** + * run a target, expect for any build exception + * + * @param target target to run + * @param cause information string to reader of report + */ + public void expectBuildException(String target, String cause) { + expectSpecificBuildException(target, cause, null); + } + + /** + * Assert that only the given message has been logged with a + * priority <= INFO when running the given target. + */ + public void expectLog(String target, String log) { + executeTarget(target); + String realLog = getLog(); + assertEquals(log, realLog); + } + + /** + * Assert that the given substring is in the log messages. + */ + public void assertLogContaining(String substring) { + String realLog = getLog(); + assertTrue("expecting log to contain \"" + substring + "\" log was \"" + + realLog + "\"", + realLog.indexOf(substring) >= 0); + } + + /** + * Assert that the given substring is not in the log messages. + */ + public void assertLogNotContaining(String substring) { + String realLog = getLog(); + assertFalse("didn't expect log to contain \"" + substring + "\" log was \"" + + realLog + "\"", + realLog.indexOf(substring) >= 0); + } + + /** + * Assert that the given substring is in the output messages. + * + * @since Ant1.7 + */ + public void assertOutputContaining(String substring) { + assertOutputContaining(null, substring); + } + + /** + * Assert that the given substring is in the output messages. + * + * @param message Print this message if the test fails. Defaults to + * a meaningful text if null is passed. + * @since Ant1.7 + */ + public void assertOutputContaining(String message, String substring) { + String realOutput = getOutput(); + String realMessage = (message != null) + ? message + : "expecting output to contain \"" + substring + "\" output was \"" + realOutput + "\""; + assertTrue(realMessage, realOutput.indexOf(substring) >= 0); + } + + /** + * Assert that the given substring is not in the output messages. + * + * @param message Print this message if the test fails. Defaults to + * a meaningful text if null is passed. + * @since Ant1.7 + */ + public void assertOutputNotContaining(String message, String substring) { + String realOutput = getOutput(); + String realMessage = (message != null) + ? message + : "expecting output to not contain \"" + substring + "\" output was \"" + realOutput + "\""; + assertFalse(realMessage, realOutput.indexOf(substring) >= 0); + } + + /** + * Assert that the given message has been logged with a priority <= INFO when running the + * given target. + */ + public void expectLogContaining(String target, String log) { + executeTarget(target); + assertLogContaining(log); + } + + /** + * Assert that the given message has not been logged with a + * priority <= INFO when running the given target. + */ + public void expectLogNotContaining(String target, String log) { + executeTarget(target); + assertLogNotContaining(log); + } + + /** + * Gets the log the BuildFileTest object. + * Only valid if configureProject() has been called. + * + * @return The log value + * @pre logBuffer!=null + */ + public String getLog() { + return logBuffer.toString(); + } + + /** + * Assert that the given message has been logged with a priority + * >= VERBOSE when running the given target. + */ + public void expectDebuglog(String target, String log) { + executeTarget(target); + String realLog = getFullLog(); + assertEquals(log, realLog); + } + + /** + * Assert that the given substring is in the log messages. + */ + public void assertDebuglogContaining(String substring) { + String realLog = getFullLog(); + assertTrue("expecting debug log to contain \"" + substring + + "\" log was \"" + + realLog + "\"", + realLog.indexOf(substring) >= 0); + } + + /** + * Gets the log the BuildFileTest object. + *

+ * Only valid if configureProject() has been called. + * + * @return The log value + * @pre fullLogBuffer!=null + */ + public String getFullLog() { + return fullLogBuffer.toString(); + } + + /** + * execute the target, verify output matches expectations + * + * @param target target to execute + * @param output output to look for + */ + public void expectOutput(String target, String output) { + executeTarget(target); + String realOutput = getOutput(); + assertEquals(output, realOutput.trim()); + } + + /** + * Executes the target, verify output matches expectations + * and that we got the named error at the end + * + * @param target target to execute + * @param output output to look for + * @param error Description of Parameter + */ + public void expectOutputAndError(String target, String output, String error) { + executeTarget(target); + String realOutput = getOutput(); + assertEquals(output, realOutput); + String realError = getError(); + assertEquals(error, realError); + } + + public String getOutput() { + return cleanBuffer(outBuffer); + } + + public String getError() { + return cleanBuffer(errBuffer); + } + + public BuildException getBuildException() { + return buildException; + } + + private String cleanBuffer(StringBuffer buffer) { + StringBuffer cleanedBuffer = new StringBuffer(); + for (int i = 0; i < buffer.length(); i++) { + char ch = buffer.charAt(i); + if (ch != '\r') { + cleanedBuffer.append(ch); + } + } + return cleanedBuffer.toString(); + } + + /** + * Sets up to run the named project + * + * @param filename name of project file to run + */ + public void configureProject(String filename) throws BuildException { + configureProject(filename, Project.MSG_DEBUG); + } + + /** + * Sets up to run the named project + * + * @param filename name of project file to run + */ + public void configureProject(String filename, int logLevel) + throws BuildException { + logBuffer = new StringBuffer(); + fullLogBuffer = new StringBuffer(); + project = new Project(); + project.init(); + File antFile = new File(System.getProperty("root"), filename); + project.setUserProperty("ant.file", antFile.getAbsolutePath()); + project.addBuildListener(new AntTestListener(logLevel)); + ProjectHelper.configureProject(project, antFile); + } + + /** + * Executes a target we have set up + * + * @param targetName target to run + * @pre configureProject has been called + */ + public void executeTarget(String targetName) { + PrintStream sysOut = System.out; + PrintStream sysErr = System.err; + try { + sysOut.flush(); + sysErr.flush(); + outBuffer = new StringBuffer(); + PrintStream out = new PrintStream(new AntOutputStream(outBuffer)); + System.setOut(out); + errBuffer = new StringBuffer(); + PrintStream err = new PrintStream(new AntOutputStream(errBuffer)); + System.setErr(err); + logBuffer = new StringBuffer(); + fullLogBuffer = new StringBuffer(); + buildException = null; + project.executeTarget(targetName); + } finally { + System.setOut(sysOut); + System.setErr(sysErr); + } + + } + + /** + * Get the project which has been configured for a test. + * + * @return the Project instance for this test. + */ + public Project getProject() { + return project; + } + + /** + * Gets the directory of the project. + * + * @return the base dir of the project + */ + public File getProjectDir() { + return project.getBaseDir(); + } + + /** + * Runs a target, wait for a build exception. + * + * @param target target to run + * @param cause information string to reader of report + * @param msg the message value of the build exception we are waiting + * for set to null for any build exception to be valid + */ + public void expectSpecificBuildException(String target, String cause, String msg) { + try { + executeTarget(target); + } catch (org.apache.tools.ant.BuildException ex) { + buildException = ex; + if ((null != msg) && (!ex.getMessage().equals(msg))) { + fail("Should throw BuildException because '" + cause + + "' with message '" + msg + + "' (actual message '" + ex.getMessage() + "' instead)"); + } + return; + } + fail("Should throw BuildException because: " + cause); + } + + /** + * run a target, expect an exception string + * containing the substring we look for (case sensitive match) + * + * @param target target to run + * @param cause information string to reader of report + * @param contains substring of the build exception to look for + */ + public void expectBuildExceptionContaining(String target, String cause, String contains) { + try { + executeTarget(target); + } catch (org.apache.tools.ant.BuildException ex) { + buildException = ex; + if ((null != contains) && (ex.getMessage().indexOf(contains) == -1)) { + fail("Should throw BuildException because '" + cause + "' with message containing '" + contains + "' (actual message '" + ex.getMessage() + "' instead)"); + } + return; + } + fail("Should throw BuildException because: " + cause); + } + + /** + * call a target, verify property is as expected + * + * @param target build file target + * @param property property name + * @param value expected value + */ + public void expectPropertySet(String target, String property, String value) { + executeTarget(target); + assertPropertyEquals(property, value); + } + + /** + * assert that a property equals a value; comparison is case sensitive. + * + * @param property property name + * @param value expected value + */ + public void assertPropertyEquals(String property, String value) { + String result = project.getProperty(property); + assertEquals("property " + property, value, result); + } + + /** + * assert that a property equals "true". + * + * @param property property name + */ + public void assertPropertySet(String property) { + assertPropertyEquals(property, "true"); + } + + /** + * assert that a property is null. + * + * @param property property name + */ + public void assertPropertyUnset(String property) { + String result = project.getProperty(property); + if (result != null) { + fail("Expected property " + property + + " to be unset, but it is set to the value: " + result); + } + } + + /** + * call a target, verify named property is "true". + * + * @param target build file target + * @param property property name + */ + public void expectPropertySet(String target, String property) { + expectPropertySet(target, property, "true"); + } + + /** + * Call a target, verify property is null. + * + * @param target build file target + * @param property property name + */ + public void expectPropertyUnset(String target, String property) { + expectPropertySet(target, property, null); + } + + /** + * Retrieve a resource from the caller classloader to avoid + * assuming a vm working directory. The resource path must be + * relative to the package name or absolute from the root path. + * + * @param resource the resource to retrieve its url. + * @throws junit.framework.AssertionFailedError + * if the resource is not found. + */ + public URL getResource(String resource) { + URL url = getClass().getResource(resource); + assertNotNull("Could not find resource :" + resource, url); + return url; + } + + /** + * an output stream which saves stuff to our buffer. + */ + protected static class AntOutputStream extends java.io.OutputStream { + private StringBuffer buffer; + + public AntOutputStream(StringBuffer buffer) { + this.buffer = buffer; + } + + public void write(int b) { + buffer.append((char) b); + } + } + + /** + * Our own personal build listener. + */ + private class AntTestListener implements BuildListener { + private int logLevel; + + /** + * Constructs a test listener which will ignore log events + * above the given level. + */ + public AntTestListener(int logLevel) { + this.logLevel = logLevel; + } + + /** + * Fired before any targets are started. + */ + public void buildStarted(BuildEvent event) { + } + + /** + * Fired after the last target has finished. This event + * will still be thrown if an error occurred during the build. + * + * @see BuildEvent#getException() + */ + public void buildFinished(BuildEvent event) { + } + + /** + * Fired when a target is started. + * + * @see BuildEvent#getTarget() + */ + public void targetStarted(BuildEvent event) { + //System.out.println("targetStarted " + event.getTarget().getName()); + } + + /** + * Fired when a target has finished. This event will + * still be thrown if an error occurred during the build. + * + * @see BuildEvent#getException() + */ + public void targetFinished(BuildEvent event) { + //System.out.println("targetFinished " + event.getTarget().getName()); + } + + /** + * Fired when a task is started. + * + * @see BuildEvent#getTask() + */ + public void taskStarted(BuildEvent event) { + //System.out.println("taskStarted " + event.getTask().getTaskName()); + } + + /** + * Fired when a task has finished. This event will still + * be throw if an error occurred during the build. + * + * @see BuildEvent#getException() + */ + public void taskFinished(BuildEvent event) { + //System.out.println("taskFinished " + event.getTask().getTaskName()); + } + + /** + * Fired whenever a message is logged. + * + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + public void messageLogged(BuildEvent event) { + if (event.getPriority() > logLevel) { + // ignore event + return; + } + + if (event.getPriority() == Project.MSG_INFO || + event.getPriority() == Project.MSG_WARN || + event.getPriority() == Project.MSG_ERR) { + logBuffer.append(event.getMessage()); + } + fullLogBuffer.append(event.getMessage()); + } + } + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java b/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java new file mode 100644 index 00000000000..4d8f0d5a81d --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/TestBuildFile.java @@ -0,0 +1,74 @@ +/* + * 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.ss.excelant; + +/** + * JUnit test for the ExcelAnt tasks. + * Leverages Ant's test framework. + * + * @see + * http://svn.apache.org/repos/asf/ant/core/trunk/src/tests/junit/org/apache/tools/ant/BuildFileTest.java + */ +public class TestBuildFile extends BuildFileTest { + + public void setUp() { + configureProject("src/excelant/testcases/org/apache/poi/ss/excelant/tests.xml"); + } + + public void testMissingFilename() { + expectSpecificBuildException("test-nofile", "required argument not specified", + "fileName attribute must be set!"); + } + + public void testFileNotFound() { + expectSpecificBuildException("test-filenotfound", "required argument not specified", + "Cannot load file invalid.xls. Make sure the path and file permissions are correct."); + } + + public void testEvaluate() { + executeTarget("test-evaluate"); + assertLogContaining("Using input file: test-data/spreadsheet/excelant.xls"); + assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4."); + } + + public void testPrecision() { + executeTarget("test-precision"); + + assertLogContaining("Using input file: test-data/spreadsheet/excelant.xls"); + assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4. " + + "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-4"); + assertLogContaining("Succeeded when evaluating 'MortgageCalculator'!$B$4. " + + "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-5"); + assertLogContaining("Failed to evaluate cell 'MortgageCalculator'!$B$4. " + + "It evaluated to 2285.5761494145563 when the value of 2285.576149 with precision of 1.0E-10 was expected."); + assertLogContaining("2/3 tests passed"); + } + + public void testPassOnError() { + executeTarget("test-passonerror"); + } + + public void testFailOnError() { + expectBuildException("test-failonerror", "fail on error"); + } + + public void testUdf() { + executeTarget("test-udf"); + assertLogContaining("1/1 tests passed"); + } +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java new file mode 100755 index 00000000000..0c6f9cbf835 --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntPrecision.java @@ -0,0 +1,48 @@ +/* ==================================================================== + 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.ss.excelant; + +import junit.framework.TestCase; + +public class TestExcelAntPrecision extends TestCase { + + private ExcelAntPrecision fixture ; + + @Override + public void setUp() { + fixture = new ExcelAntPrecision() ; + } + + @Override + public void tearDown() { + fixture = null ; + } + + public void testVerifyPrecision() { + + double value = 1.0E-1 ; + + fixture.setValue( value ) ; + + double result = fixture.getValue() ; + + assertTrue( result > 0 ) ; + + assertEquals( value, result, 0.0 ) ; + } + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java new file mode 100755 index 00000000000..cfba01b585a --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSet.java @@ -0,0 +1,64 @@ +/* ==================================================================== + 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.ss.excelant; + +import junit.framework.TestCase; + +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory; + +public class TestExcelAntSet extends TestCase { + + + // This is abstract in nature, so we'll use a + // concrete instance to test the set methods. + private ExcelAntSet fixture ; + + private final String mortgageCalculatorFileName = + "test-data/spreadsheet/mortgage-calculation.xls" ; + + + @Override + public void setUp() { + fixture = new ExcelAntSetDoubleCell() ; + } + + @Override + public void tearDown() { + fixture = null ; + } + + public void testSetter() { + String cell = "simpleCellRef!$F$1" ; + + fixture.setCell( cell ) ; + + String cellStr = fixture.getCell() ; + + assertNotNull( cellStr ) ; + assertEquals( cell, cellStr ) ; + } + + public void testSetWorkbookUtil() { + ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance( + mortgageCalculatorFileName ) ; + + assertNotNull( util ) ; + + fixture.setWorkbookUtil( util ) ; + } +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java new file mode 100755 index 00000000000..1b9e527e07d --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/TestExcelAntSetDoubleCell.java @@ -0,0 +1,66 @@ +/* ==================================================================== + 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.ss.excelant; + +import junit.framework.TestCase; + +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtil; +import org.apache.poi.ss.excelant.util.ExcelAntWorkbookUtilFactory; + +public class TestExcelAntSetDoubleCell extends TestCase { + + private ExcelAntSetDoubleCell fixture ; + + private final String mortgageCalculatorFileName = + "test-data/spreadsheet/mortgage-calculation.xls" ; + + private ExcelAntWorkbookUtil util ; + + @Override + public void setUp() { + fixture = new ExcelAntSetDoubleCell() ; + util = ExcelAntWorkbookUtilFactory.getInstance( + mortgageCalculatorFileName ) ; + fixture.setWorkbookUtil( util ) ; + } + + @Override + public void tearDown() { + fixture = null ; + } + + public void testSetDouble() { + String cellId = "'Sheet3'!$A$1" ; + double testValue = 1.1 ; + + fixture.setCell( cellId ) ; + fixture.setValue( testValue ) ; + + double value = fixture.getCellValue() ; + + assertTrue( value > 0 ) ; + assertEquals( testValue, value, 0.0 ) ; + + fixture.execute() ; + + double setValue = util.getCellAsDouble( cellId ) ; + + assertEquals( setValue, testValue, 0.0 ) ; + } + + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java b/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java new file mode 100755 index 00000000000..cf7538c20ba --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/util/ExcelAntWorkbookUtilTestHelper.java @@ -0,0 +1,55 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import org.apache.poi.ss.formula.functions.FreeRefFunction; +import org.apache.poi.ss.formula.udf.UDFFinder; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * A helper class to allow testing of protected methods and constructors. + * + * @author jsvede + * + */ +public class ExcelAntWorkbookUtilTestHelper extends ExcelAntWorkbookUtil { + + public ExcelAntWorkbookUtilTestHelper(String fName) { + super(fName); + // TODO Auto-generated constructor stub + } + + public ExcelAntWorkbookUtilTestHelper(Workbook wb) { + super(wb); + // TODO Auto-generated constructor stub + } + + @Override + public UDFFinder getFunctions() { + // TODO Auto-generated method stub + return super.getFunctions(); + } + + @Override + public FormulaEvaluator getEvaluator(String excelFileName) { + // TODO Auto-generated method stub + return super.getEvaluator(excelFileName); + } + + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java new file mode 100755 index 00000000000..833dea67a69 --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntEvaluationResult.java @@ -0,0 +1,67 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import junit.framework.TestCase; + +public class TestExcelAntEvaluationResult extends TestCase { + + private ExcelAntEvaluationResult fixture ; + + private boolean completedWithError = false ; + private boolean passed = false ; + private double retValue = 1.1 ; + private String errMessage = "error message" ; + private double delta = 2.2 ; + private String cellId = "testCell!$F$1" ; + + public void setUp() { + fixture = new ExcelAntEvaluationResult( completedWithError, + passed, + retValue, + errMessage, + delta, + cellId ) ; + } + + public void tearDown() { + fixture = null ; + } + + public void testCompletedWithErrorMessage() { + String errMsg = fixture.getErrorMessage() ; + assertNotNull( errMsg ) ; + assertEquals( errMsg, errMessage ) ; + } + + public void testPassed() { + boolean passedValue = fixture.didTestPass() ; + assertEquals( passedValue, passed ) ; + } + + public void testDelta() { + double deltaValue = fixture.getDelta() ; + assertEquals(deltaValue, delta, 0.0 ) ; + } + + public void testCellId() { + String cellIdValue = fixture.getCellName() ; + assertNotNull( cellIdValue ) ; + assertEquals( cellIdValue, cellId ) ; + } + +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java new file mode 100755 index 00000000000..5b0d3e8027d --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtil.java @@ -0,0 +1,140 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import java.util.ArrayList; + +import junit.framework.TestCase; + +import org.apache.poi.ss.examples.formula.CalculateMortgage; +import org.apache.poi.ss.formula.udf.UDFFinder; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Workbook; + +public class TestExcelAntWorkbookUtil extends TestCase { + + private final String mortgageCalculatorFileName = + "test-data/spreadsheet/excelant.xls" ; + private ExcelAntWorkbookUtilTestHelper fixture ; + + + public void tearDown() { + fixture = null ; + } + + public void testStringConstructor() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + assertNotNull( fixture ) ; + + } + + public void testAddFunction() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + assertNotNull( fixture ) ; + + fixture.addFunction("h2_ZFactor", new CalculateMortgage() ) ; + + UDFFinder functions = fixture.getFunctions() ; + + assertNotNull( functions ) ; + } + + public void testGetWorkbook() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + assertNotNull( fixture ) ; + + Workbook workbook = fixture.getWorkbook() ; + + assertNotNull( workbook ) ; + } + + public void testFileName() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + assertNotNull( fixture ) ; + + String fileName = fixture.getFileName() ; + + assertNotNull( fileName ) ; + + assertEquals( mortgageCalculatorFileName, fileName ) ; + + } + + public void testGetEvaluator() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + FormulaEvaluator evaluator = fixture.getEvaluator( + mortgageCalculatorFileName ) ; + + assertNotNull( evaluator ) ; + + + } + + public void testEvaluateCell() { + String cell = "'MortgageCalculator'!B4" ; + double expectedValue = 790.79 ; + double precision = 0.1 ; + + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + ExcelAntEvaluationResult result = fixture.evaluateCell( cell, + expectedValue, + precision ) ; + + System.out.println( result ) ; + + assertTrue( result.didTestPass() ) ; + } + + public void testGetSheets() { + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + ArrayList sheets = fixture.getSheets() ; + + assertNotNull( sheets ) ; + assertEquals( sheets.size(), 3 ) ; + } + + public void testSetString() { + String cell = "'MortgageCalculator'!C14" ; + String cellValue = "testString" ; + + fixture = new ExcelAntWorkbookUtilTestHelper( + mortgageCalculatorFileName ) ; + + fixture.setStringValue( cell, cellValue ) ; + + String value = fixture.getCellAsString( cell ) ; + + assertNotNull( value ) ; + + assertEquals( cellValue, value ) ; + + } +} diff --git a/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java new file mode 100755 index 00000000000..79c898a0349 --- /dev/null +++ b/src/excelant/testcases/org/apache/poi/ss/excelant/util/TestExcelAntWorkbookUtilFactory.java @@ -0,0 +1,69 @@ +/* ==================================================================== + 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.ss.excelant.util; + +import junit.framework.TestCase; + + +/** + * Tests for the ExcelAntWorbookUtilFactory. + * + * @author Jon Svede ( jon [at] loquatic [dot] com ) + * @author Brian Bush ( brian [dot] bush [at] nrel [dot] gov ) + * + */ +public class TestExcelAntWorkbookUtilFactory extends TestCase{ + + private final String mortgageCalculatorWorkbookFile = + "test-data/spreadsheet/mortgage-calculation.xls" ; + + + /** + * Simple test to determine if the factory properly returns an non-null + * instance of the ExcelAntWorkbookUtil class. + */ + public void testGetNewWorkbookUtilInstance() { + + ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance( + mortgageCalculatorWorkbookFile ) ; + + assertNotNull( util ) ; + + } + + + /** + * Test whether or not the factory will properly return the same reference + * to an ExcelAnt WorkbookUtil when two different Strings, that point to + * the same resource, are passed in. + */ + public void testVerifyEquivalence() { + String sameFileName = "test-data/spreadsheet/mortgage-calculation.xls" ; + + ExcelAntWorkbookUtil util = ExcelAntWorkbookUtilFactory.getInstance( + mortgageCalculatorWorkbookFile ) ; + + ExcelAntWorkbookUtil util2 = ExcelAntWorkbookUtilFactory.getInstance( + sameFileName ) ; + + assertNotNull( util ) ; + assertNotNull( util2 ) ; + + assertEquals( util, util2 ) ; + } + +} diff --git a/test-data/spreadsheet/excelant.xls b/test-data/spreadsheet/excelant.xls new file mode 100644 index 0000000000000000000000000000000000000000..e41edfbfd219f7bf212be379e76b8dc483edf07a GIT binary patch literal 37888 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 literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/mortgage-calculation.xls b/test-data/spreadsheet/mortgage-calculation.xls new file mode 100644 index 0000000000000000000000000000000000000000..92e5779b5e8aeb810c16628002cf9e29ee3161bf GIT binary patch literal 37376 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