* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/
public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
- return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex);
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex, -1);
}
/**
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory;
public SpreadsheetVersion getSpreadsheetVersion(){
return SpreadsheetVersion.EXCEL97;
}
+
+ public Table getTable(String name) {
+ throw new IllegalStateException("XSSF-style tables are not supported for HSSF");
+ }
}
*/
ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber);
+
EvaluationName getName(NamePtg namePtg);
EvaluationName getName(String name, int sheetIndex);
String resolveNameXText(NameXPtg ptg);
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
import org.apache.poi.ss.formula.ptg.AddPtg;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
import org.apache.poi.ss.formula.ptg.AreaPtg;
import org.apache.poi.ss.formula.ptg.ArrayPtg;
import org.apache.poi.ss.formula.ptg.AttrPtg;
import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
import org.apache.poi.ss.usermodel.FormulaError;
import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellReference.NameType;
private final SpreadsheetVersion _ssVersion;
private final int _sheetIndex;
+ private final int _rowIndex; // 0-based
/**
* model.Workbook, then use the convenience method on
* usermodel.HSSFFormulaEvaluator
*/
- private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex){
+ private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){
_formulaString = formula;
_pointer=0;
_book = book;
_ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
_formulaLength = _formulaString.length();
_sheetIndex = sheetIndex;
+ _rowIndex = rowIndex;
}
/**
* @param workbook the parent workbook
* @param formulaType the type of the formula, see {@link FormulaType}
* @param sheetIndex the 0-based index of the sheet this formula belongs to.
+ * @param rowIndex - the related cell's row index in 0-based form (-1 if the formula is not cell related)
+ * used to handle structured references that have the "#This Row" quantifier.
* The sheet index is required to resolve sheet-level names. <code>-1</code> means that
* the scope of the name will be ignored and the parser will match names only by name
*
* @return array of parsed tokens
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/
- public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
- FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex);
+ public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex, int rowIndex) {
+ FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
fp.parse();
return fp.getRPNPtg(formulaType);
}
+ public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
+ return parse(formula, workbook, formulaType, sheetIndex, -1);
+ }
+
+ /**
+ * Parse a structured reference. Converts the structured
+ * reference to the area that represent it.
+ *
+ * @param tableText - The structured reference text
+ * @param workbook - the parent workbook
+ * @param rowIndex - the 0-based cell's row index ( used to handle "#This Row" quantifiers )
+ * @return the area that being represented by the structured reference.
+ */
+ public static Area3DPxg parseStructuredReference(String tableText, FormulaParsingWorkbook workbook, int rowIndex) {
+ Ptg[] arr = FormulaParser.parse(tableText, workbook, 0, 0, rowIndex);
+ if (arr.length != 1 || !(arr[0] instanceof Area3DPxg) ) {
+ throw new IllegalStateException("Illegal structured reference");
+ }
+ return (Area3DPxg) arr[0];
+ }
+
/** Read New Character From Input Stream */
private void GetChar() {
// The intersection operator is a space. We track whether the run of
}
+
+ private final static String specHeaders = "Headers";
+ private final static String specAll = "All";
+ private final static String specData = "Data";
+ private final static String specTotals = "Totals";
+ private final static String specThisRow = "This Row";
+
+ /**
+ * Parses a structured reference, returns it as area reference.
+ * Examples:
+ * <pre>
+ * Table1[col]
+ * Table1[[#Totals],[col]]
+ * Table1[#Totals]
+ * Table1[#All]
+ * Table1[#Data]
+ * Table1[#Headers]
+ * Table1[#Totals]
+ * Table1[#This Row]
+ * Table1[[#All],[col]]
+ * Table1[[#Headers],[col]]
+ * Table1[[#Totals],[col]]
+ * Table1[[#All],[col1]:[col2]]
+ * Table1[[#Data],[col1]:[col2]]
+ * Table1[[#Headers],[col1]:[col2]]
+ * Table1[[#Totals],[col1]:[col2]]
+ * Table1[[#Headers],[#Data],[col2]]
+ * Table1[[#This Row], [col1]]
+ * Table1[ [col1]:[col2] ]
+ * </pre>
+ * @param tableName
+ * @return
+ */
+ private ParseNode parseStructuredReference(String tableName){
+
+ if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) {
+ throw new FormulaParseException("Strctured references work only on XSSF (Excel 2007)!");
+ }
+ Table tbl = _book.getTable(tableName);
+ if (tbl == null) {
+ throw new FormulaParseException("Illegal table name!");
+ }
+ String sheetName = tbl.getSheetName();
+
+ int startCol = tbl.getStartColIndex();
+ int endCol = tbl.getEndColIndex();
+ int startRow = tbl.getStartRowIndex();
+ int endRow = tbl.getEndRowIndex();
+
+ int savePtr0 = _pointer;
+ GetChar();
+
+ boolean isTotalsSpec = false;
+ boolean isThisRowSpec = false;
+ boolean isDataSpec = false;
+ boolean isHeadersSpec = false;
+ boolean isAllSpec = false;
+ int nSpecQuantifiers = 0; // The number of special quantifiers
+ while (true) {
+ int savePtr1 = _pointer;
+ String specName = parseAsSpecialQuantifier();
+ if (specName == null) {
+ resetPointer(savePtr1);
+ break;
+ }
+ if (specName.equals(specAll)) {
+ isAllSpec = true;
+ } else if (specName.equals(specData)) {
+ isDataSpec = true;
+ } else if (specName.equals(specHeaders)) {
+ isHeadersSpec = true;
+ } else if (specName.equals(specThisRow)) {
+ isThisRowSpec = true;
+ } else if (specName.equals(specTotals)) {
+ isTotalsSpec = true;
+ } else {
+ throw new FormulaParseException("Unknown special qunatifier "+ specName);
+ }
+ nSpecQuantifiers++ ;
+ if (look == ','){
+ GetChar();
+ } else {
+ break;
+ }
+ }
+ boolean isThisRow = false;
+ SkipWhite();
+ if (look == '@') {
+ isThisRow = true;
+ GetChar();
+ }
+ // parse column quantifier
+ String startColumnName = null;
+ String endColumnName = null;
+ int nColQuantifiers = 0;
+ int savePtr1 = _pointer;
+ startColumnName = parseAsColumnQuantifier();
+ if (startColumnName == null) {
+ resetPointer(savePtr1);
+ } else {
+ nColQuantifiers++;
+ if (look == ','){
+ throw new FormulaParseException("The formula "+ _formulaString + "is illegal: you should not use ',' with column quantifiers");
+ } else if (look == ':') {
+ GetChar();
+ endColumnName = parseAsColumnQuantifier();
+ nColQuantifiers++;
+ if (endColumnName == null) {
+ throw new FormulaParseException("The formula "+ _formulaString + "is illegal: the string after ':' must be column quantifier");
+ }
+ }
+ }
+
+ if(nColQuantifiers == 0 && nSpecQuantifiers == 0){
+ resetPointer(savePtr0);
+ savePtr0 = _pointer;
+ startColumnName = parseAsColumnQuantifier();
+ if (startColumnName != null) {
+ nColQuantifiers++;
+ } else {
+ resetPointer(savePtr0);
+ String name = parseAsSpecialQuantifier();
+ if (name!=null) {
+ if (name.equals(specAll)) {
+ isAllSpec = true;
+ } else if (name.equals(specData)) {
+ isDataSpec = true;
+ } else if (name.equals(specHeaders)) {
+ isHeadersSpec = true;
+ } else if (name.equals(specThisRow)) {
+ isThisRowSpec = true;
+ } else if (name.equals(specTotals)) {
+ isTotalsSpec = true;
+ } else {
+ throw new FormulaParseException("Unknown special qunatifier "+ name);
+ }
+ nSpecQuantifiers++;
+ } else {
+ throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
+ }
+ }
+ } else {
+ Match(']');
+ }
+
+ int actualStartRow = startRow;
+ int actualEndRow = endRow;
+ int actualStartCol = startCol;
+ int actualEndCol = endCol;
+ if (nSpecQuantifiers > 0) {
+ //Selecting rows
+ if (nSpecQuantifiers == 1 && isAllSpec) {
+ //do nothing
+ } else if (isDataSpec && isHeadersSpec) {
+ if (tbl.isHasTotalsRow()) {
+ actualEndRow = endRow - 1;
+ }
+ } else if (isDataSpec && isTotalsSpec) {
+ actualStartRow = startRow + 1;
+ } else if (nSpecQuantifiers == 1 && isDataSpec) {
+ actualStartRow = startRow + 1;
+ if (tbl.isHasTotalsRow()) {
+ actualEndRow = endRow - 1;
+ }
+ } else if (nSpecQuantifiers == 1 && isHeadersSpec) {
+ actualEndRow = actualStartRow;
+ } else if (nSpecQuantifiers == 1 && isTotalsSpec) {
+ actualStartRow = actualEndRow;
+ } else if ((nSpecQuantifiers == 1 && isThisRowSpec) || isThisRow) {
+ actualStartRow = _rowIndex; //The rowNum is 0 based
+ actualEndRow = _rowIndex;
+ } else {
+ throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
+ }
+ } else {
+ if (isThisRow) { // there is a @
+ actualStartRow = _rowIndex; //The rowNum is 0 based
+ actualEndRow = _rowIndex;
+ } else { // Really no special quantifiers
+ actualStartRow++;
+ }
+ }
+ //Selecting cols
+
+ if (nColQuantifiers == 2){
+ if (startColumnName == null || endColumnName == null){
+ throw new IllegalStateException("Fatal error");
+ }
+ int startIdx = tbl.findColumnIndex(startColumnName);
+ int endIdx = tbl.findColumnIndex(endColumnName);
+ if (startIdx == -1 || endIdx == -1) {
+ throw new FormulaParseException("One of the columns "+ startColumnName +", "+ endColumnName +" doesn't exist in table "+ tbl.getName());
+ }
+ actualStartCol = startCol+ startIdx;
+ actualEndCol = startCol + endIdx;
+
+ } else if(nColQuantifiers == 1){
+ if (startColumnName == null){
+ throw new IllegalStateException("Fatal error");
+ }
+ int idx = tbl.findColumnIndex(startColumnName);
+ if (idx == -1) {
+ throw new FormulaParseException("The column "+ startColumnName + " doesn't exist in table "+ tbl.getName());
+ }
+ actualStartCol = startCol + idx;
+ actualEndCol = actualStartCol;
+ }
+ CellReference tl = new CellReference(actualStartRow, actualStartCol);
+ CellReference br = new CellReference(actualEndRow, actualEndCol);
+ SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true));
+ Ptg ptg = _book.get3DReferencePtg(new AreaReference(tl, br), sheetIden);
+ return new ParseNode(ptg);
+ }
+
+ /**
+ * Tries to parse the next as column - can contain whitespace
+ * Caller should save pointer.
+ * @return
+ */
+ private String parseAsColumnQuantifier(){
+ if ( look != '[') {
+ return null;
+ }
+ GetChar();
+ String name = "";
+ if (look == '#') {
+ return null;
+ }
+ if (look == '@') {
+ GetChar();
+ }
+ while (look!=']') {
+ name += look;
+ GetChar();
+ }
+ Match(']');
+ return name;
+ }
+ /**
+ * Tries to parse the next as special quantifier
+ * Caller should save pointer.
+ * @return
+ */
+ private String parseAsSpecialQuantifier(){
+ if ( look != '[') {
+ return null;
+ }
+ GetChar();
+ if( look != '#') {
+ return null;
+ }
+ GetChar();
+ String name = parseAsName();
+ if ( name.equals("This")) {
+ name = name + ' ' + parseAsName();
+ }
+ Match(']');
+ return name;
+ }
+
/**
* Parses simple factors that are not primitive ranges or range components
if (look == '(') {
return function(name);
}
+ //TODO Livshen's code
+ if(look == '['){
+ return parseStructuredReference(name);
+ }
+ //TODO End of Livshen's code
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
}
private String parseAsName() {
StringBuilder sb = new StringBuilder();
- // defined names may begin with a letter or underscore
- if (!Character.isLetter(look) && look != '_') {
- throw expected("number, string, or defined name");
+ // defined names may begin with a letter or underscore or backslash
+ if (!Character.isLetter(look) && look != '_' && look != '\\') {
+ throw expected("number, string, defined name, or table");
}
while (isValidDefinedNameChar(look)) {
sb.append(look);
Match('}');
return arrayNode;
}
- if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '['){
+ // named ranges and tables can start with underscore or backslash
+ // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names
+ if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) {
return parseRangeExpression();
}
if (look == '.') {
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
*/
Name createName();
+ /**
+ * XSSF Only - gets a table that exists in the worksheet
+ */
+ Table getTable(String name);
+
/**
* Return an external name (named range, function, user-defined function) Ptg
*/
return new FunctionNameEval(name);
}
}
+ public int getSheetIndex() {
+ return _sheetIndex;
+ }
private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
try {
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.atp.AnalysisToolPak;
-import org.apache.poi.ss.formula.eval.*;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.ExternalNameEval;
+import org.apache.poi.ss.formula.eval.FunctionEval;
+import org.apache.poi.ss.formula.eval.FunctionNameEval;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.functions.Choose;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.functions.Function;
import org.apache.poi.ss.formula.functions.IfFunc;
-import org.apache.poi.ss.formula.ptg.*;
+import org.apache.poi.ss.formula.ptg.Area3DPtg;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.formula.ptg.AreaErrPtg;
+import org.apache.poi.ss.formula.ptg.AreaPtg;
+import org.apache.poi.ss.formula.ptg.AttrPtg;
+import org.apache.poi.ss.formula.ptg.BoolPtg;
+import org.apache.poi.ss.formula.ptg.ControlPtg;
+import org.apache.poi.ss.formula.ptg.DeletedArea3DPtg;
+import org.apache.poi.ss.formula.ptg.DeletedRef3DPtg;
+import org.apache.poi.ss.formula.ptg.ErrPtg;
+import org.apache.poi.ss.formula.ptg.ExpPtg;
+import org.apache.poi.ss.formula.ptg.FuncVarPtg;
+import org.apache.poi.ss.formula.ptg.IntPtg;
+import org.apache.poi.ss.formula.ptg.MemAreaPtg;
+import org.apache.poi.ss.formula.ptg.MemErrPtg;
+import org.apache.poi.ss.formula.ptg.MemFuncPtg;
+import org.apache.poi.ss.formula.ptg.MissingArgPtg;
+import org.apache.poi.ss.formula.ptg.NamePtg;
+import org.apache.poi.ss.formula.ptg.NameXPtg;
+import org.apache.poi.ss.formula.ptg.NameXPxg;
+import org.apache.poi.ss.formula.ptg.NumberPtg;
+import org.apache.poi.ss.formula.ptg.OperationPtg;
+import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.formula.ptg.Ref3DPtg;
+import org.apache.poi.ss.formula.ptg.Ref3DPxg;
+import org.apache.poi.ss.formula.ptg.RefErrorPtg;
+import org.apache.poi.ss.formula.ptg.RefPtg;
+import org.apache.poi.ss.formula.ptg.StringPtg;
+import org.apache.poi.ss.formula.ptg.UnionPtg;
+import org.apache.poi.ss.formula.ptg.UnknownPtg;
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
-
/**
* Evaluates formula cells.<p/>
*
try {
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
+// System.out.println("=====");
+// XSSFCell c = ((XSSFEvaluationCell)srcCell).getXSSFCell();
+// System.out.println("Formula is "+ c);
+// System.out.println("The cell is " + c.getSheet().getSheetName()+"!"+c.getReference());
+// System.out.println("Evaluation tokens : "); // TODO Dlivshen remove
+// for (Ptg ptg : ptgs) { // TODO Dlivshen remove
+// System.out.println(ptg); // TODO Dlivshen remove
+// } // TODO Dlivshen remove
+// System.out.println("======"); // TODO Dlivshen remove
if (evalListener == null) {
result = evaluateFormula(ec, ptgs);
} else {
tracker.updateCacheResult(result);
}
catch (NotImplementedException e) {
- throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
+ throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
} catch (RuntimeException re) {
- if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
+ if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
logInfo(re.getCause().getMessage() + " - Continuing with cached value!");
switch(srcCell.getCachedFormulaResultType()) {
case Cell.CELL_TYPE_NUMERIC:
package org.apache.poi.ss.formula.functions;
+import org.apache.poi.ss.formula.FormulaParseException;
+import org.apache.poi.ss.formula.FormulaParser;
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval;
-import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.usermodel.Table;
/**
* Implementation for Excel function INDIRECT<p/>
*/
public final class Indirect implements FreeRefFunction {
- public static final FreeRefFunction instance = new Indirect();
+ public static final FreeRefFunction instance = new Indirect();
private Indirect() {
// enforce singleton
return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
}
- private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text,
+ private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
boolean isA1style) {
+
+ ec.getRowIndex();
+ ec.getColumnIndex();
+
// Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!');
String refStrPart1;
String refStrPart2;
-
- int colonPos = refText.indexOf(':');
- if (colonPos < 0) {
- refStrPart1 = refText.trim();
- refStrPart2 = null;
- } else {
- refStrPart1 = refText.substring(0, colonPos).trim();
- refStrPart2 = refText.substring(colonPos + 1).trim();
- }
- return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
+ if (Table.isStructuredReference.matcher(refText).matches()) { // The argument is structured reference
+ Area3DPxg areaPtg = null;
+ try{
+ areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
+ } catch(FormulaParseException e) {
+ return ErrorEval.REF_INVALID;
+ }
+ return ec.getArea3DEval(areaPtg);
+ } else { // The argumnet is regular reference
+ int colonPos = refText.indexOf(':');
+ if (colonPos < 0) {
+ refStrPart1 = refText.trim();
+ refStrPart2 = null;
+ } else {
+ refStrPart1 = refText.substring(0, colonPos).trim();
+ refStrPart2 = refText.substring(colonPos + 1).trim();
+ }
+ return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
+ }
}
/**
import org.apache.poi.ss.formula.ptg.Ref3DPxg;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.NotImplemented;
return _uBook.createName();
}
+ public Table getTable(String name) {
+ return _uBook.getTable(name);
+ }
+
public UDFFinder getUDFFinder(){
return _uBook.getUDFFinder();
}
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook());
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007);
- Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex, getRowIndex());
Ptg[] fmla = sf.convertSharedFormulas(ptgs,
getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn());
return FormulaRenderer.toFormulaString(fpb, fmla);
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
//validate through the FormulaParser
- FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()));
+ FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), -1);
CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula);
public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell();
XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook);
- return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()));
+ return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()), cell.getRowIndex());
}
}
public void setRefersToFormula(String formulaText) {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
//validate through the FormulaParser
- FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
_ctName.setStringValue(formulaText);
}
return false;
}
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
- Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
return Ptg.doesFormulaReferToDeletedCell(ptgs);
}
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.apache.xmlbeans.XmlException;
*
* @author Roberto Manicardi
*/
-public class XSSFTable extends POIXMLDocumentPart {
+public class XSSFTable extends POIXMLDocumentPart implements Table {
private CTTable ctTable;
private List<XSSFXmlColumnPr> xmlColumnPr;
+ private CTTableColumn[] ctColumns;
+ private HashMap<String, Integer> columnMap;
private CellReference startCellReference;
private CellReference endCellReference;
private String commonXPath;
return maps;
}
+ private CTTableColumn[] getTableColumns() {
+ if (ctColumns == null) {
+ ctColumns = ctTable.getTableColumns().getTableColumnArray();
+ }
+ return ctColumns;
+ }
/**
*
public String getCommonXpath() {
if (commonXPath == null) {
String[] commonTokens = {};
- for (CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()) {
+ for (CTTableColumn column : getTableColumns()) {
if (column.getXmlColumnPr()!=null) {
String xpath = column.getXmlColumnPr().getXpath();
String[] tokens = xpath.split("/");
}
+ /**
+ * Note this list is static - once read, it does not notice later changes to the underlying column structures
+ * @return List of XSSFXmlColumnPr
+ */
public List<XSSFXmlColumnPr> getXmlColumnPrs() {
if (xmlColumnPr==null) {
xmlColumnPr = new ArrayList<XSSFXmlColumnPr>();
- for (CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()) {
+ for (CTTableColumn column: getTableColumns()) {
if (column.getXmlColumnPr()!=null) {
XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
xmlColumnPr.add(columnPr);
}
cellnum++;
}
+ ctColumns = null;
+ columnMap = null;
+ }
+ }
+
+ public int findColumnIndex(String column) {
+ if (columnMap == null) {
+ columnMap = new HashMap<String, Integer>(getTableColumns().length);
+
+ for (int i=0; i < getTableColumns().length; i++) {
+ columnMap.put(getTableColumns()[i].getName().toUpperCase(), Integer.valueOf(i));
+ }
}
+ // Table column names with special characters need a single quote escape
+ // but the escape is not present in the column definition
+ Integer idx = columnMap.get(column.replace("'", "").toUpperCase());
+ return idx == null ? -1 : idx.intValue();
+ }
+
+ public String getSheetName() {
+ return getXSSFSheet().getSheetName();
+ }
+
+ public boolean isHasTotalsRow() {
+ return ctTable.getTotalsRowShown();
+ }
+
+ public int getStartColIndex() {
+ return getStartCellReference().getCol();
+ }
+
+ public int getStartRowIndex() {
+ return getStartCellReference().getRow();
+ }
+
+ public int getEndColIndex() {
+ return getEndCellReference().getCol();
+ }
+
+ public int getEndRowIndex() {
+ return getEndCellReference().getRow();
}
}
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;
public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL2007;
}
+
+ /*
+ * TODO: data tables are stored at the workbook level in XSSF, but are bound to a single sheet.
+ * The current code structure has them hanging off XSSFSheet, but formulas reference them
+ * only by name (names are global, and case insensitive).
+ * This map stores names as lower case for case-insensitive lookups.
+ *
+ * FIXME: Caching tables by name here for fast formula lookup means the map is out of date if
+ * a table is renamed or added/removed to a sheet after the map is created.
+ *
+ * Perhaps tables can be managed similar to PivotTable references above?
+ */
+ Map<String, XSSFTable> _tableCache = null;
+ private Map<String, XSSFTable> getTableCache() {
+ if ( _tableCache != null ) {
+ return _tableCache;
+ }
+ // FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap
+ _tableCache = new HashMap<String, XSSFTable>();
+
+ if (sheets != null) {
+ for (XSSFSheet sheet : sheets) {
+ for (XSSFTable tbl : sheet.getTables()) {
+ String lname = tbl.getName().toLowerCase(Locale.ROOT);
+ _tableCache.put(lname, tbl);
+ }
+ }
+ }
+ return _tableCache;
+ }
+
+ /**
+ * Returns the data table with the given name (case insensitive).
+ * Tables are cached for performance (formula evaluation looks them up by name repeatedly).
+ * After the first table lookup, adding or removing a table from the document structure will cause trouble.
+ * This is meant to be used on documents whose structure is essentially static at the point formulas are evaluated.
+ *
+ * @param name the data table name (case-insensitive)
+ * @return The Data table in the workbook named <tt>name</tt>, or <tt>null</tt> if no table is named <tt>name</tt>.
+ * @since 3.15 beta 2
+ */
+ public XSSFTable getTable(String name) {
+ String lname = name.toLowerCase(Locale.ROOT);
+ return getTableCache().get(lname);
+ }
}
String formula = f.getStringValue();
if (formula != null && formula.length() > 0) {
int sheetIndex = _wb.getSheetIndex(cell.getSheet());
- Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex, cell.getRowIndex());
for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName);
}
String formula = name.getRefersToFormula();
if (formula != null) {
int sheetIndex = name.getSheetIndex();
- Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, -1);
for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName);
}
String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex();
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, -1);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla);
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
try {
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, -1);
String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
String[] formulaArray = cfRule.getFormulaArray();
for (int i = 0; i < formulaArray.length; i++) {
String formula = formulaArray[i];
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, -1);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
cfRule.setFormulaArray(i, shiftedFmla);
public final class TestXSSFFormulaParser {
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
- return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
+ return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, -1);
}
@Test
String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6";
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
- Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
+ Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1,-1);
confirmTokenClasses(ptgs,
// TODO - AttrPtg.class, // Excel prepends this
String formula = "Sheet1!A1:Sheet1!B3";
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
- Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
+ Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1, -1);
if (ptgs.length == 3) {
confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class);
confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference.");
- confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, or defined name");
- confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, or defined name");
+ confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or table");
+ confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or table");
confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found");
confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference.");
confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11.");
assertEquals("MAX(C1:C2-D1:D2)", FormulaRenderer.toFormulaString(null, ptg));
//construct a new ArrayRecord with the same contents as r1
- Ptg[] fmlaPtg = FormulaParser.parse("MAX(C1:C2-D1:D2)", null, FormulaType.ARRAY, 0);
+ Ptg[] fmlaPtg = FormulaParser.parse("MAX(C1:C2-D1:D2)", null, FormulaType.ARRAY, 0, -1);
ArrayRecord r2 = new ArrayRecord(Formula.create(fmlaPtg), new CellRangeAddress8Bit(1, 1, 1, 1));
byte[] ser = r2.serialize();
//serialize and check that the data is the same as in r1
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97);
- sharedFormula = FormulaParser.parse("A2", fpb, FormulaType.CELL, -1);
+ sharedFormula = FormulaParser.parse("A2", fpb, FormulaType.CELL, -1, -1);
convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0);
confirmOperandClasses(sharedFormula, convertedFormula);
//conversion relative to [0,0] should return the original formula
//one row down and one cell right
assertEquals("B3", FormulaRenderer.toFormulaString(fpb, convertedFormula));
- sharedFormula = FormulaParser.parse("SUM(A1:C1)", fpb, FormulaType.CELL, -1);
+ sharedFormula = FormulaParser.parse("SUM(A1:C1)", fpb, FormulaType.CELL, -1, -1);
convertedFormula = sf.convertSharedFormulas(sharedFormula, 0, 0);
confirmOperandClasses(sharedFormula, convertedFormula);
assertEquals("SUM(A1:C1)", FormulaRenderer.toFormulaString(fpb, convertedFormula));