use of org.apache.poi.ss.formula.ptg.AbstractFunctionPtg in project poi by apache.
the class FormulaParser method getFunction.
/**
* Generates the variable function ptg for the formula.
* <p>
* For IF Formulas, additional PTGs are added to the tokens
* @param name a {@link NamePtg} or {@link NameXPtg} or <code>null</code>
* @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
*/
private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
int numArgs = args.length;
if (fm == null) {
if (namePtg == null) {
throw new IllegalStateException("NamePtg must be supplied for external functions");
}
// must be external function
ParseNode[] allArgs = new ParseNode[numArgs + 1];
allArgs[0] = new ParseNode(namePtg);
System.arraycopy(args, 0, allArgs, 1, numArgs);
return new ParseNode(FuncVarPtg.create(name, numArgs + 1), allArgs);
}
if (namePtg != null) {
throw new IllegalStateException("NamePtg no applicable to internal functions");
}
boolean isVarArgs = !fm.hasFixedArgsLength();
int funcIx = fm.getIndex();
if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
// POI does the same for consistency, but this is not critical
return new ParseNode(AttrPtg.getSumSingle(), args);
// The code below would encode tFuncVar(SUM) which seems to do no harm
}
validateNumArgs(args.length, fm);
AbstractFunctionPtg retval;
if (isVarArgs) {
retval = FuncVarPtg.create(name, numArgs);
} else {
retval = FuncPtg.create(funcIx);
}
return new ParseNode(retval, args);
}
use of org.apache.poi.ss.formula.ptg.AbstractFunctionPtg in project poi by apache.
the class TestFormulaParser method testMacroFunction.
// copied from org.apache.poi.hssf.model.TestFormulaParser
@Test
public void testMacroFunction() throws Exception {
// testNames.xlsm contains a VB function called 'myFunc'
final String testFile = "testNames.xlsm";
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook(testFile);
try {
XSSFEvaluationWorkbook workbook = XSSFEvaluationWorkbook.create(wb);
//Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands would go here...), FunctionPtg(myFunc)]
Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", workbook, FormulaType.CELL, -1);
assertEquals(3, ptg.length);
// the name gets encoded as the first operand on the stack
NameXPxg tname = (NameXPxg) ptg[0];
assertEquals("myFunc", tname.toFormulaString());
// the function's arguments are pushed onto the stack from left-to-right as OperandPtgs
StringPtg arg = (StringPtg) ptg[1];
assertEquals("arg", arg.getValue());
// The external FunctionPtg is the last Ptg added to the stack
// During formula evaluation, this Ptg pops off the the appropriate number of
// arguments (getNumberOfOperands()) and pushes the result on the stack
AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2];
assertTrue(tfunc.isExternalFunction());
// confirm formula parsing is case-insensitive
FormulaParser.parse("mYfUnC(\"arg\")", workbook, FormulaType.CELL, -1);
// confirm formula parsing doesn't care about argument count or type
// this should only throw an error when evaluating the formula.
FormulaParser.parse("myFunc()", workbook, FormulaType.CELL, -1);
FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", workbook, FormulaType.CELL, -1);
// A completely unknown formula name (not saved in workbook) should still be parseable and renderable
// but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated.
FormulaParser.parse("yourFunc(\"arg\")", workbook, FormulaType.CELL, -1);
// Make sure workbook can be written and read
XSSFTestDataSamples.writeOutAndReadBack(wb).close();
// Manually check to make sure file isn't corrupted
// TODO: develop a process for occasionally manually reviewing workbooks
// to verify workbooks are not corrupted
/*
final File fileIn = XSSFTestDataSamples.getSampleFile(testFile);
final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xlsm", "-saved.xlsm"));
final FileOutputStream fos = new FileOutputStream(reSavedFile);
wb.write(fos);
fos.close();
*/
} finally {
wb.close();
}
}
use of org.apache.poi.ss.formula.ptg.AbstractFunctionPtg in project poi by apache.
the class TestFormulaParser method testMacroFunction.
@Test
public void testMacroFunction() throws IOException {
// testNames.xls contains a VB function called 'myFunc'
final String testFile = "testNames.xls";
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(testFile);
try {
HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(wb);
//Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands go here...), FunctionPtg(myFunc)]
Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", book, FormulaType.CELL, -1);
assertEquals(3, ptg.length);
// the name gets encoded as the first operand on the stack
NamePtg tname = (NamePtg) ptg[0];
assertEquals("myFunc", tname.toFormulaString(book));
// the function's arguments are pushed onto the stack from left-to-right as OperandPtgs
StringPtg arg = (StringPtg) ptg[1];
assertEquals("arg", arg.getValue());
// The external FunctionPtg is the last Ptg added to the stack
// During formula evaluation, this Ptg pops off the the appropriate number of
// arguments (getNumberOfOperands()) and pushes the result on the stack
//FuncVarPtg
AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2];
assertTrue(tfunc.isExternalFunction());
// confirm formula parsing is case-insensitive
FormulaParser.parse("mYfUnC(\"arg\")", book, FormulaType.CELL, -1);
// confirm formula parsing doesn't care about argument count or type
// this should only throw an error when evaluating the formula.
FormulaParser.parse("myFunc()", book, FormulaType.CELL, -1);
FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", book, FormulaType.CELL, -1);
// A completely unknown formula name (not saved in workbook) should still be parseable and renderable
// but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated.
FormulaParser.parse("yourFunc(\"arg\")", book, FormulaType.CELL, -1);
// Verify that myFunc and yourFunc were successfully added to Workbook names
HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb);
try {
// HSSFWorkbook/EXCEL97-specific side-effects user-defined function names must be added to Workbook's defined names in order to be saved.
assertNotNull(wb2.getName("myFunc"));
assertEqualsIgnoreCase("myFunc", wb2.getName("myFunc").getNameName());
assertNotNull(wb2.getName("yourFunc"));
assertEqualsIgnoreCase("yourFunc", wb2.getName("yourFunc").getNameName());
// Manually check to make sure file isn't corrupted
// TODO: develop a process for occasionally manually reviewing workbooks
// to verify workbooks are not corrupted
/*
final File fileIn = HSSFTestDataSamples.getSampleFile(testFile);
final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls"));
FileOutputStream fos = new FileOutputStream(reSavedFile);
wb2.write(fos);
fos.close();
*/
} finally {
wb2.close();
}
} finally {
wb.close();
}
}
use of org.apache.poi.ss.formula.ptg.AbstractFunctionPtg in project poi by apache.
the class TestParseMissingBuiltInFuncs method confirmFunc.
private static void confirmFunc(String formula, int expPtgArraySize, boolean isVarArgFunc, int funcIx) throws IOException {
Ptg[] ptgs = parse(formula);
// func is last RPN token in all these formulas
Ptg ptgF = ptgs[ptgs.length - 1];
// Check critical things in the Ptg array encoding.
if (!(ptgF instanceof AbstractFunctionPtg)) {
throw new RuntimeException("function token missing");
}
AbstractFunctionPtg func = (AbstractFunctionPtg) ptgF;
if (func.getFunctionIndex() == 255) {
throw new AssertionFailedError("Failed to recognise built-in function in formula '" + formula + "'");
}
assertEquals(expPtgArraySize, ptgs.length);
assertEquals(funcIx, func.getFunctionIndex());
Class<? extends AbstractFunctionPtg> expCls = isVarArgFunc ? FuncVarPtg.class : FuncPtg.class;
assertEquals(expCls, ptgF.getClass());
// check that parsed Ptg array converts back to formula text OK
HSSFWorkbook book = new HSSFWorkbook();
String reRenderedFormula = HSSFFormulaParser.toFormulaString(book, ptgs);
assertEquals(formula, reRenderedFormula);
book.close();
}
use of org.apache.poi.ss.formula.ptg.AbstractFunctionPtg in project poi by apache.
the class FormulaParser method isValidRangeOperand.
/**
* @return <code>false</code> if sub-expression represented the specified ParseNode definitely
* cannot appear on either side of the range (':') operator
*/
private static boolean isValidRangeOperand(ParseNode a) {
Ptg tkn = a.getToken();
// Note - order is important for these instance-of checks
if (tkn instanceof OperandPtg) {
// notably cell refs and area refs
return true;
}
// next 2 are special cases of OperationPtg
if (tkn instanceof AbstractFunctionPtg) {
AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
byte returnClass = afp.getDefaultOperandClass();
return Ptg.CLASS_REF == returnClass;
}
if (tkn instanceof ValueOperatorPtg) {
return false;
}
if (tkn instanceof OperationPtg) {
return true;
}
// one special case of ControlPtg
if (tkn instanceof ParenthesisPtg) {
// parenthesis Ptg should have only one child
return isValidRangeOperand(a.getChildren()[0]);
}
// one special case of ScalarConstantPtg
if (tkn == ErrPtg.REF_INVALID) {
return true;
}
// All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
return false;
}
Aggregations