From: Javen O'Neal Date: Fri, 10 Jun 2016 00:21:59 +0000 (+0000) Subject: bug 57840: apply patch from Daniel Livshen and Greg Woolsey to get structured referen... X-Git-Tag: REL_3_15_BETA2~183^2~14 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=03faebdf08e15da8b239867f6cfc571b46a276d6;p=poi.git 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 --- diff --git a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java index 1f295d0f33..e5f2afd250 100644 --- a/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/HSSFFormulaParser.java @@ -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); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java index dbf85b869b..1d9459a71a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFEvaluationWorkbook.java @@ -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"); + } } diff --git a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java index b0a3c76060..45e6351f53 100644 --- a/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/EvaluationWorkbook.java @@ -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); diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index 3820043d1f..b2e7acf730 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -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. -1 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: + *
+	 * 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] ]
+	 * 
+ * @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 == '.') { diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java index fe907fbb1d..0fe93eec7f 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java @@ -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 */ diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java index cc590772de..277fbfc159 100644 --- a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java +++ b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java @@ -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 { diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index f4b30c4f0e..84099bf2bb 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -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.

* @@ -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: diff --git a/src/java/org/apache/poi/ss/formula/functions/Indirect.java b/src/java/org/apache/poi/ss/formula/functions/Indirect.java index 7774af506d..7366c798cc 100644 --- a/src/java/org/apache/poi/ss/formula/functions/Indirect.java +++ b/src/java/org/apache/poi/ss/formula/functions/Indirect.java @@ -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

@@ -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); + } } /** diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java index 4a6882ac42..25d80a3721 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java @@ -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(); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 29aaa867e2..f542e0f172 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -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); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java index 70fc720671..b3d3343ec5 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java @@ -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()); } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java index ecbec23b5a..be68c30500 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java @@ -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); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java index c8a932bb98..72e3d341ad 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java @@ -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 xmlColumnPr; + private CTTableColumn[] ctColumns; + private HashMap 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 getXmlColumnPrs() { if (xmlColumnPr==null) { xmlColumnPr = new ArrayList(); - 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(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(); } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 100c2b95bd..622979d167 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -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 _tableCache = null; + private Map getTableCache() { + if ( _tableCache != null ) { + return _tableCache; + } + // FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap + _tableCache = new HashMap(); + + 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 name, or null if no table is named name. + * @since 3.15 beta 2 + */ + public XSSFTable getTable(String name) { + String lname = name.toLowerCase(Locale.ROOT); + return getTableCache().get(lname); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java index a9d995fb72..50190b0a6b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java @@ -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); } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java index 80462ac4c8..77236913b9 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java @@ -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); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java index 83dabacb9f..a9f697ba9e 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java @@ -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 diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index f39d07b94b..f99672b7cf 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -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."); diff --git a/src/testcases/org/apache/poi/hssf/record/TestArrayRecord.java b/src/testcases/org/apache/poi/hssf/record/TestArrayRecord.java index ff5fe0ad9b..7affe462e6 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestArrayRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestArrayRecord.java @@ -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 diff --git a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java index 3a5a43493e..7b81c7cff7 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java @@ -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));