bug 57840: merge changes from ^/poi/branches/xssf_structured_references to ^/poi/trunk

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1747657 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2016-06-10 07:41:09 +00:00
commit 12d7fb30a4
27 changed files with 5751 additions and 4867 deletions

View File

@ -38,6 +38,7 @@ import org.apache.poi.ss.formula.ptg.NameXPtg;
import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPtg; import org.apache.poi.ss.formula.ptg.Ref3DPtg;
import org.apache.poi.ss.formula.udf.UDFFinder; 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.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
@ -264,7 +265,16 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
return extIx; return extIx;
} }
@Override
public SpreadsheetVersion getSpreadsheetVersion(){ public SpreadsheetVersion getSpreadsheetVersion(){
return SpreadsheetVersion.EXCEL97; return SpreadsheetVersion.EXCEL97;
} }
/**
* @throws IllegalStateException: data tables are not supported in Excel 97-2003 format
*/
@Override
public Table getTable(String name) {
throw new IllegalStateException("XSSF-style tables are not supported for HSSF");
}
} }

View File

@ -28,6 +28,7 @@ import org.apache.poi.ss.formula.function.FunctionMetadata;
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
import org.apache.poi.ss.formula.ptg.AddPtg; 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.AreaPtg;
import org.apache.poi.ss.formula.ptg.ArrayPtg; import org.apache.poi.ss.formula.ptg.ArrayPtg;
import org.apache.poi.ss.formula.ptg.AttrPtg; import org.apache.poi.ss.formula.ptg.AttrPtg;
@ -69,6 +70,7 @@ import org.apache.poi.ss.formula.ptg.UnionPtg;
import org.apache.poi.ss.formula.ptg.ValueOperatorPtg; import org.apache.poi.ss.formula.ptg.ValueOperatorPtg;
import org.apache.poi.ss.usermodel.FormulaError; import org.apache.poi.ss.usermodel.FormulaError;
import org.apache.poi.ss.usermodel.Name; 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.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellReference.NameType; import org.apache.poi.ss.util.CellReference.NameType;
@ -117,6 +119,7 @@ public final class FormulaParser {
private final SpreadsheetVersion _ssVersion; private final SpreadsheetVersion _ssVersion;
private final int _sheetIndex; private final int _sheetIndex;
private final int _rowIndex; // 0-based
/** /**
@ -131,18 +134,45 @@ public final class FormulaParser {
* model.Workbook, then use the convenience method on * model.Workbook, then use the convenience method on
* usermodel.HSSFFormulaEvaluator * usermodel.HSSFFormulaEvaluator
*/ */
private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex){ private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){
_formulaString = formula; _formulaString = formula;
_pointer=0; _pointer=0;
_book = book; _book = book;
_ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion(); _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
_formulaLength = _formulaString.length(); _formulaLength = _formulaString.length();
_sheetIndex = sheetIndex; _sheetIndex = sheetIndex;
_rowIndex = rowIndex;
} }
/** /**
* Parse a formula into a array of tokens * Parse a formula into an array of tokens
* Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs) * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
* if formula contains unrecognized names (names are likely UDFs)
*
* @param formula the formula to parse
* @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.
* 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
* @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.
* Use rowIndex=-1 or {@link #parseStructuredReference(String, FormulaParsingWorkbook, int, int) if formula
* does not contain structured references.
*
* @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, int rowIndex) {
FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
fp.parse();
return fp.getRPNPtg(formulaType);
}
/**
* Parse a formula into an array of tokens
* Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
* if formula contains unrecognized names (names are likely UDFs)
* *
* @param formula the formula to parse * @param formula the formula to parse
* @param workbook the parent workbook * @param workbook the parent workbook
@ -155,9 +185,25 @@ public final class FormulaParser {
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
*/ */
public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) { public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex); return parse(formula, workbook, formulaType, sheetIndex, -1);
fp.parse(); }
return fp.getRPNPtg(formulaType);
/**
* 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) {
final int sheetIndex = -1; //don't care?
Ptg[] arr = FormulaParser.parse(tableText, workbook, FormulaType.CELL, sheetIndex, 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 */ /** Read New Character From Input Stream */
@ -529,6 +575,285 @@ public final class FormulaParser {
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("Structured references work only on XSSF (Excel 2007+)!");
}
Table tbl = _book.getTable(tableName);
if (tbl == null) {
throw new FormulaParseException("Illegal table name: '" + tableName + "'");
}
String sheetName = tbl.getSheetName();
int startCol = tbl.getStartColIndex();
int endCol = tbl.getEndColIndex();
int startRow = tbl.getStartRowIndex();
int endRow = tbl.getEndRowIndex();
// Do NOT return before done reading all the structured reference tokens from the input stream.
// Throwing exceptions is okay.
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 quantifier "+ 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 quantifier "+ name);
}
nSpecQuantifiers++;
} else {
throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
}
}
} else {
Match(']');
}
// Done reading from input stream
// Ok to return now
if (isTotalsSpec && !tbl.isHasTotalsRow()) {
return new ParseNode(ErrPtg.REF_INVALID);
}
if ((isThisRow || isThisRowSpec) && (_rowIndex < startRow || endRow < _rowIndex)) {
// structured reference is trying to reference a row above or below the table with [#This Row] or [@]
if (_rowIndex >= 0) {
return new ParseNode(ErrPtg.VALUE_INVALID);
} else {
throw new FormulaParseException(
"Formula contained [#This Row] or [@] structured reference but this row < 0. " +
"Row index must be specified for row-referencing structured references.");
}
}
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 && !isThisRow) {
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 topLeft = new CellReference(actualStartRow, actualStartCol);
CellReference bottomRight = new CellReference(actualEndRow, actualEndCol);
SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true));
Ptg ptg = _book.get3DReferencePtg(new AreaReference(topLeft, bottomRight), 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 * Parses simple factors that are not primitive ranges or range components
* i.e. '!', ':'(and equiv '...') do not appear * i.e. '!', ':'(and equiv '...') do not appear
@ -558,6 +883,9 @@ public final class FormulaParser {
if (look == '(') { if (look == '(') {
return function(name); return function(name);
} }
if(look == '['){
return parseStructuredReference(name);
}
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) { if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE"))); return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
} }
@ -581,9 +909,9 @@ public final class FormulaParser {
private String parseAsName() { private String parseAsName() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
// defined names may begin with a letter or underscore // defined names may begin with a letter or underscore or backslash
if (!Character.isLetter(look) && look != '_') { if (!Character.isLetter(look) && look != '_' && look != '\\') {
throw expected("number, string, or defined name"); throw expected("number, string, defined name, or data table");
} }
while (isValidDefinedNameChar(look)) { while (isValidDefinedNameChar(look)) {
sb.append(look); sb.append(look);
@ -1175,7 +1503,9 @@ public final class FormulaParser {
Match('}'); Match('}');
return arrayNode; 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(); return parseRangeExpression();
} }
if (look == '.') { if (look == '.') {

View File

@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.Name; 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.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
@ -41,6 +42,11 @@ public interface FormulaParsingWorkbook {
*/ */
Name createName(); 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 an external name (named range, function, user-defined function) Ptg
*/ */

View File

@ -407,6 +407,9 @@ public final class OperationEvaluationContext {
return new FunctionNameEval(name); return new FunctionNameEval(name);
} }
} }
public int getSheetIndex() {
return _sheetIndex;
}
private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) { private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
try { try {

View File

@ -26,20 +26,60 @@ import java.util.TreeSet;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException; import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.atp.AnalysisToolPak; 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.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.functions.Choose; import org.apache.poi.ss.formula.functions.Choose;
import org.apache.poi.ss.formula.functions.FreeRefFunction; import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.functions.Function; import org.apache.poi.ss.formula.functions.Function;
import org.apache.poi.ss.formula.functions.IfFunc; 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.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* Evaluates formula cells.<p/> * Evaluates formula cells.<p/>
* *

View File

@ -17,13 +17,18 @@
package org.apache.poi.ss.formula.functions; 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.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval; import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException; import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.MissingArgEval; import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.OperandResolver; import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval; 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/> * Implementation for Excel function INDIRECT<p/>
@ -88,8 +93,9 @@ public final class Indirect implements FreeRefFunction {
return OperandResolver.coerceValueToBoolean(ve, false).booleanValue(); 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) { boolean isA1style) {
// Search backwards for '!' because sheet names can contain '!' // Search backwards for '!' because sheet names can contain '!'
int plingPos = text.lastIndexOf('!'); int plingPos = text.lastIndexOf('!');
@ -110,9 +116,19 @@ public final class Indirect implements FreeRefFunction {
refText = text.substring(plingPos + 1); refText = text.substring(plingPos + 1);
} }
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 argument is regular reference
String refStrPart1; String refStrPart1;
String refStrPart2; String refStrPart2;
int colonPos = refText.indexOf(':'); int colonPos = refText.indexOf(':');
if (colonPos < 0) { if (colonPos < 0) {
refStrPart1 = refText.trim(); refStrPart1 = refText.trim();
@ -123,6 +139,7 @@ public final class Indirect implements FreeRefFunction {
} }
return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style); return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
} }
}
/** /**
* @return array of length 2: {workbookName, sheetName,}. Second element will always be * @return array of length 2: {workbookName, sheetName,}. Second element will always be

View File

@ -28,7 +28,7 @@ import org.apache.poi.xssf.usermodel.BaseXSSFEvaluationWorkbook;
* SXSSF wrapper around the SXSSF and XSSF workbooks * SXSSF wrapper around the SXSSF and XSSF workbooks
*/ */
public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook { public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
private SXSSFWorkbook _uBook; private final SXSSFWorkbook _uBook;
public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) { public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) {
if (book == null) { if (book == null) {
@ -42,15 +42,18 @@ public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
_uBook = book; _uBook = book;
} }
@Override
public int getSheetIndex(EvaluationSheet evalSheet) { public int getSheetIndex(EvaluationSheet evalSheet) {
SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet(); SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet();
return _uBook.getSheetIndex(sheet); return _uBook.getSheetIndex(sheet);
} }
@Override
public EvaluationSheet getSheet(int sheetIndex) { public EvaluationSheet getSheet(int sheetIndex) {
return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
} }
@Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) { public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell(); SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell();
SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook); SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook);

View File

@ -17,7 +17,10 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.usermodel;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.EvaluationName; import org.apache.poi.ss.formula.EvaluationName;
@ -36,6 +39,8 @@ import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPxg; import org.apache.poi.ss.formula.ptg.Ref3DPxg;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder; import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.NotImplemented; import org.apache.poi.util.NotImplemented;
@ -310,6 +315,55 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
return _uBook.createName(); return _uBook.createName();
} }
private static String caseInsensitive(String s) {
return s.toUpperCase(Locale.ROOT);
}
/*
* 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?
*/
private 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>();
for (Sheet sheet : _uBook) {
for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
String lname = caseInsensitive(tbl.getName());
_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
*/
@Override
public XSSFTable getTable(String name) {
if (name == null) return null;
String lname = caseInsensitive(name);
return getTableCache().get(lname);
}
public UDFFinder getUDFFinder(){ public UDFFinder getUDFFinder(){
return _uBook.getUDFFinder(); return _uBook.getUDFFinder();
} }

View File

@ -506,7 +506,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook()); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook());
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007); 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, Ptg[] fmla = sf.convertSharedFormulas(ptgs,
getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn()); getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn());
return FormulaRenderer.toFormulaString(fpb, fmla); return FormulaRenderer.toFormulaString(fpb, fmla);
@ -550,7 +550,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
//validate through the FormulaParser //validate through the FormulaParser
FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet())); FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex());
CTCellFormula f = CTCellFormula.Factory.newInstance(); CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula); f.setStringValue(formula);

View File

@ -38,18 +38,21 @@ public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
super(book); super(book);
} }
@Override
public int getSheetIndex(EvaluationSheet evalSheet) { public int getSheetIndex(EvaluationSheet evalSheet) {
XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet(); XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet();
return _uBook.getSheetIndex(sheet); return _uBook.getSheetIndex(sheet);
} }
@Override
public EvaluationSheet getSheet(int sheetIndex) { public EvaluationSheet getSheet(int sheetIndex) {
return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex)); return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
} }
@Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) { public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell(); XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell();
XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook); 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());
} }
} }

View File

@ -192,7 +192,7 @@ public final class XSSFName implements Name {
public void setRefersToFormula(String formulaText) { public void setRefersToFormula(String formulaText) {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
//validate through the FormulaParser //validate through the FormulaParser
FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex()); FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
_ctName.setStringValue(formulaText); _ctName.setStringValue(formulaText);
} }
@ -203,7 +203,7 @@ public final class XSSFName implements Name {
return false; return false;
} }
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook); 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); return Ptg.doesFormulaReferToDeletedCell(ptgs);
} }

View File

@ -24,13 +24,17 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.apache.poi.POIXMLDocumentPart; import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship; 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.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr; import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
import org.apache.poi.util.StringUtil;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
@ -48,10 +52,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
* *
* @author Roberto Manicardi * @author Roberto Manicardi
*/ */
public class XSSFTable extends POIXMLDocumentPart { public class XSSFTable extends POIXMLDocumentPart implements Table {
private CTTable ctTable; private CTTable ctTable;
private List<XSSFXmlColumnPr> xmlColumnPr; private List<XSSFXmlColumnPr> xmlColumnPr;
private CTTableColumn[] ctColumns;
private HashMap<String, Integer> columnMap;
private CellReference startCellReference; private CellReference startCellReference;
private CellReference endCellReference; private CellReference endCellReference;
private String commonXPath; private String commonXPath;
@ -117,32 +123,41 @@ public class XSSFTable extends POIXMLDocumentPart {
* @return true if the Table element contain mappings * @return true if the Table element contain mappings
*/ */
public boolean mapsTo(long id){ public boolean mapsTo(long id){
boolean maps =false;
List<XSSFXmlColumnPr> pointers = getXmlColumnPrs(); List<XSSFXmlColumnPr> pointers = getXmlColumnPrs();
for (XSSFXmlColumnPr pointer: pointers) { for (XSSFXmlColumnPr pointer: pointers) {
if (pointer.getMapId()==id) { if (pointer.getMapId()==id) {
maps=true; return true;
break;
} }
} }
return maps; return false;
} }
/**
* caches table columns for performance.
* Updated via updateHeaders
* @since 3.15 beta 2
*/
private CTTableColumn[] getTableColumns() {
if (ctColumns == null) {
ctColumns = ctTable.getTableColumns().getTableColumnArray();
}
return ctColumns;
}
/** /**
* *
* Calculates the xpath of the root element for the table. This will be the common part * Calculates the xpath of the root element for the table. This will be the common part
* of all the mapping's xpaths * of all the mapping's xpaths
* Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called.
* *
* @return the xpath of the table's root element * @return the xpath of the table's root element
*/ */
public String getCommonXpath() { public String getCommonXpath() {
if (commonXPath == null) { if (commonXPath == null) {
String[] commonTokens = {}; String[] commonTokens = {};
for (CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()) { for (CTTableColumn column : getTableColumns()) {
if (column.getXmlColumnPr()!=null) { if (column.getXmlColumnPr()!=null) {
String xpath = column.getXmlColumnPr().getXpath(); String xpath = column.getXmlColumnPr().getXpath();
String[] tokens = xpath.split("/"); String[] tokens = xpath.split("/");
@ -166,21 +181,24 @@ public class XSSFTable extends POIXMLDocumentPart {
} }
} }
commonXPath = ""; commonTokens[0] = "";
for (int i = 1 ; i< commonTokens.length;i++) { commonXPath = StringUtil.join(commonTokens, "/");
commonXPath +="/"+commonTokens[i];
}
} }
return commonXPath; return commonXPath;
} }
/**
* Note this list is static - once read, it does not notice later changes to the underlying column structures
* To clear the cache, call {@link #updateHeaders}
* @return List of XSSFXmlColumnPr
*/
public List<XSSFXmlColumnPr> getXmlColumnPrs() { public List<XSSFXmlColumnPr> getXmlColumnPrs() {
if (xmlColumnPr==null) { if (xmlColumnPr==null) {
xmlColumnPr = new ArrayList<XSSFXmlColumnPr>(); xmlColumnPr = new ArrayList<XSSFXmlColumnPr>();
for (CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()) { for (CTTableColumn column: getTableColumns()) {
if (column.getXmlColumnPr()!=null) { if (column.getXmlColumnPr()!=null) {
XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr()); XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
xmlColumnPr.add(columnPr); xmlColumnPr.add(columnPr);
@ -301,6 +319,83 @@ public class XSSFTable extends POIXMLDocumentPart {
} }
cellnum++; cellnum++;
} }
ctColumns = null;
columnMap = null;
xmlColumnPr = null;
commonXPath = null;
} }
} }
private static String caseInsensitive(String s) {
return s.toUpperCase(Locale.ROOT);
}
/**
* Gets the relative column index of a column in this table having the header name <code>column</code>.
* The column index is relative to the left-most column in the table, 0-indexed.
* Returns <code>-1</code> if <code>column</code> is not a header name in table.
*
* Note: this function caches column names for performance. To flush the cache (because columns
* have been moved or column headers have been changed), {@link #updateHeaders()} must be called.
*
* @since 3.15 beta 2
*/
public int findColumnIndex(String column) {
if (columnMap == null) {
// FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap
int count = getTableColumns().length;
columnMap = new HashMap<String, Integer>(count);
for (int i=0; i < count; i++) {
String columnName = getTableColumns()[i].getName();
columnMap.put(caseInsensitive(columnName), 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(caseInsensitive(column.replace("'", "")));
return idx == null ? -1 : idx.intValue();
}
/**
* @since 3.15 beta 2
*/
public String getSheetName() {
return getXSSFSheet().getSheetName();
}
/**
* @since 3.15 beta 2
*/
public boolean isHasTotalsRow() {
return ctTable.getTotalsRowShown();
}
/**
* @since 3.15 beta 2
*/
public int getStartColIndex() {
return getStartCellReference().getCol();
}
/**
* @since 3.15 beta 2
*/
public int getStartRowIndex() {
return getStartCellReference().getRow();
}
/**
* @since 3.15 beta 2
*/
public int getEndColIndex() {
return getEndCellReference().getCol();
}
/**
* @since 3.15 beta 2
*/
public int getEndRowIndex() {
return getEndCellReference().getRow();
}
} }

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -2261,4 +2262,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
public SpreadsheetVersion getSpreadsheetVersion() { public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL2007; return SpreadsheetVersion.EXCEL2007;
} }
/**
* Returns the data table with the given name (case insensitive).
*
* @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) {
if (name != null && sheets != null) {
for (XSSFSheet sheet : sheets) {
for (XSSFTable tbl : sheet.getTables()) {
if (name.equalsIgnoreCase(tbl.getName())) {
return tbl;
}
}
}
}
return null;
}
} }

View File

@ -94,7 +94,7 @@ public final class XSSFFormulaUtils {
String formula = f.getStringValue(); String formula = f.getStringValue();
if (formula != null && formula.length() > 0) { if (formula != null && formula.length() > 0) {
int sheetIndex = _wb.getSheetIndex(cell.getSheet()); 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) { for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName); updatePtg(ptg, oldName, newName);
} }
@ -113,7 +113,8 @@ public final class XSSFFormulaUtils {
String formula = name.getRefersToFormula(); String formula = name.getRefersToFormula();
if (formula != null) { if (formula != null) {
int sheetIndex = name.getSheetIndex(); int sheetIndex = name.getSheetIndex();
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex); int rowIndex = -1; //don't care
Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
for (Ptg ptg : ptgs) { for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName); updatePtg(ptg, oldName, newName);
} }

View File

@ -134,8 +134,9 @@ public final class XSSFRowShifter {
XSSFName name = wb.getNameAt(i); XSSFName name = wb.getNameAt(i);
String formula = name.getRefersToFormula(); String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex(); int sheetIndex = name.getSheetIndex();
final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla); name.setRefersToFormula(shiftedFmla);
@ -218,10 +219,11 @@ public final class XSSFRowShifter {
XSSFSheet sheet = row.getSheet(); XSSFSheet sheet = row.getSheet();
XSSFWorkbook wb = sheet.getWorkbook(); XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet); int sheetIndex = wb.getSheetIndex(sheet);
final int rowIndex = row.getRowNum();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
try { try {
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
String shiftedFmla = null; String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
@ -238,6 +240,7 @@ public final class XSSFRowShifter {
public void updateConditionalFormatting(FormulaShifter shifter) { public void updateConditionalFormatting(FormulaShifter shifter) {
XSSFWorkbook wb = sheet.getWorkbook(); XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet); int sheetIndex = wb.getSheetIndex(sheet);
final int rowIndex = -1; //don't care, structured references not allowed in conditional formatting
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
CTWorksheet ctWorksheet = sheet.getCTWorksheet(); CTWorksheet ctWorksheet = sheet.getCTWorksheet();
@ -283,7 +286,7 @@ public final class XSSFRowShifter {
String[] formulaArray = cfRule.getFormulaArray(); String[] formulaArray = cfRule.getFormulaArray();
for (int i = 0; i < formulaArray.length; i++) { for (int i = 0; i < formulaArray.length; i++) {
String formula = formulaArray[i]; String formula = formulaArray[i];
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex); Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) { if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
cfRule.setFormulaArray(i, shiftedFmla); cfRule.setFormulaArray(i, shiftedFmla);

View File

@ -18,6 +18,11 @@
*/ */
package org.apache.poi.ss.formula; package org.apache.poi.ss.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg; import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
@ -28,7 +33,7 @@ import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook; import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import junit.framework.TestCase; import org.junit.Test;
/** /**
* Test {@link FormulaParser}'s handling of row numbers at the edge of the * Test {@link FormulaParser}'s handling of row numbers at the edge of the
@ -36,8 +41,9 @@ import junit.framework.TestCase;
* *
* @author David North * @author David North
*/ */
public class TestFormulaParser extends TestCase { public class TestFormulaParser {
@Test
public void testHSSFFailsForOver65536() { public void testHSSFFailsForOver65536() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
try { try {
@ -49,16 +55,19 @@ public class TestFormulaParser extends TestCase {
} }
} }
@Test
public void testHSSFPassCase() { public void testHSSFPassCase() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook()); FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0); FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0);
} }
@Test
public void testXSSFWorksForOver65536() { public void testXSSFWorksForOver65536() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0); FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0);
} }
@Test
public void testXSSFFailCase() { public void testXSSFFailCase() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook()); FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
try { try {
@ -71,6 +80,7 @@ public class TestFormulaParser extends TestCase {
} }
// copied from org.apache.poi.hssf.model.TestFormulaParser // copied from org.apache.poi.hssf.model.TestFormulaParser
@Test
public void testMacroFunction() throws Exception { public void testMacroFunction() throws Exception {
// testNames.xlsm contains a VB function called 'myFunc' // testNames.xlsm contains a VB function called 'myFunc'
final String testFile = "testNames.xlsm"; final String testFile = "testNames.xlsm";
@ -126,6 +136,7 @@ public class TestFormulaParser extends TestCase {
} }
} }
@Test
public void testParserErrors() throws Exception { public void testParserErrors() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm");
try { try {

View File

@ -0,0 +1,63 @@
package org.apache.poi.ss.formula;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
import org.apache.poi.ss.usermodel.FormulaEvaluator;
import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
/**
* Tests Excel Table expressions (structured references)
* @see <a href="https://support.office.com/en-us/article/Using-structured-references-with-Excel-tables-F5ED2452-2337-4F71-BED3-C8AE6D2B276E">
* Excel Structured Reference Syntax
* </a>
*/
public class TestStructuredReferences {
/**
* Test the regular expression used in INDIRECT() evaluation to recognize structured references
*/
@Test
public void testTableExpressionSyntax() {
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("abc[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_abc[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[#This Row]").matches());
assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[ [col1], [col2] ]").matches());
// can't have a space between the table name and open bracket
assertFalse("Invalid structured reference syntax didn't fail expression", Table.isStructuredReference.matcher("\\abc [ [col1], [col2] ]").matches());
}
@Test
public void testTableFormulas() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
try {
final FormulaEvaluator eval = new XSSFFormulaEvaluator(wb);
confirm(eval, wb.getSheet("Table").getRow(5).getCell(0), 49);
confirm(eval, wb.getSheet("Formulas").getRow(0).getCell(0), 209);
} finally {
wb.close();
}
}
private static void confirm(FormulaEvaluator fe, Cell cell, double expectedResult) {
fe.clearAllCachedResultValues();
CellValue cv = fe.evaluate(cell);
if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
fail("expected numeric cell type but got " + cv.formatAsString());
}
assertEquals(expectedResult, cv.getNumberValue(), 0.0);
}
}

View File

@ -22,6 +22,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.io.IOException;
import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@ -44,9 +46,12 @@ public final class TestXSSFFormulaParser {
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) { private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1); return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
} }
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla, int rowIndex) {
return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, rowIndex);
}
@Test @Test
public void basicParsing() { public void basicParsing() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -118,10 +123,12 @@ public final class TestXSSFFormulaParser {
assertEquals(AttrPtg.class, ptgs[1].getClass()); assertEquals(AttrPtg.class, ptgs[1].getClass());
assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString()); assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString());
assertEquals("SUM", ptgs[1].toFormulaString()); assertEquals("SUM", ptgs[1].toFormulaString());
wb.close();
} }
@Test @Test
public void builtInFormulas() { public void builtInFormulas() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -134,10 +141,12 @@ public final class TestXSSFFormulaParser {
assertEquals(2, ptgs.length); assertEquals(2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg);
wb.close();
} }
@Test @Test
public void formaulReferncesSameWorkbook() { public void formulaReferencesSameWorkbook() throws IOException {
// Use a test file with "other workbook" style references // Use a test file with "other workbook" style references
// to itself // to itself
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
@ -153,10 +162,12 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString()); assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
wb.close();
} }
@Test @Test
public void formulaReferencesOtherSheets() { public void formulaReferencesOtherSheets() throws IOException {
// Use a test file with the named ranges in place // Use a test file with the named ranges in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@ -193,10 +204,12 @@ public final class TestXSSFFormulaParser {
assertEquals(1, ptgs.length); assertEquals(1, ptgs.length);
assertEquals(NamePtg.class, ptgs[0].getClass()); assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb)); assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb));
wb.close();
} }
@Test @Test
public void formulaReferencesOtherWorkbook() { public void formulaReferencesOtherWorkbook() throws IOException {
// Use a test file with the external linked table in place // Use a test file with the external linked table in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx"); XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@ -228,6 +241,8 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName()); assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName()); assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[1]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString()); assertEquals("[1]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
wb.close();
} }
/** /**
@ -241,7 +256,7 @@ public final class TestXSSFFormulaParser {
* (but not evaluate - that's elsewhere in the test suite) * (but not evaluate - that's elsewhere in the test suite)
*/ */
@Test @Test
public void multiSheetReferencesHSSFandXSSF() throws Exception { public void multiSheetReferencesHSSFandXSSF() throws IOException {
Workbook[] wbs = new Workbook[] { Workbook[] wbs = new Workbook[] {
HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"), HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"),
XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx") XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx")
@ -363,6 +378,8 @@ public final class TestXSSFFormulaParser {
newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA); newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA);
newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)"); newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)");
assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula()); assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula());
wb.close();
} }
} }
@ -374,7 +391,7 @@ public final class TestXSSFFormulaParser {
} }
@Test @Test
public void test58648Single() { public void test58648Single() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -384,10 +401,12 @@ public final class TestXSSFFormulaParser {
2, ptgs.length); 2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
wb.close();
} }
@Test @Test
public void test58648Basic() { public void test58648Basic() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -431,10 +450,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg);
wb.close();
} }
@Test @Test
public void test58648FormulaParsing() { public void test58648FormulaParsing() throws IOException {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx"); Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx");
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
@ -460,10 +481,12 @@ public final class TestXSSFFormulaParser {
Cell cell = sheet.getRow(1).getCell(4); Cell cell = sheet.getRow(1).getCell(4);
assertEquals(5d, cell.getNumericCellValue(), 0d); assertEquals(5d, cell.getNumericCellValue(), 0d);
wb.close();
} }
@Test @Test
public void testWhitespaceInFormula() { public void testWhitespaceInFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -505,10 +528,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg);
wb.close();
} }
@Test @Test
public void testWhitespaceInComplexFormula() { public void testWhitespaceInComplexFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook(); XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs; Ptg[] ptgs;
@ -529,5 +554,172 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg); assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg);
wb.close();
}
@Test
public void parseStructuredReferences() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
/*
The following cases are tested (copied from FormulaParser.parseStructuredReference)
1 Table1[col]
2 Table1[[#Totals],[col]]
3 Table1[#Totals]
4 Table1[#All]
5 Table1[#Data]
6 Table1[#Headers]
7 Table1[#Totals]
8 Table1[#This Row]
9 Table1[[#All],[col]]
10 Table1[[#Headers],[col]]
11 Table1[[#Totals],[col]]
12 Table1[[#All],[col1]:[col2]]
13 Table1[[#Data],[col1]:[col2]]
14 Table1[[#Headers],[col1]:[col2]]
15 Table1[[#Totals],[col1]:[col2]]
16 Table1[[#Headers],[#Data],[col2]]
17 Table1[[#This Row], [col1]]
18 Table1[ [col1]:[col2] ]
*/
final String tbl = "\\_Prime.1";
final String noTotalsRowReason = ": Tables without a Totals row should return #REF! on [#Totals]";
////// Case 1: Evaluate Table1[col] with apostrophe-escaped #-signs ////////
ptgs = parse(fpb, "SUM("+tbl+"[calc='#*'#])");
assertEquals(2, ptgs.length);
// Area3DPxg [sheet=Table ! A2:A7]
assertTrue(ptgs[0] instanceof Area3DPxg);
Area3DPxg ptg0 = (Area3DPxg) ptgs[0];
assertEquals("Table", ptg0.getSheetName());
assertEquals("A2:A7", ptg0.format2DRefAsString());
// Note: structured references are evaluated and resolved to regular 3D area references.
assertEquals("Table!A2:A7", ptg0.toFormulaString());
// AttrPtg [sum ]
assertTrue(ptgs[1] instanceof AttrPtg);
AttrPtg ptg1 = (AttrPtg) ptgs[1];
assertTrue(ptg1.isSum());
////// Case 1: Evaluate "Table1[col]" ////////
ptgs = parse(fpb, tbl+"[Name]");
assertEquals(1, ptgs.length);
assertEquals("Table1[col]", "Table!B2:B7", ptgs[0].toFormulaString());
////// Case 2: Evaluate "Table1[[#Totals],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals],[col]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 3: Evaluate "Table1[#Totals]" ////////
ptgs = parse(fpb, tbl+"[#Totals]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 4: Evaluate "Table1[#All]" ////////
ptgs = parse(fpb, tbl+"[#All]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#All]", "Table!A1:C7", ptgs[0].toFormulaString());
////// Case 5: Evaluate "Table1[#Data]" (excludes Header and Data rows) ////////
ptgs = parse(fpb, tbl+"[#Data]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Data]", "Table!A2:C7", ptgs[0].toFormulaString());
////// Case 6: Evaluate "Table1[#Headers]" ////////
ptgs = parse(fpb, tbl+"[#Headers]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Headers]", "Table!A1:C1", ptgs[0].toFormulaString());
////// Case 7: Evaluate "Table1[#Totals]" ////////
ptgs = parse(fpb, tbl+"[#Totals]");
assertEquals(1, ptgs.length);
assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 8: Evaluate "Table1[#This Row]" ////////
ptgs = parse(fpb, tbl+"[#This Row]", 2);
assertEquals(1, ptgs.length);
assertEquals("Table1[#This Row]", "Table!A3:C3", ptgs[0].toFormulaString());
////// Evaluate "Table1[@]" (equivalent to "Table1[#This Row]") ////////
ptgs = parse(fpb, tbl+"[@]", 2);
assertEquals(1, ptgs.length);
assertEquals("Table!A3:C3", ptgs[0].toFormulaString());
////// Evaluate "Table1[#This Row]" when rowIndex is outside Table ////////
ptgs = parse(fpb, tbl+"[#This Row]", 10);
assertEquals(1, ptgs.length);
assertEquals("Table1[#This Row]", ErrPtg.VALUE_INVALID, ptgs[0]);
////// Evaluate "Table1[@]" when rowIndex is outside Table ////////
ptgs = parse(fpb, tbl+"[@]", 10);
assertEquals(1, ptgs.length);
assertEquals("Table1[@]", ErrPtg.VALUE_INVALID, ptgs[0]);
////// Evaluate "Table1[[#Data],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Data], [Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Data],[col]]", "Table!C2:C7", ptgs[0].toFormulaString());
////// Case 9: Evaluate "Table1[[#All],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#All], [Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#All],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
////// Case 10: Evaluate "Table1[[#Headers],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers], [Number]]");
assertEquals(1, ptgs.length);
// also acceptable: Table1!B1
assertEquals("Table1[[#Headers],[col]]", "Table!C1:C1", ptgs[0].toFormulaString());
////// Case 11: Evaluate "Table1[[#Totals],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals],[Name]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 12: Evaluate "Table1[[#All],[col1]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#All], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#All],[col1]:[col2]]", "Table!B1:C7", ptgs[0].toFormulaString());
////// Case 13: Evaluate "Table1[[#Data],[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Data], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Data],[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
////// Case 14: Evaluate "Table1[[#Headers],[col1]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Headers],[col1]:[col2]]", "Table!B1:C1", ptgs[0].toFormulaString());
////// Case 15: Evaluate "Table1[[#Totals],[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[#Totals], [Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Totals],[col]:[col2]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
////// Case 16: Evaluate "Table1[[#Headers],[#Data],[col]]" ////////
ptgs = parse(fpb, tbl+"[[#Headers],[#Data],[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[#Headers],[#Data],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
////// Case 17: Evaluate "Table1[[#This Row], [col1]]" ////////
ptgs = parse(fpb, tbl+"[[#This Row], [Number]]", 2);
assertEquals(1, ptgs.length);
// also acceptable: Table!C3
assertEquals("Table1[[#This Row], [col1]]", "Table!C3:C3", ptgs[0].toFormulaString());
////// Case 18: Evaluate "Table1[[col]:[col2]]" ////////
ptgs = parse(fpb, tbl+"[[Name]:[Number]]");
assertEquals(1, ptgs.length);
assertEquals("Table1[[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
wb.close();
} }
} }

View File

@ -1137,7 +1137,7 @@ public final class TestXSSFSheet extends BaseTestXSheet {
} }
/** /**
* See bug #50829 * See bug #50829 test data tables
*/ */
@Test @Test
public void tables() throws IOException { public void tables() throws IOException {

View File

@ -1107,4 +1107,36 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException); assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException);
} }
} }
/**
* See bug #57840 test data tables
*/
@Test
public void getTable() throws IOException {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithTable.xlsx");
XSSFTable table1 = wb.getTable("Tabella1");
assertNotNull("Tabella1 was not found in workbook", table1);
assertEquals("Table name", "Tabella1", table1.getName());
assertEquals("Sheet name", "Foglio1", table1.getSheetName());
// Table lookup should be case-insensitive
assertSame("Case insensitive table name lookup", table1, wb.getTable("TABELLA1"));
// If workbook does not contain any data tables matching the provided name, getTable should return null
assertNull("Null table name should not throw NPE", wb.getTable(null));
assertNull("Should not be able to find non-existent table", wb.getTable("Foglio1"));
// If a table is added after getTable is called it should still be reachable by XSSFWorkbook.getTable
// This test makes sure that if any caching is done that getTable never uses a stale cache
XSSFTable table2 = wb.getSheet("Foglio2").createTable();
table2.setName("Table2");
assertSame("Did not find Table2", table2, wb.getTable("Table2"));
// If table name is modified after getTable is called, the table can only be found by its new name
// This test makes sure that if any caching is done that getTable never uses a stale cache
table1.setName("Table1");
assertSame("Did not find Tabella1 renamed to Table1", table1, wb.getTable("TABLE1"));
wb.close();
}
} }

View File

@ -27,7 +27,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({ @Suite.SuiteClasses({
TestDrawingManager.class, TestDrawingManager.class,
TestDrawingManager2.class, TestDrawingManager2.class,
TestFormulaParser.class, //TestFormulaParser.class, //converted to junit4
TestFormulaParserEval.class, TestFormulaParserEval.class,
TestFormulaParserIf.class, TestFormulaParserIf.class,
TestLinkTable.class, TestLinkTable.class,

View File

@ -1285,7 +1285,7 @@ public final class TestFormulaParser {
String formula = "Sheet1!A1:Sheet1!B3"; String formula = "Sheet1!A1:Sheet1!B3";
HSSFWorkbook wb = new HSSFWorkbook(); HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1"); 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) { if (ptgs.length == 3) {
confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class); confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class);
@ -1486,8 +1486,8 @@ public final class TestFormulaParser {
confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference."); 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!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or data table");
confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, or defined name"); confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or data table");
confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found"); 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:Sheet1!G", "'Sheet1!F' is not a proper reference.");
confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11."); confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11.");