Partitioning common formula logic. Introduced FormulaParsingWorkbook and EvaluationWorkbook interfaces to make merge with ooxml branch easier

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@696898 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-09-19 02:19:58 +00:00
parent 26b458016e
commit 0ddb062fb9
49 changed files with 1543 additions and 1141 deletions

View File

@ -20,7 +20,7 @@ package org.apache.poi.hssf.dev;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordFactory; import org.apache.poi.hssf.record.RecordFactory;
@ -181,7 +181,7 @@ public class FormulaViewer
private static String composeFormula(FormulaRecord record) private static String composeFormula(FormulaRecord record)
{ {
return FormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression()); return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, record.getParsedExpression());
} }
/** /**

View File

@ -19,7 +19,7 @@ package org.apache.poi.hssf.eventusermodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.EOFRecord; import org.apache.poi.hssf.record.EOFRecord;
@ -33,7 +33,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
* When working with the EventUserModel, if you want to * When working with the EventUserModel, if you want to
* process formulas, you need an instance of * process formulas, you need an instance of
* {@link Workbook} to pass to a {@link HSSFWorkbook}, * {@link Workbook} to pass to a {@link HSSFWorkbook},
* to finally give to {@link FormulaParser}, * to finally give to {@link HSSFFormulaParser},
* and this will build you stub ones. * and this will build you stub ones.
* Since you're working with the EventUserModel, you * Since you're working with the EventUserModel, you
* wouldn't want to get a full {@link Workbook} and * wouldn't want to get a full {@link Workbook} and
@ -41,7 +41,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
* Instead, you should collect a few key records as they * Instead, you should collect a few key records as they
* go past, then call this once you have them to build a * go past, then call this once you have them to build a
* stub {@link Workbook}, and from that a stub * stub {@link Workbook}, and from that a stub
* {@link HSSFWorkbook}, to use with the {@link FormulaParser}. * {@link HSSFWorkbook}, to use with the {@link HSSFFormulaParser}.
* *
* The records you should collect are: * The records you should collect are:
* * {@link ExternSheetRecord} * * {@link ExternSheetRecord}
@ -56,7 +56,7 @@ public class EventWorkbookBuilder {
/** /**
* Wraps up your stub {@link Workbook} as a stub * Wraps up your stub {@link Workbook} as a stub
* {@link HSSFWorkbook}, ready for passing to * {@link HSSFWorkbook}, ready for passing to
* {@link FormulaParser} * {@link HSSFFormulaParser}
* @param workbook A stub {@link Workbook} * @param workbook A stub {@link Workbook}
*/ */
public static HSSFWorkbook createStubHSSFWorkbook(Workbook workbook) { public static HSSFWorkbook createStubHSSFWorkbook(Workbook workbook) {
@ -65,11 +65,11 @@ public class EventWorkbookBuilder {
/** /**
* Creates a stub Workbook from the supplied records, * Creates a stub Workbook from the supplied records,
* suitable for use with the {@link FormulaParser} * suitable for use with the {@link HSSFFormulaParser}
* @param externs The ExternSheetRecords in your file * @param externs The ExternSheetRecords in your file
* @param bounds The BoundSheetRecords in your file * @param bounds The BoundSheetRecords in your file
* @param sst The SSTRecord in your file. * @param sst The SSTRecord in your file.
* @return A stub Workbook suitable for use with {@link FormulaParser} * @return A stub Workbook suitable for use with {@link HSSFFormulaParser}
*/ */
public static Workbook createStubWorkbook(ExternSheetRecord[] externs, public static Workbook createStubWorkbook(ExternSheetRecord[] externs,
BoundSheetRecord[] bounds, SSTRecord sst) { BoundSheetRecord[] bounds, SSTRecord sst) {
@ -103,10 +103,10 @@ public class EventWorkbookBuilder {
/** /**
* Creates a stub workbook from the supplied records, * Creates a stub workbook from the supplied records,
* suitable for use with the {@link FormulaParser} * suitable for use with the {@link HSSFFormulaParser}
* @param externs The ExternSheetRecords in your file * @param externs The ExternSheetRecords in your file
* @param bounds The BoundSheetRecords in your file * @param bounds The BoundSheetRecords in your file
* @return A stub Workbook suitable for use with {@link FormulaParser} * @return A stub Workbook suitable for use with {@link HSSFFormulaParser}
*/ */
public static Workbook createStubWorkbook(ExternSheetRecord[] externs, public static Workbook createStubWorkbook(ExternSheetRecord[] externs,
BoundSheetRecord[] bounds) { BoundSheetRecord[] bounds) {

View File

@ -32,7 +32,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFListener;
import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.eventusermodel.HSSFRequest;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BOFRecord;
import org.apache.poi.hssf.record.BoundSheetRecord; import org.apache.poi.hssf.record.BoundSheetRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.CellValueRecordInterface;
@ -45,6 +45,7 @@ import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.StringRecord;
import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
/** /**
@ -177,7 +178,7 @@ public class EventBasedExcelExtractor extends POIOLE2TextExtractor {
thisRow = frec.getRow(); thisRow = frec.getRow();
if(formulasNotResults) { if(formulasNotResults) {
thisText = FormulaParser.toFormulaString(null, frec.getParsedExpression()); thisText = HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, frec.getParsedExpression());
} else { } else {
if(frec.hasCachedResultString()) { if(frec.hasCachedResultString()) {
// Formula result is a string // Formula result is a string

View File

@ -0,0 +1,72 @@
/* ====================================================================
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.hssf.model;
import java.util.List;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
/**
* HSSF wrapper for the {@link FormulaParser}
*
* @author Josh Micich
*/
public final class HSSFFormulaParser {
private static FormulaParsingWorkbook createParsingWorkbook(HSSFWorkbook book) {
return HSSFEvaluationWorkbook.create(book);
}
private HSSFFormulaParser() {
// no instances of this class
}
public static Ptg[] parse(String formula, HSSFWorkbook workbook) {
return FormulaParser.parse(formula, createParsingWorkbook(workbook));
}
public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) {
return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType);
}
public static String toFormulaString(HSSFWorkbook book, List lptgs) {
return toFormulaString(HSSFEvaluationWorkbook.create(book), lptgs);
}
/**
* Convenience method which takes in a list then passes it to the
* other toFormulaString signature.
* @param book workbook for 3D and named references
* @param lptgs list of Ptg, can be null or empty
* @return a human readable String
*/
public static String toFormulaString(FormulaRenderingWorkbook book, List lptgs) {
Ptg[] ptgs = new Ptg[lptgs.size()];
lptgs.toArray(ptgs);
return FormulaRenderer.toFormulaString(book, ptgs);
}
public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
}
}

View File

@ -17,7 +17,7 @@
package org.apache.poi.hssf.record; package org.apache.poi.hssf.record;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.cf.BorderFormatting; import org.apache.poi.hssf.record.cf.BorderFormatting;
import org.apache.poi.hssf.record.cf.FontFormatting; import org.apache.poi.hssf.record.cf.FontFormatting;
import org.apache.poi.hssf.record.cf.PatternFormatting; import org.apache.poi.hssf.record.cf.PatternFormatting;
@ -595,6 +595,6 @@ public final class CFRuleRecord extends Record {
if(formula == null) { if(formula == null) {
return null; return null;
} }
return FormulaParser.parse(formula, workbook); return HSSFFormulaParser.parse(formula, workbook);
} }
} }

View File

@ -20,7 +20,7 @@ package org.apache.poi.hssf.record;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.formula.Area3DPtg; import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.Ref3DPtg;
@ -495,7 +495,7 @@ public final class NameRecord extends Record {
* @return area reference * @return area reference
*/ */
public String getAreaReference(HSSFWorkbook book){ public String getAreaReference(HSSFWorkbook book){
return FormulaParser.toFormulaString(book, field_13_name_definition); return HSSFFormulaParser.toFormulaString(book, field_13_name_definition);
} }
/** sets the reference , the area only (range) /** sets the reference , the area only (range)

View File

@ -24,13 +24,13 @@ import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.EvaluationWorkbook;
public final class AnalysisToolPak { public final class AnalysisToolPak {
private static final FreeRefFunction NotImplemented = new FreeRefFunction() { private static final FreeRefFunction NotImplemented = new FreeRefFunction() {
public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet,
int srcCellRow, int srcCellCol) { int srcCellRow, int srcCellCol) {
return ErrorEval.FUNCTION_NOT_IMPLEMENTED; return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
} }

View File

@ -17,7 +17,6 @@
package org.apache.poi.hssf.record.formula.atp; package org.apache.poi.hssf.record.formula.atp;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
@ -25,8 +24,7 @@ import org.apache.poi.hssf.record.formula.eval.EvaluationException;
import org.apache.poi.hssf.record.formula.eval.OperandResolver; import org.apache.poi.hssf.record.formula.eval.OperandResolver;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
* Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/> * Implementation of Excel 'Analysis ToolPak' function ISEVEN() ISODD()<br/>
* *
@ -42,7 +40,7 @@ final class ParityFunction implements FreeRefFunction {
_desiredParity = desiredParity; _desiredParity = desiredParity;
} }
public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow,
int srcCellCol) { int srcCellCol) {
if (args.length != 1) { if (args.length != 1) {
return ErrorEval.VALUE_INVALID; return ErrorEval.VALUE_INVALID;

View File

@ -21,7 +21,6 @@ import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.EvaluationException; import org.apache.poi.hssf.record.formula.eval.EvaluationException;
@ -31,8 +30,7 @@ import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
* Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/> * Implementation of Excel 'Analysis ToolPak' function YEARFRAC()<br/>
* *
@ -61,7 +59,7 @@ final class YearFrac implements FreeRefFunction {
// enforce singleton // enforce singleton
} }
public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow,
int srcCellCol) { int srcCellCol) {
double result; double result;

View File

@ -19,7 +19,7 @@ package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak; import org.apache.poi.hssf.record.formula.atp.AnalysisToolPak;
import org.apache.poi.hssf.record.formula.functions.FreeRefFunction; import org.apache.poi.hssf.record.formula.functions.FreeRefFunction;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.EvaluationWorkbook;
/** /**
* *
* Common entry point for all user-defined (non-built-in) functions (where * Common entry point for all user-defined (non-built-in) functions (where
@ -30,7 +30,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
*/ */
final class ExternalFunction implements FreeRefFunction { final class ExternalFunction implements FreeRefFunction {
public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook,
int srcCellSheet, int srcCellRow,int srcCellCol) { int srcCellSheet, int srcCellRow,int srcCellCol) {
int nIncomingArgs = args.length; int nIncomingArgs = args.length;
@ -58,9 +58,9 @@ final class ExternalFunction implements FreeRefFunction {
return targetFunc.evaluate(outGoingArgs, workbook, srcCellSheet, srcCellRow, srcCellCol); return targetFunc.evaluate(outGoingArgs, workbook, srcCellSheet, srcCellRow, srcCellCol);
} }
private FreeRefFunction findExternalUserDefinedFunction(HSSFWorkbook workbook, private FreeRefFunction findExternalUserDefinedFunction(EvaluationWorkbook workbook,
NameXEval n) throws EvaluationException { NameXEval n) throws EvaluationException {
String functionName = workbook.resolveNameXText(n.getSheetRefIndex(), n.getNameNumber()); String functionName = workbook.resolveNameXText(n.getPtg());
if(false) { if(false) {
System.out.println("received call to external user defined function (" + functionName + ")"); System.out.println("received call to external user defined function (" + functionName + ")");

View File

@ -17,32 +17,27 @@
package org.apache.poi.hssf.record.formula.eval; package org.apache.poi.hssf.record.formula.eval;
import org.apache.poi.hssf.record.formula.NameXPtg;
/** /**
* @author Josh Micich * @author Josh Micich
*/ */
public final class NameXEval implements Eval { public final class NameXEval implements Eval {
/** index to REF entry in externsheet record */ private final NameXPtg _ptg;
private final int _sheetRefIndex;
/** index to defined name or externname table(1 based) */
private final int _nameNumber;
public NameXEval(int sheetRefIndex, int nameNumber) { public NameXEval(NameXPtg ptg) {
_sheetRefIndex = sheetRefIndex; _ptg = ptg;
_nameNumber = nameNumber;
} }
public int getSheetRefIndex() { public NameXPtg getPtg() {
return _sheetRefIndex; return _ptg;
}
public int getNameNumber() {
return _nameNumber;
} }
public String toString() { public String toString() {
StringBuffer sb = new StringBuffer(64); StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" ["); sb.append(getClass().getName()).append(" [");
sb.append(_sheetRefIndex).append(", ").append(_nameNumber); sb.append(_ptg.getSheetRefIndex()).append(", ").append(_ptg.getNameIndex());
sb.append("]"); sb.append("]");
return sb.toString(); return sb.toString();
} }

View File

@ -19,8 +19,7 @@ package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
@ -53,5 +52,5 @@ public interface FreeRefFunction {
* a specified Excel error (Exceptions are never thrown to represent Excel errors). * a specified Excel error (Exceptions are never thrown to represent Excel errors).
* *
*/ */
ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol); ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol);
} }

View File

@ -1,27 +1,26 @@
/* /* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one or more Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0 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 not use this file except in compliance with
* the License. You may obtain a copy of the License at the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and See the License for the specific language governing permissions and
* limitations under the License. limitations under the License.
*/ ==================================================================== */
package org.apache.poi.hssf.record.formula.functions; package org.apache.poi.hssf.record.formula.functions;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval; import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
* Implementation for Excel function INDIRECT<p/> * Implementation for Excel function INDIRECT<p/>
@ -41,7 +40,7 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook;
*/ */
public final class Indirect implements FreeRefFunction { public final class Indirect implements FreeRefFunction {
public ValueEval evaluate(Eval[] args, HSSFWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) { public ValueEval evaluate(Eval[] args, EvaluationWorkbook workbook, int srcCellSheet, int srcCellRow, int srcCellCol) {
// TODO - implement INDIRECT() // TODO - implement INDIRECT()
return ErrorEval.FUNCTION_NOT_IMPLEMENTED; return ErrorEval.FUNCTION_NOT_IMPLEMENTED;
} }

View File

@ -20,10 +20,12 @@ import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.formula.NumberPtg; import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.StringPtg; import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaType;
/** /**
* *
@ -339,7 +341,7 @@ public class DVConstraint {
if (_explicitListValues == null) { if (_explicitListValues == null) {
// formula is parsed with slightly different RVA rules: (root node type must be 'reference') // formula is parsed with slightly different RVA rules: (root node type must be 'reference')
return FormulaParser.parse(_formula1, workbook, FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST); return HSSFFormulaParser.parse(_formula1, workbook, FormulaType.DATAVALIDATION_LIST);
// To do: Excel places restrictions on the available operations within a list formula. // To do: Excel places restrictions on the available operations within a list formula.
// Some things like union and intersection are not allowed. // Some things like union and intersection are not allowed.
} }
@ -369,7 +371,7 @@ public class DVConstraint {
if (value != null) { if (value != null) {
throw new IllegalStateException("Both formula and value cannot be present"); throw new IllegalStateException("Both formula and value cannot be present");
} }
return FormulaParser.parse(formula, workbook); return HSSFFormulaParser.parse(formula, workbook);
} }

View File

@ -25,7 +25,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Sheet;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BlankRecord;
@ -124,7 +124,7 @@ public final class HSSFCell {
short xfindex = sheet.getSheet().getXFIndexForColAt(col); short xfindex = sheet.getSheet().getXFIndexForColAt(col);
setCellType(CELL_TYPE_BLANK, false, row, col,xfindex); setCellType(CELL_TYPE_BLANK, false, row, col,xfindex);
} }
/* package */ HSSFSheet getSheet() { public HSSFSheet getSheet() {
return sheet; return sheet;
} }
@ -589,12 +589,12 @@ public final class HSSFCell {
if (rec.getXFIndex() == (short)0) { if (rec.getXFIndex() == (short)0) {
rec.setXFIndex((short) 0x0f); rec.setXFIndex((short) 0x0f);
} }
Ptg[] ptgs = FormulaParser.parse(formula, book); Ptg[] ptgs = HSSFFormulaParser.parse(formula, book);
frec.setParsedExpression(ptgs); frec.setParsedExpression(ptgs);
} }
public String getCellFormula() { public String getCellFormula() {
return FormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression()); return HSSFFormulaParser.toFormulaString(book, ((FormulaRecordAggregate)record).getFormulaRecord().getParsedExpression());
} }
/** /**

View File

@ -17,7 +17,7 @@
package org.apache.poi.hssf.usermodel; package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.CFRuleRecord; import org.apache.poi.hssf.record.CFRuleRecord;
import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator; import org.apache.poi.hssf.record.CFRuleRecord.ComparisonOperator;
import org.apache.poi.hssf.record.cf.BorderFormatting; import org.apache.poi.hssf.record.cf.BorderFormatting;
@ -205,6 +205,6 @@ public final class HSSFConditionalFormattingRule
if(parsedExpression ==null) { if(parsedExpression ==null) {
return null; return null;
} }
return FormulaParser.toFormulaString(workbook, parsedExpression); return HSSFFormulaParser.toFormulaString(workbook, parsedExpression);
} }
} }

View File

@ -1,8 +1,14 @@
package org.apache.poi.hssf.usermodel; package org.apache.poi.hssf.usermodel;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.ss.formula.EvaluationName;
import org.apache.poi.ss.formula.EvaluationWorkbook;
import org.apache.poi.ss.formula.FormulaParsingWorkbook;
import org.apache.poi.ss.formula.FormulaRenderingWorkbook; import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
/** /**
@ -10,8 +16,9 @@ import org.apache.poi.ss.formula.FormulaRenderingWorkbook;
* *
* @author Josh Micich * @author Josh Micich
*/ */
public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook { public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
private final HSSFWorkbook _uBook;
private final Workbook _iBook; private final Workbook _iBook;
public static HSSFEvaluationWorkbook create(HSSFWorkbook book) { public static HSSFEvaluationWorkbook create(HSSFWorkbook book) {
@ -22,9 +29,58 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {
} }
private HSSFEvaluationWorkbook(HSSFWorkbook book) { private HSSFEvaluationWorkbook(HSSFWorkbook book) {
_uBook = book;
_iBook = book.getWorkbook(); _iBook = book.getWorkbook();
} }
public int getExternalSheetIndex(String sheetName) {
int sheetIndex = _uBook.getSheetIndex(sheetName);
return _iBook.checkExternSheet(sheetIndex);
}
public EvaluationName getName(int index) {
return new Name(_iBook.getNameRecord(index), index);
}
public EvaluationName getName(String name) {
for(int i=0; i < _iBook.getNumNames(); i++) {
NameRecord nr = _iBook.getNameRecord(i);
if (name.equalsIgnoreCase(nr.getNameText())) {
return new Name(nr, i);
}
}
return null;
}
public int getSheetIndex(HSSFSheet sheet) {
return _uBook.getSheetIndex(sheet);
}
public String getSheetName(int sheetIndex) {
return _uBook.getSheetName(sheetIndex);
}
public int getNameIndex(String name) {
return _uBook.getNameIndex(name);
}
public NameXPtg getNameXPtg(String name) {
return _iBook.getNameXPtg(name);
}
public HSSFSheet getSheet(int sheetIndex) {
return _uBook.getSheetAt(sheetIndex);
}
public HSSFSheet getSheetByExternSheetIndex(int externSheetIndex) {
int sheetIndex = _iBook.getSheetIndexFromExternSheetIndex(externSheetIndex);
return _uBook.getSheetAt(sheetIndex);
}
public HSSFWorkbook getWorkbook() {
return _uBook;
}
public String resolveNameXText(NameXPtg n) { public String resolveNameXText(NameXPtg n) {
return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex()); return _iBook.resolveNameXText(n.getSheetRefIndex(), n.getNameIndex());
} }
@ -35,4 +91,45 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook {
public String getNameText(NamePtg namePtg) { public String getNameText(NamePtg namePtg) {
return _iBook.getNameRecord(namePtg.getIndex()).getNameText(); return _iBook.getNameRecord(namePtg.getIndex()).getNameText();
} }
public EvaluationName getName(NamePtg namePtg) {
int ix = namePtg.getIndex();
return new Name(_iBook.getNameRecord(ix), ix);
}
public Ptg[] getFormulaTokens(HSSFCell cell) {
return HSSFFormulaParser.parse(cell.getCellFormula(), _uBook);
}
private static final class Name implements EvaluationName {
private final NameRecord _nameRecord;
private final int _index;
public Name(NameRecord nameRecord, int index) {
_nameRecord = nameRecord;
_index = index;
}
public Ptg[] getNameDefinition() {
return _nameRecord.getNameDefinition();
}
public String getNameText() {
return _nameRecord.getNameText();
}
public boolean hasFormula() {
return _nameRecord.hasFormula();
}
public boolean isFunctionName() {
return _nameRecord.isFunctionName();
}
public boolean isRange() {
return _nameRecord.hasFormula(); // TODO - is this right?
}
public NamePtg createPtg() {
return new NamePtg(_index);
}
}
} }

View File

@ -18,43 +18,13 @@
package org.apache.poi.hssf.usermodel; package org.apache.poi.hssf.usermodel;
import java.util.Iterator; import java.util.Iterator;
import java.util.Stack;
import org.apache.poi.hssf.model.FormulaParser;
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ControlPtg;
import org.apache.poi.hssf.record.formula.ErrPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.MemErrPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.UnionPtg;
import org.apache.poi.hssf.record.formula.UnknownPtg;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval; import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval; import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.NameEval;
import org.apache.poi.hssf.record.formula.eval.NameXEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval; import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval; import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.ss.formula.WorkbookEvaluator;
/** /**
* Evaluates formula cells.<p/> * Evaluates formula cells.<p/>
@ -68,21 +38,7 @@ import org.apache.poi.hssf.util.CellReference;
*/ */
public class HSSFFormulaEvaluator { public class HSSFFormulaEvaluator {
/** private WorkbookEvaluator _bookEvaluator;
* used to track the number of evaluations
*/
private static final class Counter {
public int value;
public int depth;
public Counter() {
value = 0;
}
}
private final HSSFWorkbook _workbook;
private final EvaluationCache _cache;
private Counter _evaluationCounter;
/** /**
* @deprecated (Sep 2008) HSSFSheet parameter is ignored * @deprecated (Sep 2008) HSSFSheet parameter is ignored
@ -94,35 +50,14 @@ public class HSSFFormulaEvaluator {
} }
} }
public HSSFFormulaEvaluator(HSSFWorkbook workbook) { public HSSFFormulaEvaluator(HSSFWorkbook workbook) {
this(workbook, new EvaluationCache(), new Counter()); _bookEvaluator = new WorkbookEvaluator(HSSFEvaluationWorkbook.create(workbook));
}
private HSSFFormulaEvaluator(HSSFWorkbook workbook, EvaluationCache cache, Counter evaluationCounter) {
_workbook = workbook;
_cache = cache;
_evaluationCounter = evaluationCounter;
} }
/** /**
* for debug use. Used in toString methods * TODO for debug/test use
*/
/* package */ String getSheetName(HSSFSheet sheet) {
return _workbook.getSheetName(_workbook.getSheetIndex(sheet));
}
/**
* for debug/test use
*/ */
/* package */ int getEvaluationCount() { /* package */ int getEvaluationCount() {
return _evaluationCounter.value; return _bookEvaluator.getEvaluationCount();
}
private static boolean isDebugLogEnabled() {
return false;
}
private static void logDebug(String s) {
if (isDebugLogEnabled()) {
System.out.println(s);
}
} }
/** /**
@ -137,23 +72,21 @@ public class HSSFFormulaEvaluator {
} }
/** /**
* Should be called whenever there are changes to input cells in the evaluated workbook. * Should be called whenever there are major changes (e.g. moving sheets) to input cells
* in the evaluated workbook.
* Failure to call this method after changing cell values will cause incorrect behaviour * Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class * of the evaluate~ methods of this class
*/ */
public void clearCache() { public void clearAllCachedResultValues() {
_cache.clear(); _bookEvaluator.clearAllCachedResultValues();
} }
/** /**
* Returns an underlying FormulaParser, for the specified * Should be called whenever there are changes to individual input cells in the evaluated workbook.
* Formula String and HSSFWorkbook. * Failure to call this method after changing cell values will cause incorrect behaviour
* This will allow you to generate the Ptgs yourself, if * of the evaluate~ methods of this class
* your needs are more complex than just having the
* formula evaluated.
*/ */
public static FormulaParser getUnderlyingParser(HSSFWorkbook workbook, String formula) { public void clearCachedResultValue(HSSFSheet sheet, int rowIndex, int columnIndex) {
return new FormulaParser(formula, workbook); _bookEvaluator.clearCachedResultValue(sheet, rowIndex, columnIndex);
} }
/** /**
@ -314,7 +247,7 @@ public class HSSFFormulaEvaluator {
* @param eval * @param eval
*/ */
private CellValue evaluateFormulaCellValue(HSSFCell cell) { private CellValue evaluateFormulaCellValue(HSSFCell cell) {
ValueEval eval = internalEvaluate(cell); ValueEval eval = _bookEvaluator.evaluate(cell);
if (eval instanceof NumberEval) { if (eval instanceof NumberEval) {
NumberEval ne = (NumberEval) eval; NumberEval ne = (NumberEval) eval;
return new CellValue(ne.getNumberValue()); return new CellValue(ne.getNumberValue());
@ -333,257 +266,6 @@ public class HSSFFormulaEvaluator {
throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")"); throw new RuntimeException("Unexpected eval class (" + eval.getClass().getName() + ")");
} }
/**
* Dev. Note: Internal evaluate must be passed only a formula cell
* else a runtime exception will be thrown somewhere inside the method.
* (Hence this is a private method.)
* @return never <code>null</code>, never {@link BlankEval}
*/
private ValueEval internalEvaluate(HSSFCell srcCell) {
int srcRowNum = srcCell.getRowIndex();
int srcColNum = srcCell.getCellNum();
ValueEval result;
int sheetIndex = _workbook.getSheetIndex(srcCell.getSheet());
result = _cache.getValue(sheetIndex, srcRowNum, srcColNum);
if (result != null) {
return result;
}
_evaluationCounter.value++;
_evaluationCounter.depth++;
EvaluationCycleDetector tracker = EvaluationCycleDetectorManager.getTracker();
if(!tracker.startEvaluate(_workbook, sheetIndex, srcRowNum, srcColNum)) {
return ErrorEval.CIRCULAR_REF_ERROR;
}
try {
result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, srcCell.getCellFormula());
} finally {
tracker.endEvaluate(_workbook, sheetIndex, srcRowNum, srcColNum);
_cache.setValue(sheetIndex, srcRowNum, srcColNum, result);
_evaluationCounter.depth--;
}
if (isDebugLogEnabled()) {
String sheetName = _workbook.getSheetName(sheetIndex);
CellReference cr = new CellReference(srcRowNum, srcColNum);
logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
}
return result;
}
private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, String cellFormulaText) {
Ptg[] ptgs = FormulaParser.parse(cellFormulaText, _workbook);
Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc
continue;
}
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) {
// TODO - might need to push BlankEval or MissingArgEval
continue;
}
Eval opResult;
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
if (optg instanceof UnionPtg) { continue; }
OperationEval operation = OperationEvaluatorFactory.create(optg);
int numops = operation.getNumberOfOperands();
Eval[] ops = new Eval[numops];
// storing the ops in reverse order since they are popping
for (int j = numops - 1; j >= 0; j--) {
Eval p = (Eval) stack.pop();
ops[j] = p;
}
// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum);
} else {
opResult = getEvalForPtg(ptg, sheetIndex);
}
if (opResult == null) {
throw new RuntimeException("Evaluation result must not be null");
}
// logDebug("push " + opResult);
stack.push(opResult);
}
ValueEval value = ((ValueEval) stack.pop());
if (!stack.isEmpty()) {
throw new IllegalStateException("evaluation stack not empty");
}
value = dereferenceValue(value, srcRowNum, srcColNum);
if (value == BlankEval.INSTANCE) {
// Note Excel behaviour here. A blank final final value is converted to zero.
return NumberEval.ZERO;
// Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
// blank, the actual value is empty string. This can be verified with ISBLANK().
}
return value;
}
/**
* Dereferences a single value from any AreaEval or RefEval evaluation result.
* If the supplied evaluationResult is just a plain value, it is returned as-is.
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
* <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
*/
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
if (evaluationResult instanceof RefEval) {
RefEval rv = (RefEval) evaluationResult;
return rv.getInnerValueEval();
}
if (evaluationResult instanceof AreaEval) {
AreaEval ae = (AreaEval) evaluationResult;
if (ae.isRow()) {
if(ae.isColumn()) {
return ae.getRelativeValue(0, 0);
}
return ae.getValueAt(ae.getFirstRow(), srcColNum);
}
if (ae.isColumn()) {
return ae.getValueAt(srcRowNum, ae.getFirstColumn());
}
return ErrorEval.VALUE_INVALID;
}
return evaluationResult;
}
private static Eval invokeOperation(OperationEval operation, Eval[] ops,
HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
if(operation instanceof FunctionEval) {
FunctionEval fe = (FunctionEval) operation;
if(fe.isFreeRefFunction()) {
return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
}
}
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
}
private HSSFSheet getOtherSheet(int externSheetIndex) {
Workbook wb = _workbook.getWorkbook();
return _workbook.getSheetAt(wb.getSheetIndexFromExternSheetIndex(externSheetIndex));
}
/**
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be
* one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
* StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be
* passed here!
*/
private Eval getEvalForPtg(Ptg ptg, int sheetIndex) {
if (ptg instanceof NamePtg) {
// named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
int numberOfNames = _workbook.getNumberOfNames();
int nameIndex = namePtg.getIndex();
if(nameIndex < 0 || nameIndex >= numberOfNames) {
throw new RuntimeException("Bad name index (" + nameIndex
+ "). Allowed range is (0.." + (numberOfNames-1) + ")");
}
NameRecord nameRecord = _workbook.getWorkbook().getNameRecord(nameIndex);
if (nameRecord.isFunctionName()) {
return new NameEval(nameRecord.getNameText());
}
if (nameRecord.hasFormula()) {
return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex);
}
throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
}
if (ptg instanceof NameXPtg) {
NameXPtg nameXPtg = (NameXPtg) ptg;
return new NameXEval(nameXPtg.getSheetRefIndex(), nameXPtg.getNameIndex());
}
if (ptg instanceof IntPtg) {
return new NumberEval(((IntPtg)ptg).getValue());
}
if (ptg instanceof NumberPtg) {
return new NumberEval(((NumberPtg)ptg).getValue());
}
if (ptg instanceof StringPtg) {
return new StringEval(((StringPtg) ptg).getValue());
}
if (ptg instanceof BoolPtg) {
return BoolEval.valueOf(((BoolPtg) ptg).getValue());
}
if (ptg instanceof ErrPtg) {
return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
}
HSSFSheet sheet = _workbook.getSheetAt(sheetIndex);
if (ptg instanceof RefPtg) {
return new LazyRefEval(((RefPtg) ptg), sheet, this);
}
if (ptg instanceof AreaPtg) {
return new LazyAreaEval(((AreaPtg) ptg), sheet, this);
}
if (ptg instanceof Ref3DPtg) {
Ref3DPtg refPtg = (Ref3DPtg) ptg;
HSSFSheet xsheet = getOtherSheet(refPtg.getExternSheetIndex());
return new LazyRefEval(refPtg, xsheet, this);
}
if (ptg instanceof Area3DPtg) {
Area3DPtg a3dp = (Area3DPtg) ptg;
HSSFSheet xsheet = getOtherSheet(a3dp.getExternSheetIndex());
return new LazyAreaEval(a3dp, xsheet, this);
}
if (ptg instanceof UnknownPtg) {
// POI uses UnknownPtg when the encoded Ptg array seems to be corrupted.
// This seems to occur in very rare cases (e.g. unused name formulas in bug 44774, attachment 21790)
// In any case, formulas are re-parsed before execution, so UnknownPtg should not get here
throw new RuntimeException("UnknownPtg not allowed");
}
throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")");
}
private Eval evaluateNameFormula(Ptg[] ptgs, int sheetIndex) {
if (ptgs.length > 1) {
throw new RuntimeException("Complex name formulas not supported yet");
}
return getEvalForPtg(ptgs[0], sheetIndex);
}
/**
* Given a cell, find its type and from that create an appropriate ValueEval
* impl instance and return that. Since the cell could be an external
* reference, we need the sheet that this belongs to.
* Non existent cells are treated as empty.
*/
/* package */ ValueEval getEvalForCell(HSSFCell cell) {
if (cell == null) {
return BlankEval.INSTANCE;
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
return new NumberEval(cell.getNumericCellValue());
case HSSFCell.CELL_TYPE_STRING:
return new StringEval(cell.getRichStringCellValue().getString());
case HSSFCell.CELL_TYPE_FORMULA:
return internalEvaluate(cell);
case HSSFCell.CELL_TYPE_BOOLEAN:
return BoolEval.valueOf(cell.getBooleanCellValue());
case HSSFCell.CELL_TYPE_BLANK:
return BlankEval.INSTANCE;
case HSSFCell.CELL_TYPE_ERROR:
return ErrorEval.valueOf(cell.getErrorCellValue());
}
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
/** /**
* Mimics the 'data view' of a cell. This allows formula evaluator * Mimics the 'data view' of a cell. This allows formula evaluator
* to return a CellValue instead of precasting the value to String * to return a CellValue instead of precasting the value to String
@ -683,21 +365,4 @@ public class HSSFFormulaEvaluator {
return "<error unexpected cell type " + _cellType + ">"; return "<error unexpected cell type " + _cellType + ">";
} }
} }
/**
* debug method
*/
void inspectPtgs(String formula) {
Ptg[] ptgs = FormulaParser.parse(formula, _workbook);
System.out.println("<ptg-group>");
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
System.out.println("<ptg>");
System.out.println(ptgs[i]);
if (ptgs[i] instanceof OperationPtg) {
System.out.println("numoperands: " + ((OperationPtg) ptgs[i]).getNumberOfOperands());
}
System.out.println("</ptg>");
}
System.out.println("</ptg-group>");
}
} }

View File

@ -95,7 +95,7 @@ public final class HSSFName {
*/ */
private void setSheetName(String sheetName){ private void setSheetName(String sheetName){
int sheetNumber = _book.getSheetIndex(sheetName); int sheetNumber = _book.getSheetIndex(sheetName);
short externSheetNumber = _book.getExternalSheetIndex(sheetNumber); short externSheetNumber = _book.getWorkbook().checkExternSheet(sheetNumber);
_definedNameRec.setExternSheetNumber(externSheetNumber); _definedNameRec.setExternSheetNumber(externSheetNumber);
} }

View File

@ -661,52 +661,6 @@ public class HSSFWorkbook extends POIDocument
return -1; return -1;
} }
/* package */ int findSheetIndex(Sheet sheet) {
for(int i=0; i<_sheets.size(); i++) {
HSSFSheet hSheet = (HSSFSheet) _sheets.get(i);
if(hSheet.getSheet() == sheet) {
return i;
}
}
throw new IllegalArgumentException("Specified sheet not found in this workbook");
}
/**
* Returns the external sheet index of the sheet
* with the given internal index, creating one
* if needed.
* Used by some of the more obscure formula and
* named range things.
* @deprecated for POI internal use only (formula parsing). This method is likely to
* be removed in future versions of POI.
*/
public short getExternalSheetIndex(int internalSheetIndex) {
return workbook.checkExternSheet(internalSheetIndex);
}
/**
* @deprecated for POI internal use only (formula rendering). This method is likely to
* be removed in future versions of POI.
*/
public String findSheetNameFromExternSheet(int externSheetIndex){
// TODO - don't expose internal ugliness like externSheet indexes to the user model API
return workbook.findSheetNameFromExternSheet(externSheetIndex);
}
/**
* @deprecated for POI internal use only (formula rendering). This method is likely to
* be removed in future versions of POI.
*
* @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
* @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
* @return the string representation of the defined or external name
*/
public String resolveNameXText(int refIndex, int definedNameIndex) {
// TODO - make this less cryptic / move elsewhere
return workbook.resolveNameXText(refIndex, definedNameIndex);
}
/** /**
* create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns * create an HSSFSheet for this HSSFWorkbook, adds it to the sheets and returns
* the high level representation. Use this to create new sheets. * the high level representation. Use this to create new sheets.
@ -750,7 +704,7 @@ public class HSSFWorkbook extends POIDocument
if (filterDbNameIndex >=0) { if (filterDbNameIndex >=0) {
NameRecord origNameRecord = workbook.getNameRecord(filterDbNameIndex); NameRecord origNameRecord = workbook.getNameRecord(filterDbNameIndex);
// copy original formula but adjust 3D refs to the new external sheet index // copy original formula but adjust 3D refs to the new external sheet index
int newExtSheetIx = getExternalSheetIndex(newSheetIndex); int newExtSheetIx = workbook.checkExternSheet(newSheetIndex);
Ptg[] ptgs = origNameRecord.getNameDefinition(); Ptg[] ptgs = origNameRecord.getNameDefinition();
for (int i=0; i< ptgs.length; i++) { for (int i=0; i< ptgs.length; i++) {
Ptg ptg = ptgs[i]; Ptg ptg = ptgs[i];

View File

@ -0,0 +1,71 @@
/* ====================================================================
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.formula;
/**
* Stores the parameters that identify the evaluation of one cell.<br/>
*/
final class CellEvaluationFrame {
private final int _sheetIndex;
private final int _srcRowNum;
private final int _srcColNum;
private final int _hashCode;
public CellEvaluationFrame(int sheetIndex, int srcRowNum, int srcColNum) {
if (sheetIndex < 0) {
throw new IllegalArgumentException("sheetIndex must not be negative");
}
_sheetIndex = sheetIndex;
_srcRowNum = srcRowNum;
_srcColNum = srcColNum;
_hashCode = sheetIndex + 17 * (srcRowNum + 17 * srcColNum);
}
public boolean equals(Object obj) {
CellEvaluationFrame other = (CellEvaluationFrame) obj;
if (_sheetIndex != other._sheetIndex) {
return false;
}
if (_srcRowNum != other._srcRowNum) {
return false;
}
if (_srcColNum != other._srcColNum) {
return false;
}
return true;
}
public int hashCode() {
return _hashCode;
}
/**
* @return human readable string for debug purposes
*/
public String formatAsString() {
return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _sheetIndex;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,54 @@
package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFSheet;
final class CellEvaluator {
private final WorkbookEvaluator _bookEvaluator;
private final EvaluationTracker _tracker;
public CellEvaluator(WorkbookEvaluator bookEvaluator, EvaluationTracker tracker) {
_bookEvaluator = bookEvaluator;
_tracker = tracker;
}
/**
* Given a cell, find its type and from that create an appropriate ValueEval
* impl instance and return that. Since the cell could be an external
* reference, we need the sheet that this belongs to.
* Non existent cells are treated as empty.
*/
public ValueEval getEvalForCell(HSSFCell cell) {
if (cell == null) {
return BlankEval.INSTANCE;
}
switch (cell.getCellType()) {
case HSSFCell.CELL_TYPE_NUMERIC:
return new NumberEval(cell.getNumericCellValue());
case HSSFCell.CELL_TYPE_STRING:
return new StringEval(cell.getRichStringCellValue().getString());
case HSSFCell.CELL_TYPE_FORMULA:
return _bookEvaluator.internalEvaluate(cell, _tracker);
case HSSFCell.CELL_TYPE_BOOLEAN:
return BoolEval.valueOf(cell.getBooleanCellValue());
case HSSFCell.CELL_TYPE_BLANK:
return BlankEval.INSTANCE;
case HSSFCell.CELL_TYPE_ERROR:
return ErrorEval.valueOf(cell.getErrorCellValue());
}
throw new RuntimeException("Unexpected cell type (" + cell.getCellType() + ")");
}
public String getSheetName(HSSFSheet sheet) {
return _bookEvaluator.getSheetName(sheet);
}
}

View File

@ -15,12 +15,14 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
/** /**
* Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously * Performance optimisation for {@link HSSFFormulaEvaluator}. This class stores previously
@ -31,55 +33,29 @@ import org.apache.poi.hssf.record.formula.eval.ValueEval;
* @author Josh Micich * @author Josh Micich
*/ */
final class EvaluationCache { final class EvaluationCache {
private static final class Key {
private final int _sheetIndex;
private final int _srcRowNum;
private final int _srcColNum;
private final int _hashCode;
public Key(int sheetIndex, int srcRowNum, int srcColNum) {
_sheetIndex = sheetIndex;
_srcRowNum = srcRowNum;
_srcColNum = srcColNum;
_hashCode = sheetIndex + srcRowNum + srcColNum;
}
public int hashCode() {
return _hashCode;
}
public boolean equals(Object obj) {
Key other = (Key) obj;
if (_hashCode != other._hashCode) {
return false;
}
if (_sheetIndex != other._sheetIndex) {
return false;
}
if (_srcRowNum != other._srcRowNum) {
return false;
}
if (_srcColNum != other._srcColNum) {
return false;
}
return true;
}
}
private static final CellEvaluationFrame[] EMPTY_CEF_ARRAY = { };
private final Map _valuesByKey; private final Map _valuesByKey;
private final Map _consumingCellsByDest;
/* package */EvaluationCache() { /* package */EvaluationCache() {
_valuesByKey = new HashMap(); _valuesByKey = new HashMap();
_consumingCellsByDest = new HashMap();
} }
public ValueEval getValue(int sheetIndex, int srcRowNum, int srcColNum) { public ValueEval getValue(int sheetIndex, int srcRowNum, int srcColNum) {
Key key = new Key(sheetIndex, srcRowNum, srcColNum); return getValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum));
}
/* package */ ValueEval getValue(CellEvaluationFrame key) {
return (ValueEval) _valuesByKey.get(key); return (ValueEval) _valuesByKey.get(key);
} }
public void setValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) { public void setValue(int sheetIndex, int srcRowNum, int srcColNum, ValueEval value) {
Key key = new Key(sheetIndex, srcRowNum, srcColNum); setValue(new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum), value);
}
/* package */ void setValue(CellEvaluationFrame key, ValueEval value) {
if (_valuesByKey.containsKey(key)) { if (_valuesByKey.containsKey(key)) {
throw new RuntimeException("Already have cached value for this cell"); throw new RuntimeException("Already have cached value for this cell");
} }
@ -92,4 +68,32 @@ final class EvaluationCache {
public void clear() { public void clear() {
_valuesByKey.clear(); _valuesByKey.clear();
} }
public void clearValue(int sheetIndex, int rowIndex, int columnIndex) {
clearValuesRecursive(new CellEvaluationFrame(sheetIndex, rowIndex, columnIndex));
}
private void clearValuesRecursive(CellEvaluationFrame cef) {
CellEvaluationFrame[] consumingCells = getConsumingCells(cef);
for (int i = 0; i < consumingCells.length; i++) {
clearValuesRecursive(consumingCells[i]);
}
_valuesByKey.remove(cef);
_consumingCellsByDest.remove(cef);
}
private CellEvaluationFrame[] getConsumingCells(CellEvaluationFrame cef) {
List temp = (List) _consumingCellsByDest.get(cef);
if (temp == null) {
return EMPTY_CEF_ARRAY;
}
int nItems = temp.size();
if (temp.size() < 1) {
return EMPTY_CEF_ARRAY;
}
CellEvaluationFrame[] result = new CellEvaluationFrame[nItems];
temp.toArray(result);
return result;
}
} }

View File

@ -0,0 +1,41 @@
/* ====================================================================
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.formula;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.Ptg;
/**
* Abstracts a name record for formula evaluation.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface EvaluationName {
String getNameText();
boolean isFunctionName();
boolean hasFormula();
Ptg[] getNameDefinition();
boolean isRange();
NamePtg createPtg();
}

View File

@ -15,81 +15,31 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
/** /**
* Instances of this class keep track of multiple dependent cell evaluations due * Instances of this class keep track of multiple dependent cell evaluations due
* to recursive calls to <tt>HSSFFormulaEvaluator.internalEvaluate()</tt>. * to recursive calls to {@link WorkbookEvaluator#evaluate(HSSFCell)}
* The main purpose of this class is to detect an attempt to evaluate a cell * The main purpose of this class is to detect an attempt to evaluate a cell
* that is already being evaluated. In other words, it detects circular * that is already being evaluated. In other words, it detects circular
* references in spreadsheet formulas. * references in spreadsheet formulas.
* *
* @author Josh Micich * @author Josh Micich
*/ */
final class EvaluationCycleDetector { final class EvaluationTracker {
/**
* Stores the parameters that identify the evaluation of one cell.<br/>
*/
private static final class CellEvaluationFrame {
private final HSSFWorkbook _workbook;
private final int _sheetIndex;
private final int _srcRowNum;
private final int _srcColNum;
public CellEvaluationFrame(HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
if (workbook == null) {
throw new IllegalArgumentException("workbook must not be null");
}
if (sheetIndex < 0) {
throw new IllegalArgumentException("sheetIndex must not be negative");
}
_workbook = workbook;
_sheetIndex = sheetIndex;
_srcRowNum = srcRowNum;
_srcColNum = srcColNum;
}
public boolean equals(Object obj) {
CellEvaluationFrame other = (CellEvaluationFrame) obj;
if (_workbook != other._workbook) {
return false;
}
if (_sheetIndex != other._sheetIndex) {
return false;
}
if (_srcRowNum != other._srcRowNum) {
return false;
}
if (_srcColNum != other._srcColNum) {
return false;
}
return true;
}
/**
* @return human readable string for debug purposes
*/
public String formatAsString() {
return "R=" + _srcRowNum + " C=" + _srcColNum + " ShIx=" + _sheetIndex;
}
public String toString() {
StringBuffer sb = new StringBuffer(64);
sb.append(getClass().getName()).append(" [");
sb.append(formatAsString());
sb.append("]");
return sb.toString();
}
}
private final List _evaluationFrames; private final List _evaluationFrames;
private final EvaluationCache _cache;
public EvaluationCycleDetector() { public EvaluationTracker(EvaluationCache cache) {
_cache = cache;
_evaluationFrames = new ArrayList(); _evaluationFrames = new ArrayList();
} }
@ -108,13 +58,16 @@ final class EvaluationCycleDetector {
* @return <code>true</code> if the specified cell has not been visited yet in the current * @return <code>true</code> if the specified cell has not been visited yet in the current
* evaluation. <code>false</code> if the specified cell is already being evaluated. * evaluation. <code>false</code> if the specified cell is already being evaluated.
*/ */
public boolean startEvaluate(HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { public ValueEval startEvaluate(int sheetIndex, int srcRowNum, int srcColNum) {
CellEvaluationFrame cef = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); CellEvaluationFrame cef = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
if (_evaluationFrames.contains(cef)) { if (_evaluationFrames.contains(cef)) {
return false; return ErrorEval.CIRCULAR_REF_ERROR;
} }
ValueEval result = _cache.getValue(cef);
if (result == null) {
_evaluationFrames.add(cef); _evaluationFrames.add(cef);
return true; }
return result;
} }
/** /**
@ -128,8 +81,9 @@ final class EvaluationCycleDetector {
* Assuming a well behaved client, parameters to this method would not be * Assuming a well behaved client, parameters to this method would not be
* required. However, they have been included to assert correct behaviour, * required. However, they have been included to assert correct behaviour,
* and form more meaningful error messages. * and form more meaningful error messages.
* @param result
*/ */
public void endEvaluate(HSSFWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) { public void endEvaluate(int sheetIndex, int srcRowNum, int srcColNum, ValueEval result) {
int nFrames = _evaluationFrames.size(); int nFrames = _evaluationFrames.size();
if (nFrames < 1) { if (nFrames < 1) {
throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate"); throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
@ -137,7 +91,7 @@ final class EvaluationCycleDetector {
nFrames--; nFrames--;
CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames); CellEvaluationFrame cefExpected = (CellEvaluationFrame) _evaluationFrames.get(nFrames);
CellEvaluationFrame cefActual = new CellEvaluationFrame(workbook, sheetIndex, srcRowNum, srcColNum); CellEvaluationFrame cefActual = new CellEvaluationFrame(sheetIndex, srcRowNum, srcColNum);
if (!cefActual.equals(cefExpected)) { if (!cefActual.equals(cefExpected)) {
throw new RuntimeException("Wrong cell specified. " throw new RuntimeException("Wrong cell specified. "
+ "Corresponding startEvaluate() call was for cell {" + "Corresponding startEvaluate() call was for cell {"
@ -146,5 +100,7 @@ final class EvaluationCycleDetector {
} }
// else - no problems so pop current frame // else - no problems so pop current frame
_evaluationFrames.remove(nFrames); _evaluationFrames.remove(nFrames);
_cache.setValue(cefActual, result);
} }
} }

View File

@ -0,0 +1,42 @@
/* ====================================================================
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.formula;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFSheet;
/**
* Abstracts a workbook for the purpose of formula evaluation.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface EvaluationWorkbook {
String getSheetName(int sheetIndex);
int getSheetIndex(HSSFSheet sheet);
HSSFSheet getSheet(int sheetIndex);
HSSFSheet getSheetByExternSheetIndex(int externSheetIndex);
EvaluationName getName(NamePtg namePtg);
String resolveNameXText(NameXPtg ptg);
Ptg[] getFormulaTokens(HSSFCell cell);
}

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.model; package org.apache.poi.ss.formula;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -58,14 +58,9 @@ import org.apache.poi.hssf.record.formula.UnaryPlusPtg;
import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
import org.apache.poi.hssf.usermodel.HSSFErrorConstants; import org.apache.poi.hssf.usermodel.HSSFErrorConstants;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFName;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.AreaReference; import org.apache.poi.hssf.util.AreaReference;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.hssf.util.CellReference.NameType; import org.apache.poi.hssf.util.CellReference.NameType;
import org.apache.poi.ss.formula.FormulaRenderer;
/** /**
* This class parses a formula string into a List of tokens in RPN order. * This class parses a formula string into a List of tokens in RPN order.
@ -83,6 +78,7 @@ import org.apache.poi.ss.formula.FormulaRenderer;
* @author Cameron Riley (criley at ekmail.com) * @author Cameron Riley (criley at ekmail.com)
* @author Peter M. Murray (pete at quantrix dot com) * @author Peter M. Murray (pete at quantrix dot com)
* @author Pavel Krupets (pkrupets at palmtreebusiness dot com) * @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
* @author Josh Micich
*/ */
public final class FormulaParser { public final class FormulaParser {
@ -99,14 +95,6 @@ public final class FormulaParser {
} }
} }
public static final int FORMULA_TYPE_CELL = 0;
public static final int FORMULA_TYPE_SHARED = 1;
public static final int FORMULA_TYPE_ARRAY =2;
public static final int FORMULA_TYPE_CONDFORMAT = 3;
public static final int FORMULA_TYPE_NAMEDRANGE = 4;
// this constant is currently very specific. The exact differences from general data
// validation formulas or conditional format formulas is not known yet
public static final int FORMULA_TYPE_DATAVALIDATION_LIST = 5;
private final String formulaString; private final String formulaString;
private final int formulaLength; private final int formulaLength;
@ -122,7 +110,8 @@ public final class FormulaParser {
*/ */
private char look; private char look;
private HSSFWorkbook book; private FormulaParsingWorkbook book;
/** /**
@ -137,19 +126,19 @@ public final class FormulaParser {
* model.Workbook, then use the convenience method on * model.Workbook, then use the convenience method on
* usermodel.HSSFFormulaEvaluator * usermodel.HSSFFormulaEvaluator
*/ */
public FormulaParser(String formula, HSSFWorkbook book){ private FormulaParser(String formula, FormulaParsingWorkbook book){
formulaString = formula; formulaString = formula;
pointer=0; pointer=0;
this.book = book; this.book = book;
formulaLength = formulaString.length(); formulaLength = formulaString.length();
} }
public static Ptg[] parse(String formula, HSSFWorkbook book) { public static Ptg[] parse(String formula, FormulaParsingWorkbook book) {
return parse(formula, book, FORMULA_TYPE_CELL); return parse(formula, book, FormulaType.CELL);
} }
public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType) { public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType) {
FormulaParser fp = HSSFFormulaEvaluator.getUnderlyingParser(workbook, formula); FormulaParser fp = new FormulaParser(formula, workbook);
fp.parse(); fp.parse();
return fp.getRPNPtg(formulaType); return fp.getRPNPtg(formulaType);
} }
@ -308,7 +297,7 @@ public final class FormulaParser {
Match('!'); Match('!');
String sheetName = name; String sheetName = name;
String first = parseIdentifier(); String first = parseIdentifier();
short externIdx = book.getExternalSheetIndex(book.getSheetIndex(sheetName)); int externIdx = book.getExternalSheetIndex(sheetName);
areaRef = parseArea(name); areaRef = parseArea(name);
if (areaRef != null) { if (areaRef != null) {
// will happen if dots are used instead of colon // will happen if dots are used instead of colon
@ -330,7 +319,7 @@ public final class FormulaParser {
} }
return new Area3DPtg(first+":"+second,externIdx); return new Area3DPtg(first+":"+second,externIdx);
} }
return new Ref3DPtg(first,externIdx); return new Ref3DPtg(first, externIdx);
} }
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new BoolPtg(name.toUpperCase()); return new BoolPtg(name.toUpperCase());
@ -346,16 +335,17 @@ public final class FormulaParser {
new FormulaParseException("Name '" + name new FormulaParseException("Name '" + name
+ "' does not look like a cell reference or named range"); + "' does not look like a cell reference or named range");
} }
EvaluationName evalName = book.getName(name);
for(int i = 0; i < book.getNumberOfNames(); i++) { if (evalName == null) {
// named range name matching is case insensitive
if(book.getNameAt(i).getNameName().equalsIgnoreCase(name)) {
return new NamePtg(i);
}
}
throw new FormulaParseException("Specified named range '" throw new FormulaParseException("Specified named range '"
+ name + "' does not exist in the current workbook."); + name + "' does not exist in the current workbook.");
} }
if (evalName.isRange()) {
return evalName.createPtg();
}
throw new FormulaParseException("Specified name '"
+ name + "' is not a range as expected");
}
/** /**
* @param name an 'identifier' like string (i.e. contains alphanums, and dots) * @param name an 'identifier' like string (i.e. contains alphanums, and dots)
@ -410,10 +400,15 @@ public final class FormulaParser {
// user defined function // user defined function
// in the token tree, the name is more or less the first argument // in the token tree, the name is more or less the first argument
EvaluationName hName = book.getName(name);
if (hName == null) {
int nameIndex = book.getNameIndex(name); nameToken = book.getNameXPtg(name);
if (nameIndex >= 0) { if (nameToken == null) {
HSSFName hName = book.getNameAt(nameIndex); throw new FormulaParseException("Name '" + name
+ "' is completely unknown in the current workbook");
}
} else {
if (!hName.isFunctionName()) { if (!hName.isFunctionName()) {
throw new FormulaParseException("Attempt to use name '" + name throw new FormulaParseException("Attempt to use name '" + name
+ "' as a function, but defined name in workbook does not refer to a function"); + "' as a function, but defined name in workbook does not refer to a function");
@ -421,14 +416,7 @@ public final class FormulaParser {
// calls to user-defined functions within the workbook // calls to user-defined functions within the workbook
// get a Name token which points to a defined name record // get a Name token which points to a defined name record
nameToken = new NamePtg(nameIndex); nameToken = hName.createPtg();
} else {
nameToken = book.getNameXPtg(name);
if (nameToken == null) {
throw new FormulaParseException("Name '" + name
+ "' is completely unknown in the current workbook");
}
} }
} }
@ -965,9 +953,9 @@ end;
/** /**
* API call to execute the parsing of the formula * API call to execute the parsing of the formula
* @deprecated use {@link #parse(String, HSSFWorkbook)} directly *
*/ */
public void parse() { private void parse() {
pointer=0; pointer=0;
GetChar(); GetChar();
_rootNode = comparisonExpression(); _rootNode = comparisonExpression();
@ -979,60 +967,10 @@ end;
} }
} }
private Ptg[] getRPNPtg(int formulaType) {
/*********************************
* PARSER IMPLEMENTATION ENDS HERE
* EXCEL SPECIFIC METHODS BELOW
*******************************/
/** API call to retrive the array of Ptgs created as
* a result of the parsing
*/
public Ptg[] getRPNPtg() {
return getRPNPtg(FORMULA_TYPE_CELL);
}
public Ptg[] getRPNPtg(int formulaType) {
OperandClassTransformer oct = new OperandClassTransformer(formulaType); OperandClassTransformer oct = new OperandClassTransformer(formulaType);
// RVA is for 'operand class': 'reference', 'value', 'array' // RVA is for 'operand class': 'reference', 'value', 'array'
oct.transformFormula(_rootNode); oct.transformFormula(_rootNode);
return ParseNode.toTokenArray(_rootNode); return ParseNode.toTokenArray(_rootNode);
} }
/**
* Convenience method which takes in a list then passes it to the
* other toFormulaString signature.
* @param book workbook for 3D and named references
* @param lptgs list of Ptg, can be null or empty
* @return a human readable String
*/
public static String toFormulaString(HSSFWorkbook book, List lptgs) {
String retval = null;
if (lptgs == null || lptgs.size() == 0) return "#NAME";
Ptg[] ptgs = new Ptg[lptgs.size()];
ptgs = (Ptg[])lptgs.toArray(ptgs);
retval = toFormulaString(book, ptgs);
return retval;
}
/**
* Convenience method which takes in a list then passes it to the
* other toFormulaString signature. Works on the current
* workbook for 3D and named references
* @param lptgs list of Ptg, can be null or empty
* @return a human readable String
*/
public String toFormulaString(List lptgs) {
return toFormulaString(book, lptgs);
}
/**
* Static method to convert an array of Ptgs in RPN order
* to a human readable string format in infix mode.
* @param book workbook for named and 3D references
* @param ptgs array of Ptg, can be null or empty
* @return a human readable String
*/
public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
}
} }

View File

@ -0,0 +1,37 @@
/* ====================================================================
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.formula;
import org.apache.poi.hssf.record.formula.NameXPtg;
/**
* Abstracts a workbook for the purpose of formula parsing.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface FormulaParsingWorkbook {
/**
* named range name matching is case insensitive
*/
EvaluationName getName(String name);
int getExternalSheetIndex(String sheetName);
NameXPtg getNameXPtg(String name);
}

View File

@ -1,3 +1,20 @@
/* ====================================================================
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.formula; package org.apache.poi.ss.formula;
import java.util.List; import java.util.List;
@ -11,6 +28,13 @@ import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.ParenthesisPtg; import org.apache.poi.hssf.record.formula.ParenthesisPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
/**
* Common logic for rendering formulas.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public class FormulaRenderer { public class FormulaRenderer {
/** /**
* Convenience method which takes in a list then passes it to the * Convenience method which takes in a list then passes it to the

View File

@ -1,8 +1,32 @@
/* ====================================================================
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.formula; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg; import org.apache.poi.hssf.record.formula.NameXPtg;
/**
* Abstracts a workbook for the purpose of converting formula to text.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface FormulaRenderingWorkbook { public interface FormulaRenderingWorkbook {
String getSheetNameByExternSheet(int externSheetIndex); String getSheetNameByExternSheet(int externSheetIndex);

View File

@ -15,32 +15,26 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
/** /**
* This class makes an <tt>EvaluationCycleDetector</tt> instance available to * Enumeration of various formula types.<br/>
* each thread via a <tt>ThreadLocal</tt> in order to avoid adding a parameter *
* to a few protected methods within <tt>HSSFFormulaEvaluator</tt>. * For POI internal use only
* *
* @author Josh Micich * @author Josh Micich
*/ */
final class EvaluationCycleDetectorManager { public final class FormulaType {
private FormulaType() {
ThreadLocal tl = null;
private static ThreadLocal _tlEvaluationTracker = new ThreadLocal() {
protected synchronized Object initialValue() {
return new EvaluationCycleDetector();
}
};
/**
* @return
*/
public static EvaluationCycleDetector getTracker() {
return (EvaluationCycleDetector) _tlEvaluationTracker.get();
}
private EvaluationCycleDetectorManager() {
// no instances of this class // no instances of this class
} }
public static final int CELL = 0;
public static final int SHARED = 1;
public static final int ARRAY =2;
public static final int CONDFORMAT = 3;
public static final int NAMEDRANGE = 4;
// this constant is currently very specific. The exact differences from general data
// validation formulas or conditional format formulas is not known yet
public static final int DATAVALIDATION_LIST = 5;
} }

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.AreaI; import org.apache.poi.hssf.record.formula.AreaI;
import org.apache.poi.hssf.record.formula.AreaI.OffsetArea; import org.apache.poi.hssf.record.formula.AreaI.OffsetArea;
@ -23,6 +23,9 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.AreaEvalBase; import org.apache.poi.hssf.record.formula.eval.AreaEvalBase;
import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
/** /**
@ -32,9 +35,9 @@ import org.apache.poi.hssf.util.CellReference;
final class LazyAreaEval extends AreaEvalBase { final class LazyAreaEval extends AreaEvalBase {
private final HSSFSheet _sheet; private final HSSFSheet _sheet;
private HSSFFormulaEvaluator _evaluator; private final CellEvaluator _evaluator;
public LazyAreaEval(AreaI ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { public LazyAreaEval(AreaI ptg, HSSFSheet sheet, CellEvaluator evaluator) {
super(ptg); super(ptg);
_sheet = sheet; _sheet = sheet;
_evaluator = evaluator; _evaluator = evaluator;

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.AreaI; import org.apache.poi.hssf.record.formula.AreaI;
import org.apache.poi.hssf.record.formula.Ref3DPtg; import org.apache.poi.hssf.record.formula.Ref3DPtg;
@ -25,6 +25,9 @@ import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval; import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.RefEvalBase; import org.apache.poi.hssf.record.formula.eval.RefEvalBase;
import org.apache.poi.hssf.record.formula.eval.ValueEval; import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellReference; import org.apache.poi.hssf.util.CellReference;
/** /**
@ -34,15 +37,15 @@ import org.apache.poi.hssf.util.CellReference;
final class LazyRefEval extends RefEvalBase { final class LazyRefEval extends RefEvalBase {
private final HSSFSheet _sheet; private final HSSFSheet _sheet;
private final HSSFFormulaEvaluator _evaluator; private final CellEvaluator _evaluator;
public LazyRefEval(RefPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { public LazyRefEval(RefPtg ptg, HSSFSheet sheet, CellEvaluator evaluator) {
super(ptg.getRow(), ptg.getColumn()); super(ptg.getRow(), ptg.getColumn());
_sheet = sheet; _sheet = sheet;
_evaluator = evaluator; _evaluator = evaluator;
} }
public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, HSSFFormulaEvaluator evaluator) { public LazyRefEval(Ref3DPtg ptg, HSSFSheet sheet, CellEvaluator evaluator) {
super(ptg.getRow(), ptg.getColumn()); super(ptg.getRow(), ptg.getColumn());
_sheet = sheet; _sheet = sheet;
_evaluator = evaluator; _evaluator = evaluator;

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.model; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.ControlPtg; import org.apache.poi.hssf.record.formula.ControlPtg;
@ -63,10 +63,10 @@ final class OperandClassTransformer {
public void transformFormula(ParseNode rootNode) { public void transformFormula(ParseNode rootNode) {
byte rootNodeOperandClass; byte rootNodeOperandClass;
switch (_formulaType) { switch (_formulaType) {
case FormulaParser.FORMULA_TYPE_CELL: case FormulaType.CELL:
rootNodeOperandClass = Ptg.CLASS_VALUE; rootNodeOperandClass = Ptg.CLASS_VALUE;
break; break;
case FormulaParser.FORMULA_TYPE_DATAVALIDATION_LIST: case FormulaType.DATAVALIDATION_LIST:
rootNodeOperandClass = Ptg.CLASS_REF; rootNodeOperandClass = Ptg.CLASS_REF;
break; break;
default: default:

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.usermodel; package org.apache.poi.ss.formula;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;

View File

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.model; package org.apache.poi.ss.formula;
import org.apache.poi.hssf.record.formula.AttrPtg; import org.apache.poi.hssf.record.formula.AttrPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg;

View File

@ -1,5 +1,30 @@
/* ====================================================================
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.formula; package org.apache.poi.ss.formula;
/**
* Should be implemented by any {@link Ptg} subclass that needs a workbook to render its formula.
* <br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public interface WorkbookDependentFormula { public interface WorkbookDependentFormula {
String toFormulaString(FormulaRenderingWorkbook book); String toFormulaString(FormulaRenderingWorkbook book);
} }

View File

@ -0,0 +1,348 @@
/* ====================================================================
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.formula;
import java.util.Stack;
import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.hssf.record.formula.AreaPtg;
import org.apache.poi.hssf.record.formula.BoolPtg;
import org.apache.poi.hssf.record.formula.ControlPtg;
import org.apache.poi.hssf.record.formula.ErrPtg;
import org.apache.poi.hssf.record.formula.IntPtg;
import org.apache.poi.hssf.record.formula.MemErrPtg;
import org.apache.poi.hssf.record.formula.MissingArgPtg;
import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.NumberPtg;
import org.apache.poi.hssf.record.formula.OperationPtg;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.record.formula.Ref3DPtg;
import org.apache.poi.hssf.record.formula.RefPtg;
import org.apache.poi.hssf.record.formula.StringPtg;
import org.apache.poi.hssf.record.formula.UnionPtg;
import org.apache.poi.hssf.record.formula.UnknownPtg;
import org.apache.poi.hssf.record.formula.eval.AreaEval;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
import org.apache.poi.hssf.record.formula.eval.BoolEval;
import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.Eval;
import org.apache.poi.hssf.record.formula.eval.FunctionEval;
import org.apache.poi.hssf.record.formula.eval.NameEval;
import org.apache.poi.hssf.record.formula.eval.NameXEval;
import org.apache.poi.hssf.record.formula.eval.NumberEval;
import org.apache.poi.hssf.record.formula.eval.OperationEval;
import org.apache.poi.hssf.record.formula.eval.RefEval;
import org.apache.poi.hssf.record.formula.eval.StringEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellReference;
/**
* Evaluates formula cells.<p/>
*
* For performance reasons, this class keeps a cache of all previously calculated intermediate
* cell values. Be sure to call {@link #clearCache()} if any workbook cells are changed between
* calls to evaluate~ methods on this class.<br/>
*
* For POI internal use only
*
* @author Josh Micich
*/
public class WorkbookEvaluator {
/**
* used to track the number of evaluations
*/
private static final class Counter {
public int value;
public int depth;
public Counter() {
value = 0;
}
}
private final EvaluationWorkbook _workbook;
private final EvaluationCache _cache;
private Counter _evaluationCounter;
public WorkbookEvaluator(EvaluationWorkbook workbook) {
_workbook = workbook;
_cache = new EvaluationCache();
_evaluationCounter = new Counter();
}
/**
* for debug use. Used in toString methods
*/
/* package */ String getSheetName(HSSFSheet sheet) {
return getSheetName(getSheetIndex(sheet));
}
private String getSheetName(int sheetIndex) {
return _workbook.getSheetName(sheetIndex);
}
/**
* for debug/test use
*/
public int getEvaluationCount() {
return _evaluationCounter.value;
}
private static boolean isDebugLogEnabled() {
return false;
}
private static void logDebug(String s) {
if (isDebugLogEnabled()) {
System.out.println(s);
}
}
/**
* Should be called whenever there are changes to input cells in the evaluated workbook.
* Failure to call this method after changing cell values will cause incorrect behaviour
* of the evaluate~ methods of this class
*/
public void clearAllCachedResultValues() {
_cache.clear();
}
public void clearCachedResultValue(HSSFSheet sheet, int rowIndex, int columnIndex) {
int sheetIndex = getSheetIndex(sheet);
_cache.clearValue(sheetIndex, rowIndex, columnIndex);
}
private int getSheetIndex(HSSFSheet sheet) {
// TODO cache sheet indexes too
return _workbook.getSheetIndex(sheet);
}
public ValueEval evaluate(HSSFCell srcCell) {
return internalEvaluate(srcCell, new EvaluationTracker(_cache));
}
/**
* Dev. Note: Internal evaluate must be passed only a formula cell
* else a runtime exception will be thrown somewhere inside the method.
* (Hence this is a private method.)
* @return never <code>null</code>, never {@link BlankEval}
*/
/* package */ ValueEval internalEvaluate(HSSFCell srcCell, EvaluationTracker tracker) {
int srcRowNum = srcCell.getRowIndex();
int srcColNum = srcCell.getCellNum();
ValueEval result;
int sheetIndex = getSheetIndex(srcCell.getSheet());
result = tracker.startEvaluate(sheetIndex, srcRowNum, srcColNum);
if (result != null) {
return result;
}
_evaluationCounter.value++;
_evaluationCounter.depth++;
try {
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
result = evaluateCell(sheetIndex, srcRowNum, (short)srcColNum, ptgs, tracker);
} finally {
tracker.endEvaluate(sheetIndex, srcRowNum, srcColNum, result);
_evaluationCounter.depth--;
}
if (isDebugLogEnabled()) {
String sheetName = getSheetName(sheetIndex);
CellReference cr = new CellReference(srcRowNum, srcColNum);
logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
}
return result;
}
private ValueEval evaluateCell(int sheetIndex, int srcRowNum, short srcColNum, Ptg[] ptgs, EvaluationTracker tracker) {
Stack stack = new Stack();
for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
// since we don't know how to handle these yet :(
Ptg ptg = ptgs[i];
if (ptg instanceof ControlPtg) {
// skip Parentheses, Attr, etc
continue;
}
if (ptg instanceof MemErrPtg) { continue; }
if (ptg instanceof MissingArgPtg) {
// TODO - might need to push BlankEval or MissingArgEval
continue;
}
Eval opResult;
if (ptg instanceof OperationPtg) {
OperationPtg optg = (OperationPtg) ptg;
if (optg instanceof UnionPtg) { continue; }
OperationEval operation = OperationEvaluatorFactory.create(optg);
int numops = operation.getNumberOfOperands();
Eval[] ops = new Eval[numops];
// storing the ops in reverse order since they are popping
for (int j = numops - 1; j >= 0; j--) {
Eval p = (Eval) stack.pop();
ops[j] = p;
}
// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
opResult = invokeOperation(operation, ops, _workbook, sheetIndex, srcRowNum, srcColNum);
} else {
opResult = getEvalForPtg(ptg, sheetIndex, tracker);
}
if (opResult == null) {
throw new RuntimeException("Evaluation result must not be null");
}
// logDebug("push " + opResult);
stack.push(opResult);
}
ValueEval value = ((ValueEval) stack.pop());
if (!stack.isEmpty()) {
throw new IllegalStateException("evaluation stack not empty");
}
value = dereferenceValue(value, srcRowNum, srcColNum);
if (value == BlankEval.INSTANCE) {
// Note Excel behaviour here. A blank final final value is converted to zero.
return NumberEval.ZERO;
// Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
// blank, the actual value is empty string. This can be verified with ISBLANK().
}
return value;
}
/**
* Dereferences a single value from any AreaEval or RefEval evaluation result.
* If the supplied evaluationResult is just a plain value, it is returned as-is.
* @return a <tt>NumberEval</tt>, <tt>StringEval</tt>, <tt>BoolEval</tt>,
* <tt>BlankEval</tt> or <tt>ErrorEval</tt>. Never <code>null</code>.
*/
private static ValueEval dereferenceValue(ValueEval evaluationResult, int srcRowNum, short srcColNum) {
if (evaluationResult instanceof RefEval) {
RefEval rv = (RefEval) evaluationResult;
return rv.getInnerValueEval();
}
if (evaluationResult instanceof AreaEval) {
AreaEval ae = (AreaEval) evaluationResult;
if (ae.isRow()) {
if(ae.isColumn()) {
return ae.getRelativeValue(0, 0);
}
return ae.getValueAt(ae.getFirstRow(), srcColNum);
}
if (ae.isColumn()) {
return ae.getValueAt(srcRowNum, ae.getFirstColumn());
}
return ErrorEval.VALUE_INVALID;
}
return evaluationResult;
}
private static Eval invokeOperation(OperationEval operation, Eval[] ops,
EvaluationWorkbook workbook, int sheetIndex, int srcRowNum, int srcColNum) {
if(operation instanceof FunctionEval) {
FunctionEval fe = (FunctionEval) operation;
if(fe.isFreeRefFunction()) {
return fe.getFreeRefFunction().evaluate(ops, workbook, sheetIndex, srcRowNum, srcColNum);
}
}
return operation.evaluate(ops, srcRowNum, (short)srcColNum);
}
private HSSFSheet getOtherSheet(int externSheetIndex) {
return _workbook.getSheetByExternSheetIndex(externSheetIndex);
}
/**
* returns an appropriate Eval impl instance for the Ptg. The Ptg must be
* one of: Area3DPtg, AreaPtg, ReferencePtg, Ref3DPtg, IntPtg, NumberPtg,
* StringPtg, BoolPtg <br/>special Note: OperationPtg subtypes cannot be
* passed here!
*/
private Eval getEvalForPtg(Ptg ptg, int sheetIndex, EvaluationTracker tracker) {
if (ptg instanceof NamePtg) {
// named ranges, macro functions
NamePtg namePtg = (NamePtg) ptg;
EvaluationName nameRecord = _workbook.getName(namePtg);
if (nameRecord.isFunctionName()) {
return new NameEval(nameRecord.getNameText());
}
if (nameRecord.hasFormula()) {
return evaluateNameFormula(nameRecord.getNameDefinition(), sheetIndex, tracker);
}
throw new RuntimeException("Don't now how to evalate name '" + nameRecord.getNameText() + "'");
}
if (ptg instanceof NameXPtg) {
return new NameXEval(((NameXPtg) ptg));
}
if (ptg instanceof IntPtg) {
return new NumberEval(((IntPtg)ptg).getValue());
}
if (ptg instanceof NumberPtg) {
return new NumberEval(((NumberPtg)ptg).getValue());
}
if (ptg instanceof StringPtg) {
return new StringEval(((StringPtg) ptg).getValue());
}
if (ptg instanceof BoolPtg) {
return BoolEval.valueOf(((BoolPtg) ptg).getValue());
}
if (ptg instanceof ErrPtg) {
return ErrorEval.valueOf(((ErrPtg) ptg).getErrorCode());
}
CellEvaluator ce = new CellEvaluator(this, tracker);
HSSFSheet sheet = _workbook.getSheet(sheetIndex);
if (ptg instanceof RefPtg) {
return new LazyRefEval(((RefPtg) ptg), sheet, ce);
}
if (ptg instanceof AreaPtg) {
return new LazyAreaEval(((AreaPtg) ptg), sheet, ce);
}
if (ptg instanceof Ref3DPtg) {
Ref3DPtg refPtg = (Ref3DPtg) ptg;
HSSFSheet xsheet = getOtherSheet(refPtg.getExternSheetIndex());
return new LazyRefEval(refPtg, xsheet, ce);
}
if (ptg instanceof Area3DPtg) {
Area3DPtg a3dp = (Area3DPtg) ptg;
HSSFSheet xsheet = getOtherSheet(a3dp.getExternSheetIndex());
return new LazyAreaEval(a3dp, xsheet, ce);
}
if (ptg instanceof UnknownPtg) {
// POI uses UnknownPtg when the encoded Ptg array seems to be corrupted.
// This seems to occur in very rare cases (e.g. unused name formulas in bug 44774, attachment 21790)
// In any case, formulas are re-parsed before execution, so UnknownPtg should not get here
throw new RuntimeException("UnknownPtg not allowed");
}
throw new RuntimeException("Unexpected ptg class (" + ptg.getClass().getName() + ")");
}
private Eval evaluateNameFormula(Ptg[] ptgs, int sheetIndex, EvaluationTracker tracker) {
if (ptgs.length > 1) {
throw new RuntimeException("Complex name formulas not supported yet");
}
return getEvalForPtg(ptgs[0], sheetIndex, tracker);
}
}

View File

@ -26,7 +26,7 @@ import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener; import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
@ -101,7 +101,7 @@ public final class TestEventWorkbookBuilder extends TestCase {
// Check we can get the formula without breaking // Check we can get the formula without breaking
for(int i=0; i<fRecs.length; i++) { for(int i=0; i<fRecs.length; i++) {
FormulaParser.toFormulaString(stubHSSF, fRecs[i].getParsedExpression()); HSSFFormulaParser.toFormulaString(stubHSSF, fRecs[i].getParsedExpression());
} }
// Peer into just one formula, and check that // Peer into just one formula, and check that
@ -123,19 +123,19 @@ public final class TestEventWorkbookBuilder extends TestCase {
fr = fRecs[0]; fr = fRecs[0];
assertEquals(1, fr.getRow()); assertEquals(1, fr.getRow());
assertEquals(0, fr.getColumn()); assertEquals(0, fr.getColumn());
assertEquals("Sheet1!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); assertEquals("Sheet1!A1", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Sheet 1 A5 is to another sheet // Sheet 1 A5 is to another sheet
fr = fRecs[3]; fr = fRecs[3];
assertEquals(4, fr.getRow()); assertEquals(4, fr.getRow());
assertEquals(0, fr.getColumn()); assertEquals(0, fr.getColumn());
assertEquals("'S2'!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); assertEquals("'S2'!A1", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Sheet 1 A7 is to another sheet, range // Sheet 1 A7 is to another sheet, range
fr = fRecs[5]; fr = fRecs[5];
assertEquals(6, fr.getRow()); assertEquals(6, fr.getRow());
assertEquals(0, fr.getColumn()); assertEquals(0, fr.getColumn());
assertEquals("SUM(Sh3!A1:A4)", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); assertEquals("SUM(Sh3!A1:A4)", HSSFFormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()));
// Now, load via Usermodel and re-check // Now, load via Usermodel and re-check

View File

@ -21,7 +21,6 @@ import junit.framework.AssertionFailedError;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
import org.apache.poi.hssf.record.constant.ErrorConstant; import org.apache.poi.hssf.record.constant.ErrorConstant;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.AddPtg; import org.apache.poi.hssf.record.formula.AddPtg;
@ -56,6 +55,7 @@ import org.apache.poi.hssf.usermodel.HSSFName;
import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.FormulaParserTestHelper;
/** /**
* Test the low level formula parser functionality. High level tests are to * Test the low level formula parser functionality. High level tests are to
@ -67,10 +67,13 @@ public final class TestFormulaParser extends TestCase {
* @return parsed token array already confirmed not <code>null</code> * @return parsed token array already confirmed not <code>null</code>
*/ */
/* package */ static Ptg[] parseFormula(String formula) { /* package */ static Ptg[] parseFormula(String formula) {
Ptg[] result = FormulaParser.parse(formula, null); Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null);
assertNotNull("Ptg array should not be null", result); assertNotNull("Ptg array should not be null", result);
return result; return result;
} }
private static String toFormulaString(Ptg[] ptgs) {
return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs);
}
public void testSimpleFormula() { public void testSimpleFormula() {
Ptg[] ptgs = parseFormula("2+2"); Ptg[] ptgs = parseFormula("2+2");
@ -133,7 +136,7 @@ public final class TestFormulaParser extends TestCase {
HSSFWorkbook w = HSSFTestDataSamples.openSampleWorkbook("testNames.xls"); HSSFWorkbook w = HSSFTestDataSamples.openSampleWorkbook("testNames.xls");
HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(w); HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(w);
Ptg[] ptg = FormulaParser.parse("myFunc()", w); Ptg[] ptg = HSSFFormulaParser.parse("myFunc()", w);
// myFunc() actually takes 1 parameter. Don't know if POI will ever be able to detect this problem // myFunc() actually takes 1 parameter. Don't know if POI will ever be able to detect this problem
// the name gets encoded as the first arg // the name gets encoded as the first arg
@ -405,7 +408,7 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptgs = { Ptg[] ptgs = {
new FuncPtg(10), new FuncPtg(10),
}; };
assertEquals("NA()", FormulaParser.toFormulaString(book, ptgs)); assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs));
} }
public void testPercent() { public void testPercent() {
@ -639,11 +642,11 @@ public final class TestFormulaParser extends TestCase {
String formulaString; String formulaString;
Ptg[] ptgs; Ptg[] ptgs;
ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))"); ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))");
formulaString = FormulaParser.toFormulaString(null, ptgs); formulaString = toFormulaString(ptgs);
assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString); assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString);
ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)"); ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)");
formulaString = FormulaParser.toFormulaString(null, ptgs); formulaString = toFormulaString(ptgs);
assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString); assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString);
} }
public void testParserErrors() { public void testParserErrors() {
@ -665,12 +668,9 @@ public final class TestFormulaParser extends TestCase {
try { try {
parseFormula(formula); parseFormula(formula);
throw new AssertionFailedError("expected parse exception"); throw new AssertionFailedError("expected parse exception");
} catch (FormulaParseException e) {
// expected during successful test
assertNotNull(e.getMessage());
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); // expected during successful test
fail("Wrong exception:" + e.getMessage()); FormulaParserTestHelper.confirmParseException(e);
} }
} }
@ -697,7 +697,7 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptgs = { spacePtg, new IntPtg(4), }; Ptg[] ptgs = { spacePtg, new IntPtg(4), };
String formulaString; String formulaString;
try { try {
formulaString = FormulaParser.toFormulaString(null, ptgs); formulaString = toFormulaString(ptgs);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) { if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) {
throw new AssertionFailedError("Identified bug 44609"); throw new AssertionFailedError("Identified bug 44609");
@ -709,7 +709,7 @@ public final class TestFormulaParser extends TestCase {
assertEquals("4", formulaString); assertEquals("4", formulaString);
ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, }; ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, };
formulaString = FormulaParser.toFormulaString(null, ptgs); formulaString = toFormulaString(ptgs);
assertEquals("3+4", formulaString); assertEquals("3+4", formulaString);
} }
@ -725,7 +725,7 @@ public final class TestFormulaParser extends TestCase {
DividePtg.instance, DividePtg.instance,
}; };
try { try {
FormulaParser.toFormulaString(null, ptgs); toFormulaString(ptgs);
fail("Expected exception was not thrown"); fail("Expected exception was not thrown");
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// expected during successful test // expected during successful test
@ -764,10 +764,10 @@ public final class TestFormulaParser extends TestCase {
private static void confirmArgCountMsg(String formula, String expectedMessage) { private static void confirmArgCountMsg(String formula, String expectedMessage) {
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
try { try {
FormulaParser.parse(formula, book); HSSFFormulaParser.parse(formula, book);
throw new AssertionFailedError("Didn't get parse exception as expected"); throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (FormulaParseException e) { } catch (RuntimeException e) {
assertEquals(expectedMessage, e.getMessage()); FormulaParserTestHelper.confirmParseException(e, expectedMessage);
} }
} }
@ -776,15 +776,17 @@ public final class TestFormulaParser extends TestCase {
try { try {
parseFormula("round(3.14;2)"); parseFormula("round(3.14;2)");
throw new AssertionFailedError("Didn't get parse exception as expected"); throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (FormulaParseException e) { } catch (RuntimeException e) {
assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage()); FormulaParserTestHelper.confirmParseException(e,
"Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'");
} }
try { try {
parseFormula(" =2+2"); parseFormula(" =2+2");
throw new AssertionFailedError("Didn't get parse exception as expected"); throw new AssertionFailedError("Didn't get parse exception as expected");
} catch (FormulaParseException e) { } catch (RuntimeException e) {
assertEquals("The specified formula ' =2+2' starts with an equals sign which is not allowed.", e.getMessage()); FormulaParserTestHelper.confirmParseException(e,
"The specified formula ' =2+2' starts with an equals sign which is not allowed.");
} }
} }
@ -819,7 +821,7 @@ public final class TestFormulaParser extends TestCase {
Ptg[] ptgs; Ptg[] ptgs;
try { try {
ptgs = FormulaParser.parse("count(pfy1)", wb); ptgs = HSSFFormulaParser.parse("count(pfy1)", wb);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
if (e.getMessage().equals("Specified colIx (1012) is out of range")) { if (e.getMessage().equals("Specified colIx (1012) is out of range")) {
throw new AssertionFailedError("Identified bug 45354"); throw new AssertionFailedError("Identified bug 45354");
@ -835,10 +837,9 @@ public final class TestFormulaParser extends TestCase {
try { try {
cell.setCellFormula("count(pf1)"); cell.setCellFormula("count(pf1)");
throw new AssertionFailedError("Expected formula parse execption"); throw new AssertionFailedError("Expected formula parse execption");
} catch (FormulaParseException e) { } catch (RuntimeException e) {
if (!e.getMessage().equals("Specified named range 'pf1' does not exist in the current workbook.")) { FormulaParserTestHelper.confirmParseException(e,
throw e; "Specified named range 'pf1' does not exist in the current workbook.");
}
} }
cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
} }
@ -850,14 +851,14 @@ public final class TestFormulaParser extends TestCase {
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
book.createSheet("Sheet1"); book.createSheet("Sheet1");
ptgs = FormulaParser.parse("Sheet1!A10:A40000", book); ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
aptg = (AreaI) ptgs[0]; aptg = (AreaI) ptgs[0];
if (aptg.getLastRow() == -25537) { if (aptg.getLastRow() == -25537) {
throw new AssertionFailedError("Identified bug 45358"); throw new AssertionFailedError("Identified bug 45358");
} }
assertEquals(39999, aptg.getLastRow()); assertEquals(39999, aptg.getLastRow());
ptgs = FormulaParser.parse("Sheet1!A10:A65536", book); ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
aptg = (AreaI) ptgs[0]; aptg = (AreaI) ptgs[0];
assertEquals(65535, aptg.getLastRow()); assertEquals(65535, aptg.getLastRow());

View File

@ -17,9 +17,9 @@
package org.apache.poi.hssf.model; package org.apache.poi.hssf.model;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.model.FormulaParser.FormulaParseException;
import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.NamePtg; import org.apache.poi.hssf.record.formula.NamePtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
@ -30,6 +30,7 @@ import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue;
import org.apache.poi.ss.formula.FormulaParserTestHelper;
/** /**
* Test the low level formula parser functionality, * Test the low level formula parser functionality,
@ -51,21 +52,21 @@ public final class TestFormulaParserEval extends TestCase {
name.setNameName("testName"); name.setNameName("testName");
name.setReference("A1:A2"); name.setReference("A1:A2");
ptgs = FormulaParser.parse("SUM(testName)", workbook); ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook);
assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2);
assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass());
// Now make it a single cell // Now make it a single cell
name.setReference("C3"); name.setReference("C3");
ptgs = FormulaParser.parse("SUM(testName)", workbook); ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook);
assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2);
assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass());
// And make it non-contiguous // And make it non-contiguous
name.setReference("A1:A2,C3"); name.setReference("A1:A2,C3");
ptgs = FormulaParser.parse("SUM(testName)", workbook); ptgs = HSSFFormulaParser.parse("SUM(testName)", workbook);
assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2); assertTrue("two tokens expected, got "+ptgs.length,ptgs.length == 2);
assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals(FuncVarPtg.class, ptgs[1].getClass());
@ -89,11 +90,12 @@ public final class TestFormulaParserEval extends TestCase {
CellValue result; CellValue result;
try { try {
result = fe.evaluate(cell); result = fe.evaluate(cell);
} catch (FormulaParseException e) { } catch (RuntimeException e) {
if(e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) { FormulaParserTestHelper.confirmParseException(e);
fail("Identifed bug 44539"); if (!e.getMessage().equals("Found reference to named range \"A\", but that named range wasn't defined!")) {
throw new AssertionFailedError("Identifed bug 44539");
} }
throw new RuntimeException(e); throw e;
} }
assertEquals(HSSFCell.CELL_TYPE_NUMERIC, result.getCellType()); assertEquals(HSSFCell.CELL_TYPE_NUMERIC, result.getCellType());
assertEquals(42.0, result.getNumberValue(), 0.0); assertEquals(42.0, result.getNumberValue(), 0.0);

View File

@ -23,6 +23,7 @@ import junit.framework.TestCase;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
* Tests specific formula examples in <tt>OperandClassTransformer</tt>. * Tests specific formula examples in <tt>OperandClassTransformer</tt>.
@ -31,9 +32,15 @@ import org.apache.poi.hssf.record.formula.Ptg;
*/ */
public final class TestOperandClassTransformer extends TestCase { public final class TestOperandClassTransformer extends TestCase {
private static Ptg[] parseFormula(String formula) {
Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null);
assertNotNull("Ptg array should not be null", result);
return result;
}
public void testMdeterm() { public void testMdeterm() {
String formula = "MDETERM(ABS(A1))"; String formula = "MDETERM(ABS(A1))";
Ptg[] ptgs = FormulaParser.parse(formula, null); Ptg[] ptgs = parseFormula(formula);
confirmTokenClass(ptgs, 0, Ptg.CLASS_ARRAY); confirmTokenClass(ptgs, 0, Ptg.CLASS_ARRAY);
confirmFuncClass(ptgs, 1, "ABS", Ptg.CLASS_ARRAY); confirmFuncClass(ptgs, 1, "ABS", Ptg.CLASS_ARRAY);
@ -51,7 +58,7 @@ public final class TestOperandClassTransformer extends TestCase {
*/ */
public void DISABLED_testIndexPi1() { public void DISABLED_testIndexPi1() {
String formula = "INDEX(PI(),1)"; String formula = "INDEX(PI(),1)";
Ptg[] ptgs = FormulaParser.parse(formula, null); Ptg[] ptgs = parseFormula(formula);
confirmFuncClass(ptgs, 1, "PI", Ptg.CLASS_ARRAY); // fails as of POI 3.1 confirmFuncClass(ptgs, 1, "PI", Ptg.CLASS_ARRAY); // fails as of POI 3.1
confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE); confirmFuncClass(ptgs, 2, "INDEX", Ptg.CLASS_VALUE);
@ -63,7 +70,7 @@ public final class TestOperandClassTransformer extends TestCase {
*/ */
public void testDirectOperandOfValueOperator() { public void testDirectOperandOfValueOperator() {
String formula = "COUNT(A1*1)"; String formula = "COUNT(A1*1)";
Ptg[] ptgs = FormulaParser.parse(formula, null); Ptg[] ptgs = parseFormula(formula);
if (ptgs[0].getPtgClass() == Ptg.CLASS_REF) { if (ptgs[0].getPtgClass() == Ptg.CLASS_REF) {
throw new AssertionFailedError("Identified bug 45348"); throw new AssertionFailedError("Identified bug 45348");
} }
@ -78,13 +85,13 @@ public final class TestOperandClassTransformer extends TestCase {
public void testRtoV() { public void testRtoV() {
String formula = "lookup(A1, A3:A52, B3:B52)"; String formula = "lookup(A1, A3:A52, B3:B52)";
Ptg[] ptgs = FormulaParser.parse(formula, null); Ptg[] ptgs = parseFormula(formula);
confirmTokenClass(ptgs, 0, Ptg.CLASS_VALUE); confirmTokenClass(ptgs, 0, Ptg.CLASS_VALUE);
} }
public void testComplexIRR_bug45041() { public void testComplexIRR_bug45041() {
String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1"; String formula = "(1+IRR(SUMIF(A:A,ROW(INDIRECT(MIN(A:A)&\":\"&MAX(A:A))),B:B),0))^365-1";
Ptg[] ptgs = FormulaParser.parse(formula, null); Ptg[] ptgs = parseFormula(formula);
FuncVarPtg rowFunc = (FuncVarPtg) ptgs[10]; FuncVarPtg rowFunc = (FuncVarPtg) ptgs[10];
FuncVarPtg sumifFunc = (FuncVarPtg) ptgs[12]; FuncVarPtg sumifFunc = (FuncVarPtg) ptgs[12];

View File

@ -82,7 +82,7 @@ public final class TestRVA extends TestCase {
private void confirmCell(HSSFCell formulaCell, String formula, HSSFWorkbook wb) { private void confirmCell(HSSFCell formulaCell, String formula, HSSFWorkbook wb) {
Ptg[] excelPtgs = FormulaExtractor.getPtgs(formulaCell); Ptg[] excelPtgs = FormulaExtractor.getPtgs(formulaCell);
Ptg[] poiPtgs = FormulaParser.parse(formula, wb); Ptg[] poiPtgs = HSSFFormulaParser.parse(formula, wb);
int nExcelTokens = excelPtgs.length; int nExcelTokens = excelPtgs.length;
int nPoiTokens = poiPtgs.length; int nPoiTokens = poiPtgs.length;
if (nExcelTokens != nPoiTokens) { if (nExcelTokens != nPoiTokens) {

View File

@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.formula;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
/** /**
@ -87,7 +87,7 @@ public final class TestAreaPtg extends TestCase {
private static String shiftAllColumnsBy1(String formula) { private static String shiftAllColumnsBy1(String formula) {
int letUsShiftColumn1By1Column=1; int letUsShiftColumn1By1Column=1;
HSSFWorkbook wb = null; HSSFWorkbook wb = null;
Ptg[] ptgs = FormulaParser.parse(formula, wb); Ptg[] ptgs = HSSFFormulaParser.parse(formula, wb);
for(int i=0; i<ptgs.length; i++) for(int i=0; i<ptgs.length; i++)
{ {
Ptg ptg = ptgs[i]; Ptg ptg = ptgs[i];
@ -98,7 +98,7 @@ public final class TestAreaPtg extends TestCase {
aptg.setLastColumn((short)(aptg.getLastColumn()+letUsShiftColumn1By1Column)); aptg.setLastColumn((short)(aptg.getLastColumn()+letUsShiftColumn1By1Column));
} }
} }
String newFormula = FormulaParser.toFormulaString(wb, ptgs); String newFormula = HSSFFormulaParser.toFormulaString(wb, ptgs);
return newFormula; return newFormula;
} }
} }

View File

@ -24,7 +24,7 @@ import java.io.IOException;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
@ -52,7 +52,7 @@ public final class TestExternalFunctionFormulas extends TestCase {
public void testParse() { public void testParse() {
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("externalFunctionExample.xls"); HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("externalFunctionExample.xls");
Ptg[] ptgs = FormulaParser.parse("YEARFRAC(B1,C1)", wb); Ptg[] ptgs = HSSFFormulaParser.parse("YEARFRAC(B1,C1)", wb);
assertEquals(4, ptgs.length); assertEquals(4, ptgs.length);
assertEquals(NameXPtg.class, ptgs[0].getClass()); assertEquals(NameXPtg.class, ptgs[0].getClass());

View File

@ -17,7 +17,7 @@
package org.apache.poi.hssf.record.formula; package org.apache.poi.hssf.record.formula;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
@ -36,7 +36,7 @@ public final class TestFuncVarPtg extends TestCase {
*/ */
public void testOperandClass() { public void testOperandClass() {
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
Ptg[] ptgs = FormulaParser.parse("sum(A1:A2)", book); Ptg[] ptgs = HSSFFormulaParser.parse("sum(A1:A2)", book);
assertEquals(2, ptgs.length); assertEquals(2, ptgs.length);
assertEquals(AreaPtg.class, ptgs[0].getClass()); assertEquals(AreaPtg.class, ptgs[0].getClass());

View File

@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.formula.function;
import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg; import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
import org.apache.poi.hssf.record.formula.FuncPtg; import org.apache.poi.hssf.record.formula.FuncPtg;
import org.apache.poi.hssf.record.formula.FuncVarPtg; import org.apache.poi.hssf.record.formula.FuncVarPtg;
@ -36,7 +36,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase {
private static Ptg[] parse(String formula) { private static Ptg[] parse(String formula) {
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
return FormulaParser.parse(formula, book); return HSSFFormulaParser.parse(formula, book);
} }
private static void confirmFunc(String formula, int expPtgArraySize, boolean isVarArgFunc, int funcIx) { private static void confirmFunc(String formula, int expPtgArraySize, boolean isVarArgFunc, int funcIx) {
Ptg[] ptgs = parse(formula); Ptg[] ptgs = parse(formula);
@ -58,7 +58,7 @@ public final class TestParseMissingBuiltInFuncs extends TestCase {
// check that parsed Ptg array converts back to formula text OK // check that parsed Ptg array converts back to formula text OK
HSSFWorkbook book = new HSSFWorkbook(); HSSFWorkbook book = new HSSFWorkbook();
String reRenderedFormula = FormulaParser.toFormulaString(book, ptgs); String reRenderedFormula = HSSFFormulaParser.toFormulaString(book, ptgs);
assertEquals(formula, reRenderedFormula); assertEquals(formula, reRenderedFormula);
} }

View File

@ -76,7 +76,7 @@ public final class TestDate extends TestCase {
private void confirm(String formulaText, double expectedResult) { private void confirm(String formulaText, double expectedResult) {
cell11.setCellFormula(formulaText); cell11.setCellFormula(formulaText);
evaluator.clearCache(); evaluator.clearAllCachedResultValues();
double actualValue = evaluator.evaluate(cell11).getNumberValue(); double actualValue = evaluator.evaluate(cell11).getNumberValue();
assertEquals(expectedResult, actualValue, 0); assertEquals(expectedResult, actualValue, 0);
} }

View File

@ -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.formula;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaParser.FormulaParseException;
/**
* Avoids making {@link FormulaParser#FormulaParseException} public
*
* @author Josh Micich
*/
public class FormulaParserTestHelper {
public static void confirmParseException(RuntimeException e, String expectedMessage) {
checkType(e);
Assert.assertEquals(expectedMessage, e.getMessage());
}
public static void confirmParseException(RuntimeException e) {
checkType(e);
Assert.assertNotNull(e.getMessage());
}
private static void checkType(RuntimeException e) throws AssertionFailedError {
if (!(e instanceof FormulaParseException)) {
String failMsg = "Expected FormulaParseException, but got ("
+ e.getClass().getName() + "):";
System.err.println(failMsg);
e.printStackTrace();
throw new AssertionFailedError(failMsg);
}
}
}