本文转自:http://poi.apache.org/spreadsheet/user-defined-functions.html

How to Create and Use User Defined Functions

Description

This document describes the User Defined Functions within POI.            User defined functions allow you to take code that is written in VBA            and re-write in Java and use within POI. Consider the following example.

An Example

Suppose you are given a spreadsheet that can calculate the principal and interest        payments for a mortgage.  The user enters the principal loan amount, the interest rate        and the term of the loan.  The Excel spreadsheet does the rest.

When you actually look at the workbook you discover that rather than having        the formula in a cell it has been written as VBA function.  You review the          function and determine that it could be written in Java:

If we write a small program to try to evaluate this cell, we'll fail.  Consider this source code:

import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
import java.io.IOException ; import org.apache.poi.openxml4j.exceptions.InvalidFormatException ;
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.Cell ;
import org.apache.poi.ss.usermodel.CellValue ;
import org.apache.poi.ss.usermodel.Row ;
import org.apache.poi.ss.usermodel.Sheet ;
import org.apache.poi.ss.usermodel.Workbook ;
import org.apache.poi.ss.usermodel.WorkbookFactory ;
import org.apache.poi.ss.util.CellReference ; public class Evaluator { public static void main( String[] args ) { System.out.println( "fileName: " + args[0] ) ;
System.out.println( "cell: " + args[1] ) ; File workbookFile = new File( args[0] ) ; try {
FileInputStream fis = new FileInputStream(workbookFile);
Workbook workbook = WorkbookFactory.create(fis); FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); CellReference cr = new CellReference( args[1] ) ;
String sheetName = cr.getSheetName() ;
Sheet sheet = workbook.getSheet( sheetName ) ;
int rowIdx = cr.getRow() ;
int colIdx = cr.getCol() ;
Row row = sheet.getRow( rowIdx ) ;
Cell cell = row.getCell( colIdx ) ; CellValue value = evaluator.evaluate( cell ) ; System.out.println("returns value: " + value ) ; } catch( FileNotFoundException e ) {
e.printStackTrace();
} catch( InvalidFormatException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
}
}

If you run this code, you're likely to get the following error:

Exception in thread "main" org.apache.poi.ss.formula.eval.NotImplementedException: Error evaluating cell Sheet1!B4
at org.apache.poi.ss.formula.WorkbookEvaluator.addExceptionInfo(WorkbookEvaluator.java:321)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateAny(WorkbookEvaluator.java:288)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluate(WorkbookEvaluator.java:221)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateFormulaCellValue(HSSFFormulaEvaluator.java:320)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluate(HSSFFormulaEvaluator.java:182)
at poi.tests.Evaluator.main(Evaluator.java:61)
Caused by: org.apache.poi.ss.formula.eval.NotImplementedException: calculatePayment
at org.apache.poi.ss.formula.UserDefinedFunction.evaluate(UserDefinedFunction.java:59)
at org.apache.poi.ss.formula.OperationEvaluatorFactory.evaluate(OperationEvaluatorFactory.java:129)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateFormula(WorkbookEvaluator.java:456)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateAny(WorkbookEvaluator.java:279)
... 4 more

How would we make it so POI can use this sheet?

Defining Your Function

To 'convert' this code to Java and make it available to POI you need to implement        a FreeRefFunction instance.  FreeRefFunction is an interface in the org.apache.poi.ss.formula.functions         package.  This interface defines one method, evaluate(ValueEval[] args, OperationEvaluationContext ec),        which is how you will receive the argument values from POI.

The evaluate() method as defined above is where you will convert the ValueEval instances to the         proper number types.  The following code snippet shows you how to get your values:

public class CalculateMortgage implements FreeRefFunction {

@Override
public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
if (args.length != 3) {
return ErrorEval.VALUE_INVALID;
} double principal, rate, years, result;
try {
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() ) ; principal = OperandResolver.coerceValueToDouble( v1 ) ;
rate = OperandResolver.coerceValueToDouble( v2 ) ;
years = OperandResolver.coerceValueToDouble( v3 ) ;

The first thing we do is check the number of arguments being passed since there is no sense     in attempting to go further if you are missing critical information.

Next we declare our variables, in our case we need variables for:

  • principal - the amount of the loan
  • rate - the interest rate as a decimal
  • years - the length of the loan in years
  • result - the result of the calculation

Next, we use the OperandResolver to convert the ValueEval instances to doubles, though not directly.       First we start by getting discreet values.  Using the OperandResolver.getSingleValue() method     we retrieve each of the values passed in by the cell in the spreadsheet.  Next, we use the     OperandResolver again to convert the ValueEval instances to doubles, in this case.  This     class has other methods of coercion for gettings Strings, ints and booleans.  Now that we've      got our primitive values we can move on to calculating the value.

As shown previously, we have the VBA source.  We need to add code to our class to calculate      the payment.  To do this you could simply add it to the method we've already created but I've     chosen to add it as its own method.  Add the following method:

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 ;
}

The biggest change necessary is related to the exponents; Java doesn't have a notation for this     so we had to add calls to Math.pow().  Now we need to add this call to our previous method:

     	 result = calculateMortgagePayment( principal, rate, years ) ;
     		

Having done that, the last things we need to do are to check to make sure we didn't get a bad result and,     if not, we need to return the value. Add the following code to the class:

private void checkValue(double result) throws EvaluationException {
if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
}

Then add a line of code to our evaluate method to call this new static method, complete our try/catch and return the value:

        checkValue(result);

    } catch (EvaluationException e) {
e.printStackTrace() ;
return e.getErrorEval();
} return new NumberEval( result ) ;

So the whole class would be as follows:

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 function to calculate principal and interest.
*
* @author Jon Svede
*
*/
public class CalculateMortgage implements FreeRefFunction { @Override
public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
if (args.length != 3) {
return ErrorEval.VALUE_INVALID;
} double principal, rate, years, result;
try {
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() ) ; principal = OperandResolver.coerceValueToDouble( v1 ) ;
rate = OperandResolver.coerceValueToDouble( v2 ) ;
years = OperandResolver.coerceValueToDouble( v3 ) ; result = calculateMortgagePayment( principal, rate, years ) ; checkValue(result); } catch (EvaluationException e) {
e.printStackTrace() ;
return e.getErrorEval();
} return new NumberEval( result ) ;
} public double calculateMortgagePayment( double p, double r, double y ) {
double i = r / 12 ;
double n = y * 12 ; //M = P [ i(1 + i)n ] / [ (1 + i)n - 1]
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 <tt>result</tt> is <tt>NaN</> or <tt>Infinity</tt>
*/
static final void checkValue(double result) throws EvaluationException {
if (Double.isNaN(result) || Double.isInfinite(result)) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
} }

Great!  Now we need to go back to our original program that failed to evaluate our cell and add code that will allow it run our new Java code.

Registering Your Function

Now we need to register our function in the Workbook, so that the Formula Evaluator can resolve the name "calculatePayment" and map it to the actual implementation (CalculateMortgage). This is done using the UDFFinder object.  The UDFFinder manages FreeRefFunctions which are our analogy for the VBA code.  We need to create a UDFFinder. There are     a few things we need to know in order to do this:

  • The name of the function in the VBA code (in our case it is calculatePayment)
  • The Class name of our FreeRefFunction

UDFFinder is actually an interface, so we need to use an actual implementation of this interface.  Therefore we use the org.apache.poi.ss.formula.udf.DefaultUDFFinder class.  If you refer to the Javadocs you'll see that this class expects to get two arrays, one     containing the alias and the other containing an instance of the class that will represent that alias.  In our case our alias will be calculatePayment      and our class instance will be of the  CalculateMortgage type.  This class needs to be available at compile and runtime.  Be sure to keep these arrays     well organized because you'll run into problems if these arrays are of different sizes or the alias aren't in the same relative position in their respective     arrays.  Add the following code:

String[] functionNames = { "calculatePayment" } ;
FreeRefFunction[] functionImpls = { new CalculateMortgage() } ; UDFFinder udfs = new DefaultUDFFinder( functionNames, functionImpls ) ;
UDFFinder udfToolpack = new AggregatingUDFFinder( udfs ) ;

Now we have our UDFFinder instance and we've created the AggregatingUDFFinder instance.  The last step is to pass this to our Workbook:

workbook.addToolPack(udfToolpack);
     	  	

So now the whole class will look like this:

import java.io.File ;
import java.io.FileInputStream ;
import java.io.FileNotFoundException ;
import java.io.IOException ; import org.apache.poi.openxml4j.exceptions.InvalidFormatException ;
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.Cell ;
import org.apache.poi.ss.usermodel.CellValue ;
import org.apache.poi.ss.usermodel.Row ;
import org.apache.poi.ss.usermodel.Sheet ;
import org.apache.poi.ss.usermodel.Workbook ;
import org.apache.poi.ss.usermodel.WorkbookFactory ;
import org.apache.poi.ss.util.CellReference ; public class Evaluator { public static void main( String[] args ) { System.out.println( "fileName: " + args[0] ) ;
System.out.println( "cell: " + args[1] ) ; File workbookFile = new File( args[0] ) ; try {
FileInputStream fis = new FileInputStream(workbookFile);
Workbook workbook = WorkbookFactory.create(fis); String[] functionNames = { "calculatePayment" } ;
FreeRefFunction[] functionImpls = { new CalculateMortgage() } ; UDFFinder udfs = new DefaultUDFFinder( functionNames, functionImpls ) ;
UDFFinder udfToolpack = new AggregatingUDFFinder( udfs ) ; workbook.addToolPack(udfToolpack); FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); CellReference cr = new CellReference( args[1] ) ;
String sheetName = cr.getSheetName() ;
Sheet sheet = workbook.getSheet( sheetName ) ;
int rowIdx = cr.getRow() ;
int colIdx = cr.getCol() ;
Row row = sheet.getRow( rowIdx ) ;
Cell cell = row.getCell( colIdx ) ; CellValue value = evaluator.evaluate( cell ) ; System.out.println("returns value: " + value ) ; } catch( FileNotFoundException e ) {
e.printStackTrace();
} catch( InvalidFormatException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
}
}

Now that our evaluator is aware of the UDFFinder which in turn is aware of our FreeRefFunction, we're ready to re-run our example:

Evaluator mortgage-calculation.xls Sheet1!B4

which prints the following output in the console:

fileName: mortgage-calculation.xls
cell: Sheet1!B4
returns value: org.apache.poi.ss.usermodel.CellValue [790.7936267415464]

That is it!  Now you can create Java code and register it, allowing your POI based appliction to run spreadsheets that previously were inaccessible.

This example can be found in the src/examples/src/org/apache/poi/ss/examples/formula folder in the source.

[转]POI : How to Create and Use User Defined Functions的更多相关文章

  1. Read / Write Excel file in Java using Apache POI

    Read / Write Excel file in Java using Apache POI 2014-04-18 BY DINESH LEAVE A COMMENT About a year o ...

  2. [Hive - LanguageManual] Create/Drop/Alter -View、 Index 、 Function

    Create/Drop/Alter View Create View Drop View Alter View Properties Alter View As Select Version info ...

  3. PostgreSQL 之 CREATE FUNCTION

    官方文档 语法: CREATE [ OR REPLACE ] FUNCTION name ( [ [ argmode ] [ argname ] argtype [ { DEFAULT | = } d ...

  4. MySql批处理的小窍门:排行榜类数据生成

    MySql批处理的小窍门:排行榜类数据生成 最近在做新版本的开发,其中涉及到排行榜的批量预生成,在此分享给大家. 关键点 名次的计算(不考虑用游标) 单榜单查询 对于排行榜这种类型的数据,当只查一个排 ...

  5. 超级顽固的流方式读取doc,docx乱码问题

    因为工作中需要一个把doc或者docx的office文档内容,需要读取出来,并且也没展示功能.代码中第一考虑可能就是通过读取流方式,结果写了以后,各种乱码,百科的解决方案也是千奇百怪,第一点:可能是文 ...

  6. Phoenix综述(史上最全Phoenix中文文档)

    个人主页:http://www.linbingdong.com 简书地址:http://www.jianshu.com/users/6cb45a00b49c/latest_articles 网上关于P ...

  7. 【repost】JavaScript Scoping and Hoisting

    JavaScript Scoping and Hoisting Do you know what value will be alerted if the following is executed ...

  8. PHP7函数大全(4553个函数)

    转载来自: http://www.infocool.net/kb/PHP/201607/168683.html a 函数 说明 abs 绝对值 acos 反余弦 acosh 反双曲余弦 addcsla ...

  9. 有关binlog的那点事(mysql5.7.13)

    binlog作为mysql中最重要的日志之一,能实现异常恢复以及主从复制. 我们主要讨论的是主从复制中的binlog,这里将以mysql5.7.13的源码为主要依据来分析binlog. 在主从复制中, ...

随机推荐

  1. 清理html中空白符/空格/换行在行内元素中产生的间距

    问题:行内元素之间产生间隔 原因:换行符,Tab制表符,空格产生间隔 解决方法: 1.行内元素写成一行 2.设置font-size为0px 把父级文本设置为0px; 再为需要显示文字的行内元素设置文字 ...

  2. hdu-5728 PowMod(数论)

    题目链接: PowMod Time Limit: 3000/1500 MS (Java/Others)     Memory Limit: 262144/262144 K (Java/Others) ...

  3. Opencv— — mix channels

    // define head function #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGORITHM_H_INCLUDED #include < ...

  4. Swift引用计数器

    ARC概述 和4.2+版本的Xcode对OC的支持一样,Swift也是使用ARC来管理内存,文档是这么描述的: Swift uses Automatic Reference Counting(ARC) ...

  5. JQ对象和原生DOM对象

    相同点:两者本质上都是DOM元素. 不同点:JQ对象是在原生DOM对象上进行了一次封装,使开发人员使用起来更简洁.高效. 两者之间用法也完全不同,很说初学者经常混淆. 其实区分两者并不难, 1.语法不 ...

  6. C++模板之隐式实例化、显示实例化、隐式调用、显示调用和模板特化详解

    模板的实例化指函数模板(类模板)生成模板函数(模板类)的过程.对于函数模板而言,模板实例化之后,会生成一个真正的函数.而类模板经过实例化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始 ...

  7. iOS设备屏幕分辨率分布

    iOS设备屏幕分辨率比较单一,960*640是iPhone4和4s的分辨率,占比67.4%;1024*768是iPad1和iPad2的分辨率,占比22.5%;480*320是iPhone3和3gs的分 ...

  8. CodeForces 1103E. Radix sum

    题目简述:对任意两个(正)十进制数$a = \overline{a_{k-1}\dots a_1a_0}$和$b = \overline{b_{k-1}\dots b_1b_0}$,定义其[十进制按位 ...

  9. idea2018.2.5版本使用之背景色

    idea 背景色: 写代码区换眼色豆沙色:

  10. HDOJ-1391

    Number Steps Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Tota ...