bug 57840: apply patch from Daniel Livshen and Greg Woolsey to get structured references working in XSSFWorkbook formulas

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xssf_structured_references@1747612 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2016-06-10 00:21:59 +00:00
parent 944901f8f2
commit 83abda843a
20 changed files with 528 additions and 47 deletions

View File

@ -67,7 +67,7 @@ public final class HSSFFormulaParser {
* @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);
}
/**

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.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;
@ -267,4 +268,8 @@ public final class HSSFEvaluationWorkbook implements FormulaRenderingWorkbook, E
public SpreadsheetVersion getSpreadsheetVersion(){
return SpreadsheetVersion.EXCEL97;
}
public Table getTable(String name) {
throw new IllegalStateException("XSSF-style tables are not supported for HSSF");
}
}

View File

@ -68,6 +68,7 @@ public interface EvaluationWorkbook {
*/
ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber);
EvaluationName getName(NamePtg namePtg);
EvaluationName getName(String name, int sheetIndex);
String resolveNameXText(NameXPtg ptg);

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.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;
@ -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.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;
@ -117,6 +119,7 @@ public final class FormulaParser {
private final SpreadsheetVersion _ssVersion;
private final int _sheetIndex;
private final int _rowIndex; // 0-based
/**
@ -131,13 +134,14 @@ public final class FormulaParser {
* 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;
}
/**
@ -148,18 +152,41 @@ public final class FormulaParser {
* @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
@ -528,6 +555,266 @@ 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("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
@ -558,6 +845,11 @@ public final class FormulaParser {
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")));
}
@ -581,9 +873,9 @@ public final class FormulaParser {
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);
@ -1175,7 +1467,9 @@ public final class FormulaParser {
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 == '.') {

View File

@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
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;
@ -41,6 +42,11 @@ public interface FormulaParsingWorkbook {
*/
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
*/

View File

@ -407,6 +407,9 @@ public final class OperationEvaluationContext {
return new FunctionNameEval(name);
}
}
public int getSheetIndex() {
return _sheetIndex;
}
private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
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.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/>
*
@ -259,6 +299,15 @@ public final class WorkbookEvaluator {
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 {
@ -270,9 +319,9 @@ public final class WorkbookEvaluator {
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:

View File

@ -17,13 +17,18 @@
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/>
@ -42,7 +47,7 @@ import org.apache.poi.ss.formula.OperationEvaluationContext;
*/
public final class Indirect implements FreeRefFunction {
public static final FreeRefFunction instance = new Indirect();
public static final FreeRefFunction instance = new Indirect();
private Indirect() {
// enforce singleton
@ -88,8 +93,12 @@ public final class Indirect implements FreeRefFunction {
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('!');
@ -112,16 +121,25 @@ public final class Indirect implements FreeRefFunction {
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);
}
}
/**

View File

@ -36,6 +36,7 @@ import org.apache.poi.ss.formula.ptg.Ptg;
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;
@ -310,6 +311,10 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
return _uBook.createName();
}
public Table getTable(String name) {
return _uBook.getTable(name);
}
public UDFFinder getUDFFinder(){
return _uBook.getUDFFinder();
}

View File

@ -506,7 +506,7 @@ public final class XSSFCell implements Cell {
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);
@ -550,7 +550,7 @@ public final class XSSFCell implements Cell {
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);

View File

@ -50,6 +50,6 @@ public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
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());
}
}

View File

@ -192,7 +192,7 @@ public final class XSSFName implements Name {
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);
}
@ -203,7 +203,7 @@ public final class XSSFName implements Name {
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);
}

View File

@ -24,11 +24,13 @@ import java.io.InputStream;
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;
@ -48,10 +50,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
*
* @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;
@ -131,6 +135,12 @@ public class XSSFTable extends POIXMLDocumentPart {
return maps;
}
private CTTableColumn[] getTableColumns() {
if (ctColumns == null) {
ctColumns = ctTable.getTableColumns().getTableColumnArray();
}
return ctColumns;
}
/**
*
@ -142,7 +152,7 @@ public class XSSFTable extends POIXMLDocumentPart {
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("/");
@ -176,11 +186,15 @@ public class XSSFTable extends POIXMLDocumentPart {
}
/**
* 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);
@ -301,6 +315,46 @@ public class XSSFTable extends POIXMLDocumentPart {
}
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();
}
}

View File

@ -33,6 +33,7 @@ import java.util.HashMap;
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;
@ -2261,4 +2262,49 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
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);
}
}

View File

@ -94,7 +94,7 @@ public final class XSSFFormulaUtils {
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);
}
@ -113,7 +113,7 @@ public final class XSSFFormulaUtils {
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);
}

View File

@ -135,7 +135,7 @@ public final class XSSFRowShifter {
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);
@ -221,7 +221,7 @@ public final class XSSFRowShifter {
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);
@ -283,7 +283,7 @@ public final class XSSFRowShifter {
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);

View File

@ -42,7 +42,7 @@ import java.util.Arrays;
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

View File

@ -1197,7 +1197,7 @@ public final class TestFormulaParser {
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
@ -1285,7 +1285,7 @@ public final class TestFormulaParser {
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);
@ -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, "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.");

View File

@ -51,7 +51,7 @@ public final class TestArrayRecord extends TestCase {
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

View File

@ -107,7 +107,7 @@ public final class TestSharedFormulaRecord extends TestCase {
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
@ -123,7 +123,7 @@ public final class TestSharedFormulaRecord extends TestCase {
//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));