}
Table tbl = _book.getTable(tableName);
if (tbl == null) {
- throw new FormulaParseException("Illegal table name!");
+ throw new FormulaParseException("Illegal table name: '" + tableName + "'");
}
String sheetName = tbl.getSheetName();
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();
} else if (specName.equals(specTotals)) {
isTotalsSpec = true;
} else {
- throw new FormulaParseException("Unknown special qunatifier "+ specName);
+ throw new FormulaParseException("Unknown special quantifier "+ specName);
}
- nSpecQuantifiers++ ;
+ nSpecQuantifiers++;
if (look == ','){
GetChar();
} else {
} else if (name.equals(specTotals)) {
isTotalsSpec = true;
} else {
- throw new FormulaParseException("Unknown special qunatifier "+ name);
+ throw new FormulaParseException("Unknown special quantifier "+ name);
}
nSpecQuantifiers++;
} else {
} 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;
actualStartRow++;
}
}
+
//Selecting cols
- if (nColQuantifiers == 2){
- if (startColumnName == null || endColumnName == null){
+ if (nColQuantifiers == 2) {
+ if (startColumnName == null || endColumnName == null) {
throw new IllegalStateException("Fatal error");
}
int startIdx = tbl.findColumnIndex(startColumnName);
actualStartCol = startCol+ startIdx;
actualEndCol = startCol + endIdx;
- } else if(nColQuantifiers == 1){
- if (startColumnName == null){
+ } else if (nColQuantifiers == 1 && !isThisRow) {
+ if (startColumnName == null) {
throw new IllegalStateException("Fatal error");
}
int idx = tbl.findColumnIndex(startColumnName);
actualStartCol = startCol + idx;
actualEndCol = actualStartCol;
}
- CellReference tl = new CellReference(actualStartRow, actualStartCol);
- CellReference br = new CellReference(actualEndRow, actualEndCol);
+ 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(tl, br), sheetIden);
+ Ptg ptg = _book.get3DReferencePtg(new AreaReference(topLeft, bottomRight), sheetIden);
return new ParseNode(ptg);
}
* Caller should save pointer.
* @return
*/
- private String parseAsColumnQuantifier(){
+ private String parseAsColumnQuantifier() {
if ( look != '[') {
return null;
}
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.IOException;
+
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
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
public void basicParsing() {
}
@Test
- public void formaulReferncesSameWorkbook() {
+ public void formulaReferencesSameWorkbook() {
// Use a test file with "other workbook" style references
// to itself
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg);
}
+
+ @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();
+ }
}