简体   繁体   中英

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

Interesting enough, when I try to regist this function WorkbookEvaluator.registerFunction("NUMBERVALUE", new NumberRValue()); it gives me another error: java.lang.IllegalArgumentException: NUMBERVALUE is not a function from the Excel Analysis Toolpack.

I also tried to use the user-defined functions by implementing FreeRefFunction, it backs to the first error again.

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

How to implement a text function in Apache POI?

WorkbookEvaluator.registerFunction only works for functions apache poi at least knows per name. 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.

But NUMBERVALUE function is not in this list. So this only can be added as a user defined function. See User Defined Functions of apache poi 's documentation.

The function must implement org.apache.poi.ss.formula.functions.FreeRefFunction and must be registered in UDF toolpack of the Workbook .

Following complete example shows a basic implementation of _xlfn.NUMBERVALUE . Implementation is done using the description in NUMBERVALUE function . It is a working draft until now and might need to be improved to better fulfill compatibility to Excel's own results.

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:

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. ?

Excel uses this to mark functions which were introduced after Excel 2007. The prefix is stored as part of the function name. 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. See Issue: An _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" }; .


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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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