]> source.dussan.org Git - poi.git/commitdiff
bug 57840: add structured reference formula parsing tests from FormulaParser.parseStr...
authorJaven O'Neal <onealj@apache.org>
Fri, 10 Jun 2016 07:33:30 +0000 (07:33 +0000)
committerJaven O'Neal <onealj@apache.org>
Fri, 10 Jun 2016 07:33:30 +0000 (07:33 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xssf_structured_references@1747655 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/formula/FormulaParser.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java

index 74cbd87fb9bdf3a32560bf228c606e1e2b53243f..3ddcd74639a2d1a2267c7a3c98e4662cfba15ebb 100644 (file)
@@ -614,7 +614,7 @@ public final class FormulaParser {
         }
         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();
         
@@ -623,6 +623,8 @@ public final class FormulaParser {
         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();
         
@@ -650,9 +652,9 @@ public final class FormulaParser {
             } 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 {
@@ -708,7 +710,7 @@ public final class FormulaParser {
                     } else if (name.equals(specTotals)) {
                         isTotalsSpec  = true;
                     } else {
-                        throw new FormulaParseException("Unknown special qunatifier "+ name);
+                        throw new FormulaParseException("Unknown special quantifier "+ name);
                     }
                     nSpecQuantifiers++;
                 } else {
@@ -718,6 +720,22 @@ public final class FormulaParser {
         } 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;
@@ -756,10 +774,11 @@ public final class FormulaParser {
                 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);
@@ -770,8 +789,8 @@ public final class FormulaParser {
             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);
@@ -781,10 +800,10 @@ public final class FormulaParser {
             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);
     }
     
@@ -793,7 +812,7 @@ public final class FormulaParser {
      * Caller should save pointer.
      * @return
     */
-    private String parseAsColumnQuantifier(){
+    private String parseAsColumnQuantifier() {
         if ( look != '[') {
             return null;
         }
index d6802d0fb936b67e501c4779fcefdd33241a8908..bda58860da1ff08e6c502b134e6800dea0773dae 100644 (file)
@@ -22,6 +22,8 @@ import static org.junit.Assert.assertNotNull;
 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;
@@ -44,6 +46,9 @@ public final class TestXSSFFormulaParser {
     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() {
@@ -137,7 +142,7 @@ public final class TestXSSFFormulaParser {
     }
     
     @Test
-    public void formaulReferncesSameWorkbook() {
+    public void formulaReferencesSameWorkbook() {
         // Use a test file with "other workbook" style references
         //  to itself
         XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
@@ -530,4 +535,169 @@ public final class TestXSSFFormulaParser {
         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();
+    }
 }