简体   繁体   English

如何使用 ZB713AE04A02A810D6F33DD956F42794Z POI 公式中的 excel 文本 function "NUMBERVALUE"

[英]How to use excel text function "NUMBERVALUE" from Apache POI formula

I am using Apache POI to evaluate a formula cell that has a text function "NUMBERVALUE", then I got an exception Caused by: org.apache.poi.ss.formula.eval.NotImplementedFunctionException: _xlfn.NUMBERVALUE I am using Apache POI to evaluate a formula cell that has a text function "NUMBERVALUE", then I got an exception Caused by: org.apache.poi.ss.formula.eval.NotImplementedFunctionException: _xlfn.NUMBERVALUE

Interesting enough, when I try to regist this function WorkbookEvaluator.registerFunction("NUMBERVALUE", new NumberRValue());有趣的是,当我尝试注册这个 function WorkbookEvaluator.registerFunction("NUMBERVALUE", new NumberRValue()); it gives me another error: java.lang.IllegalArgumentException: NUMBERVALUE is not a function from the Excel Analysis Toolpack.它给了我另一个错误: java.lang.IllegalArgumentException: NUMBERVALUE is not a function 来自 Excel 分析工具包。

I also tried to use the user-defined functions by implementing FreeRefFunction, it backs to the first error again.我还尝试通过实现 FreeRefFunction 来使用用户定义的函数,它又回到了第一个错误。

Caused by: org.apache.poi.ss.formula.eval.NotImplementedFunctionException: _xlfn.NUMBERVALUE引起:org.apache.poi.ss.formula.eval.NotImplementedFunctionException: _xlfn.NUMBERVALUE

How to implement a text function in Apache POI?如何在 Apache POI 中实现文本 function?

WorkbookEvaluator.registerFunction only works for functions apache poi at least knows per name. WorkbookEvaluator.registerFunction仅适用于函数apache poi至少知道每个名称。 This are all functions listed through:这是通过以下方式列出的所有功能:

java.util.Collection<String> unsupportedFuncs = org.apache.poi.ss.formula.WorkbookEvaluator.getNotSupportedFunctionNames();
System.out.println(unsupportedFuncs);

All the listed functions can be registered using WorkbookEvaluator.registerFunction either as org.apache.poi.hssf.record.formula.functions.Function , when in org.apache.poi.hssf.record.formula.atp.AnalysisToolPak or else as org.apache.poi.hssf.record.formula.functions.FreeRefFunction if not. All the listed functions can be registered using WorkbookEvaluator.registerFunction either as org.apache.poi.hssf.record.formula.functions.Function , when in org.apache.poi.hssf.record.formula.atp.AnalysisToolPak or else as org.apache.poi.hssf.record.formula.functions.FreeRefFunction如果没有。

But NUMBERVALUE function is not in this list.NUMBERVALUE function 不在此列表中。 So this only can be added as a user defined function.所以这只能作为用户定义的 function 添加。 See User Defined Functions of apache poi 's documentation.请参阅apache poi文档的用户定义函数

The function must implement org.apache.poi.ss.formula.functions.FreeRefFunction and must be registered in UDF toolpack of the Workbook . function 必须实现org.apache.poi.ss.formula.functions.FreeRefFunction并且必须在Workbook的 UDF 工具包中注册。

Following complete example shows a basic implementation of _xlfn.NUMBERVALUE .以下完整示例显示了_xlfn.NUMBERVALUE的基本实现。 Implementation is done using the description in NUMBERVALUE function .使用NUMBERVALUE function中的描述完成实现。 It is a working draft until now and might need to be improved to better fulfill compatibility to Excel's own results.到目前为止,它还是一个工作草案,可能需要改进以更好地实现与 Excel 自身结果的兼容性。

import java.io.FileInputStream;
import java.util.Locale;
import org.apache.poi.ss.usermodel.*;
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.util.LocaleUtil;

public class EvaluateNUMBERVALUE {

 public static void main( String[] args ) throws Exception {
  Workbook workbook = WorkbookFactory.create(new FileInputStream("./ExcelWithNUMBERVALUE.xlsx"));    
  
  String[] functionNames = { "_xlfn.NUMBERVALUE" } ;
  FreeRefFunction[] functionImpls = { new NumberValue() } ;
  UDFFinder udfs = new DefaultUDFFinder( functionNames, functionImpls ) ;
  UDFFinder udfToolpack = new AggregatingUDFFinder( udfs ) ;    
  workbook.addToolPack(udfToolpack);
  
  LocaleUtil.setUserLocale(Locale.US);
  FormulaEvaluator formulaEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
  
  DataFormatter dataFormatter = new DataFormatter();
  
  for (Sheet sheet: workbook) {
   for (Row row : sheet) {
    for (Cell cell : row) {
     String cellValue = dataFormatter.formatCellValue(cell, formulaEvaluator);
     System.out.println(cellValue);
    }
   }   
  }
 }
}

Class NumberValue used in above code: Class NumberValue在上面的代码中使用:

import java.lang.NumberFormatException;
import java.util.Locale;
import java.text.DecimalFormatSymbols;
import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.eval.NumberEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.util.LocaleUtil;


public final class NumberValue implements FreeRefFunction  {
 @Override
 public ValueEval evaluate( ValueEval[] args, OperationEvaluationContext ec ) {
     
  Locale locale = LocaleUtil.getUserLocale();
  DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(locale);
  
  String text = null;
  //If the Decimal_separator and Group_separator arguments are not specified, separators from the current locale are used.
  String decSep = String.valueOf(decimalFormatSymbols.getDecimalSeparator());
  String groupSep = String.valueOf(decimalFormatSymbols.getGroupingSeparator());

  Double result = Double.NaN;
  ValueEval v1 = null;
  ValueEval v2 = null;
  ValueEval v3 = null;
  
  try {
   if (args.length == 1) {  
    v1 = OperandResolver.getSingleValue( args[0], ec.getRowIndex(), ec.getColumnIndex());
    text = OperandResolver.coerceValueToString(v1);
   } else if (args.length == 2) { 
    v1 = OperandResolver.getSingleValue( args[0], ec.getRowIndex(), ec.getColumnIndex());
    v2 = OperandResolver.getSingleValue( args[1], ec.getRowIndex(), ec.getColumnIndex());
    text = OperandResolver.coerceValueToString(v1);
    decSep = OperandResolver.coerceValueToString(v2).substring(0, 1); //If multiple characters are used in the Decimal_separator or Group_separator arguments, only the first character is used.
   } else if (args.length == 3) { 
    v1 = OperandResolver.getSingleValue( args[0], ec.getRowIndex(), ec.getColumnIndex());
    v2 = OperandResolver.getSingleValue( args[1], ec.getRowIndex(), ec.getColumnIndex());
    v3 = OperandResolver.getSingleValue( args[2], ec.getRowIndex(), ec.getColumnIndex());
    text = OperandResolver.coerceValueToString(v1);
    decSep = OperandResolver.coerceValueToString(v2).substring(0, 1); //If multiple characters are used in the Decimal_separator or Group_separator arguments, only the first character is used.
    groupSep = OperandResolver.coerceValueToString(v3).substring(0, 1); //If multiple characters are used in the Decimal_separator or Group_separator arguments, only the first character is used.
   }
  } catch (EvaluationException e) {
   e.printStackTrace() ;
   return e.getErrorEval();
  }  
  
  if("".equals(text)) text = "0"; //If an empty string ("") is specified as the Text argument, the result is 0.
  text = text.replace(" ", ""); //Empty spaces in the Text argument are ignored, even in the middle of the argument. For example, " 3 000 " is returned as 3000.
  String[] parts = text.split("["+decSep+"]");
  String sigPart = "";
  String decPart = "";
  if (parts.length > 2) return ErrorEval.VALUE_INVALID; //If a decimal separator is used more than once in the Text argument, NUMBERVALUE returns the #VALUE! error value.
  if (parts.length > 1) {
   sigPart = parts[0];
   decPart = parts[1];
   if (decPart.contains(groupSep)) return ErrorEval.VALUE_INVALID; //If the group separator occurs after the decimal separator in the Text argument, NUMBERVALUE returns the #VALUE! error value.
   sigPart = sigPart.replace(groupSep, ""); //If the group separator occurs before the decimal separator in the Text argument , the group separator is ignored.
   text = sigPart + "." + decPart;
  } else if (parts.length > 0) {
   sigPart = parts[0];
   sigPart = sigPart.replace(groupSep, ""); //If the group separator occurs before the decimal separator in the Text argument , the group separator is ignored.
   text = sigPart;
  } 
  
  //If the Text argument ends in one or more percent signs (%), they are used in the calculation of the result. 
  //Multiple percent signs are additive if they are used in the Text argument just as they are if they are used in a formula. 
  //For example, =NUMBERVALUE("9%%") returns the same result (0.0009) as the formula =9%%.
  int countPercent = 0;
  while (text.endsWith("%")) {
   countPercent++;
   text = text.substring(0, text.length()-1);   
  }
    
  try {  
   result = Double.valueOf(text);
   result = result / Math.pow(100, countPercent); //If the Text argument ends in one or more percent signs (%), they are used in the calculation of the result. 
   checkValue(result);
  } catch (EvaluationException e) {
    e.printStackTrace() ;
    return e.getErrorEval();
  } catch (Exception anyex) {
    return ErrorEval.VALUE_INVALID; //If any of the arguments are not valid, NUMBERVALUE returns the #VALUE! error value.
  }
  
  return new NumberEval(result);
 
 }
 
 static final void checkValue(double result) throws EvaluationException {
  if (Double.isNaN(result) || Double.isInfinite(result)) {
   throw new EvaluationException(ErrorEval.NUM_ERROR);
  }
 }
}

What about the prefix _xlfn.前缀_xlfn. ? ?

Excel uses this to mark functions which were introduced after Excel 2007. The prefix is stored as part of the function name. Excel 使用它来标记在 Excel 2007 之后引入的功能。前缀存储为 function 名称的一部分。 If the Excel version knows that function because it is later than Excel 2007, then the GUI will not show that prefix and evaluates the function. If the Excel version knows that function because it is later than Excel 2007, then the GUI will not show that prefix and evaluates the function. If the Excel version doesn't know that function, then the GUI will show the prefix to inform the user about that incompatibility.如果 Excel 版本不知道 function,则 GUI 将显示前缀以通知用户该不兼容。 See Issue: An _xlfn.请参阅问题:一个 _xlfn。 prefix is displayed in front of a formula 前缀显示在公式前面

Since the prefix is stored, the user definde function must be registered having the function name including the prefix: String[] functionNames = { "_xlfn.NUMBERVALUE" };由于存储了前缀,用户定义 function 必须注册为 function 名称,包括前缀: String[] functionNames = { "_xlfn.NUMBERVALUE" }; . .


This code is public available.此代码是公开的。 It is free to be reused in any kind of project.它可以在任何类型的项目中免费重用。 Of course it is without any warranty from my side.当然,我这边没有任何保证。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM