]> source.dussan.org Git - poi.git/commitdiff
Bug 55640, added reproducer, a fix for the Exception cases and some verify-tests...
authorDominik Stadler <centic@apache.org>
Mon, 2 Dec 2013 19:14:03 +0000 (19:14 +0000)
committerDominik Stadler <centic@apache.org>
Mon, 2 Dec 2013 19:14:03 +0000 (19:14 +0000)
changes are checked via unit tests. There might be more work pending to
make grouping fully work the same way as Excel does.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1547153 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetRowGrouping.java [new file with mode: 0644]
test-data/spreadsheet/55640.xlsx [new file with mode: 0644]
test-data/spreadsheet/GroupTest.xlsx [new file with mode: 0644]

index 0ebe113a261b4c639997b92d076ff770d4964244..afa56a57715cfbea9037ddde7ca04d66c7374811 100644 (file)
@@ -2154,6 +2154,12 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
         int level = xRow.getCTRow().getOutlineLevel();
         for (Iterator<Row> it = rowIterator(); it.hasNext();) {
             xRow = (XSSFRow) it.next();
+            
+            // skip rows before the start of this group
+            if(xRow.getRowNum() < rowIndex) {
+                continue;
+            }
+
             if (xRow.getCTRow().getOutlineLevel() >= level) {
                 xRow.getCTRow().setHidden(hidden);
                 rowIndex++;
@@ -2171,8 +2177,9 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
             return;
         XSSFRow row = getRow(rowNumber);
         // If it is already expanded do nothing.
-        if (!row.getCTRow().isSetHidden())
+        if (!row.getCTRow().isSetHidden()) {
             return;
+        }
 
         // Find the start of the group.
         int startIdx = findStartOfRowOutlineGroup(rowNumber);
@@ -2202,7 +2209,11 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
             }
         }
         // Write collapse field
-        getRow(endIdx).getCTRow().unsetCollapsed();
+        CTRow ctRow = getRow(endIdx).getCTRow();
+        // This avoids an IndexOutOfBounds if multiple nested groups are collapsed/expanded
+        if(ctRow.getCollapsed()) {
+            ctRow.unsetCollapsed();
+        }
     }
 
     /**
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetRowGrouping.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetRowGrouping.java
new file mode 100644 (file)
index 0000000..cb8fc60
--- /dev/null
@@ -0,0 +1,441 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xssf.usermodel;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.XSSFTestDataSamples;
+
+public final class TestXSSFSheetRowGrouping extends TestCase {
+
+    private static final int ROWS_NUMBER = 200;
+    private static final int GROUP_SIZE = 5;
+
+    //private int o_groupsNumber = 0;
+
+    public void test55640() throws IOException {
+        //long startTime = System.currentTimeMillis();
+        Workbook wb = new XSSFWorkbook();
+        fillData(wb);
+        writeToFile(wb);
+
+        //System.out.println("Number of groups: " + o_groupsNumber);
+        //System.out.println("Execution time: " + (System.currentTimeMillis()-startTime) + " ms");
+    }
+
+
+    private void fillData(Workbook p_wb) {
+        Sheet sheet = p_wb.createSheet("sheet123");
+        sheet.setRowSumsBelow(false);
+
+        for (int i = 0; i < ROWS_NUMBER; i++) {
+            Row row = sheet.createRow(i);
+            Cell cell = row.createCell(0);
+            cell.setCellValue(i+1);
+        }
+
+        int i = 1;
+        while (i < ROWS_NUMBER) {
+            int end = i+(GROUP_SIZE-2);
+            int start = i;                    // natural order
+//            int start = end - 1;                // reverse order
+            while (start < end) {             // natural order
+//                while (start >= i) {            // reverse order
+                sheet.groupRow(start, end);
+                //o_groupsNumber++;
+                boolean collapsed = isCollapsed();
+                //System.out.println("Set group " + start + "->" + end + " to " + collapsed);
+                sheet.setRowGroupCollapsed(start, collapsed);
+                start++;                      // natural order
+//                start--;                        // reverse order
+            }
+            i += GROUP_SIZE;
+        }
+    }
+
+    private boolean isCollapsed() {
+        return Math.random() > 0.5d;
+    }
+
+    private void writeToFile(Workbook p_wb) throws IOException {
+//        FileOutputStream fileOut = new FileOutputStream("/tmp/55640.xlsx");
+//        try {
+//            p_wb.write(fileOut);
+//        } finally {
+//            fileOut.close();
+//        }
+        assertNotNull(XSSFTestDataSamples.writeOutAndReadBack(p_wb));
+    }
+
+    public void test55640reduce1() throws IOException {
+        Workbook wb = new XSSFWorkbook();
+        Sheet sheet = wb.createSheet("sheet123");
+        sheet.setRowSumsBelow(false);
+
+        for (int i = 0; i < ROWS_NUMBER; i++) {
+            Row row = sheet.createRow(i);
+            Cell cell = row.createCell(0);
+            cell.setCellValue(i+1);
+        }
+
+        int i = 1;
+        while (i < ROWS_NUMBER) {
+            int end = i+(GROUP_SIZE-2);
+            int start = i;                    // natural order
+            while (start < end) {             // natural order
+                sheet.groupRow(start, end);
+                //o_groupsNumber++;
+                boolean collapsed = start % 2 == 0 ? false : true;
+                //System.out.println("Set group " + start + "->" + end + " to " + collapsed);
+                sheet.setRowGroupCollapsed(start, collapsed);
+                start++;                      // natural order
+            }
+            i += GROUP_SIZE;
+        }
+        writeToFile(wb);
+    }
+
+
+    public void test55640_VerifyCases() throws IOException {
+        // NOTE: This is currently based on current behavior of POI, somehow
+        // what POI returns in the calls to collapsed/hidden is not fully matching 
+        // the examples in the spec or I did not fully understand how POI stores the data internally...
+        
+        // all expanded
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                false, false, false, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, false}, 
+                // hidden:
+                new boolean[] { false, false, false, false, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3 }
+                );
+
+        
+        // Level 1 collapsed, others expanded, should only have 4 rows, all hidden: 
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                true, false, false, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, false},
+                // hidden:
+                new boolean[] { true, true, true, true, true},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3 }
+                );
+
+        // Level 1 and 2 collapsed, Level 3 expanded, 
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                true, true, false, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, true, false},
+                // hidden:
+                new boolean[] { true, true, true, true, true, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3, 0 }
+                );
+
+        // Level 1 collapsed, Level 2 expanded, Level 3 collapsed 
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                true, false, true, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, false, true},
+                // hidden:
+                new boolean[] { true, true, true, true, true, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3, 0 }
+                );
+
+        // Level 2 collapsed, others expanded:
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                false, true, false, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, false, false},
+                // hidden:
+                new boolean[] { false, true, true, true, true, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3, 0 }
+                );
+
+        // Level 3 collapsed, others expanded 
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                false, false, true, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, false, true},
+                // hidden:
+                new boolean[] { false, false, true, true, true, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3, 0 }
+                );
+
+        // All collapsed 
+        verifyGroupCollapsed(
+                // level1, level2, level3
+                true, true, true, 
+                // collapsed:
+                new Boolean[] { false, false, false, false, true, true},
+                // hidden:
+                new boolean[] { true, true, true, true, true, false},
+                // outlineLevel
+                new int[] { 1, 2, 3, 3, 3, 0 }
+                );
+    }
+    
+   
+    private void verifyGroupCollapsed(boolean level1, boolean level2, boolean level3, 
+            Boolean[] collapsed, boolean[] hidden, int[] outlineLevel) throws IOException {
+        Workbook wb = new XSSFWorkbook();
+        Sheet sheet = wb.createSheet("sheet123");
+
+        for (int i = 0; i < 4; i++) {
+            sheet.createRow(i);
+        }
+        
+        sheet.groupRow(0, 4);
+        sheet.groupRow(1, 4);
+        sheet.groupRow(2, 4);
+        
+        sheet.setRowGroupCollapsed(0, level1);
+        sheet.setRowGroupCollapsed(1, level2);
+        sheet.setRowGroupCollapsed(2, level3);
+
+        checkWorkbookGrouping(wb, collapsed, hidden, outlineLevel);
+    }
+
+    public void test55640_VerifyCasesSpec() throws IOException {
+        // NOTE: This is currently based on current behavior of POI, somehow
+        // what POI returns in the calls to collapsed/hidden is not fully matching 
+        // the examples in the spec or I did not fully understand how POI stores the data internally...
+        
+        // all expanded
+        verifyGroupCollapsedSpec(
+                // level3, level2, level1
+                false, false, false, 
+                // collapsed:
+                new Boolean[] { false, false, false, false}, 
+                // hidden:
+                new boolean[] { false, false, false, false},
+                // outlineLevel
+                new int[] { 3, 3, 2, 1 }
+                );
+
+        
+        verifyGroupCollapsedSpec(
+                // level3, level2, level1
+                false, false, true, 
+                // collapsed:
+                new Boolean[] { false, false, false, true},
+                // hidden:
+                new boolean[] { true, true, true, false},
+                // outlineLevel
+                new int[] { 3, 3, 2, 1 }
+                );
+
+        verifyGroupCollapsedSpec(
+                // level3, level2, level1
+                false, true, false, 
+                // collapsed:
+                new Boolean[] { false, false, true, false},
+                // hidden:
+                new boolean[] { true, true, true, false},
+                // outlineLevel
+                new int[] { 3, 3, 2, 1 }
+                );
+        
+        verifyGroupCollapsedSpec(
+                // level3, level2, level1
+                false, true, true, 
+                // collapsed:
+                new Boolean[] { false, false, true, true},
+                // hidden:
+                new boolean[] { true, true, true, false},
+                // outlineLevel
+                new int[] { 3, 3, 2, 1 }
+                );
+    }
+
+    private void verifyGroupCollapsedSpec(boolean level1, boolean level2, boolean level3, 
+            Boolean[] collapsed, boolean[] hidden, int[] outlineLevel) throws IOException {
+        Workbook wb = new XSSFWorkbook();
+        Sheet sheet = wb.createSheet("sheet123");
+
+        for (int i = 5; i < 9; i++) {
+            sheet.createRow(i);
+        }
+        
+        sheet.groupRow(5, 6);
+        sheet.groupRow(5, 7);
+        sheet.groupRow(5, 8);
+        
+        sheet.setRowGroupCollapsed(6, level1);
+        sheet.setRowGroupCollapsed(7, level2);
+        sheet.setRowGroupCollapsed(8, level3);
+        
+        checkWorkbookGrouping(wb, collapsed, hidden, outlineLevel);
+    }
+
+    private void checkWorkbookGrouping(Workbook wb, Boolean[] collapsed, boolean[] hidden, int[] outlineLevel) throws IOException {
+        printWorkbook(wb);
+        Sheet sheet = wb.getSheetAt(0);
+        
+        assertEquals(collapsed.length, hidden.length);
+        assertEquals(collapsed.length, outlineLevel.length);
+        assertEquals("Expected " + collapsed.length + " rows with collapsed state, but had " + (sheet.getLastRowNum()-sheet.getFirstRowNum()+1) + " rows ("
+                + sheet.getFirstRowNum() + "-" + sheet.getLastRowNum() + ")",  
+                collapsed.length, sheet.getLastRowNum()-sheet.getFirstRowNum()+1);
+        for(int i = sheet.getFirstRowNum(); i < sheet.getLastRowNum();i++) {
+            if(collapsed[i-sheet.getFirstRowNum()] == null) {
+                continue;
+            }
+            XSSFRow row = (XSSFRow) sheet.getRow(i);
+            assertNotNull("Could not read row " + i, row);
+            assertNotNull("Could not read row " + i, row.getCTRow());
+            assertEquals("Row: " + i + ": collapsed", collapsed[i-sheet.getFirstRowNum()].booleanValue(), row.getCTRow().getCollapsed());
+            assertEquals("Row: " + i + ": hidden", hidden[i-sheet.getFirstRowNum()], row.getCTRow().getHidden());
+            
+            assertEquals("Row: " + i + ": level", outlineLevel[i-sheet.getFirstRowNum()], row.getCTRow().getOutlineLevel());
+        }
+        
+        writeToFile(wb);
+    }
+
+    public void test55640working() throws IOException {
+        Workbook wb = new XSSFWorkbook();
+        Sheet sheet = wb.createSheet("sheet123");
+
+        sheet.groupRow(1, 4);
+        sheet.groupRow(2, 5);
+        sheet.groupRow(3, 6);
+        
+        sheet.setRowGroupCollapsed(1, true);
+        sheet.setRowGroupCollapsed(2, false);
+        sheet.setRowGroupCollapsed(3, false);
+
+        writeToFile(wb);
+    }
+
+    // just used for printing out contents of spreadsheets
+    public void notRuntest55640printSample() {
+        XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("55640.xlsx");
+        printWorkbook(wb);
+        
+        wb = XSSFTestDataSamples.openSampleWorkbook("GroupTest.xlsx");
+        printWorkbook(wb);
+    }
+
+    private void printWorkbook(Workbook wb) {
+        // disable all output for now...
+//        Sheet sheet = wb.getSheetAt(0);
+//        
+//        for(Iterator<Row> it = sheet.rowIterator();it.hasNext();) {
+//            XSSFRow row = (XSSFRow) it.next();
+//            boolean collapsed = row.getCTRow().getCollapsed();
+//            boolean hidden = row.getCTRow().getHidden();
+//            short level = row.getCTRow().getOutlineLevel();
+//            
+//            System.out.println("Row: " + row.getRowNum() + ": Level: " + level + " Collapsed: " + collapsed + " Hidden: " + hidden);
+//        }
+    }
+    
+    public void testGroupingTest() throws IOException {
+        XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("GroupTest.xlsx");       
+        
+        assertEquals(31, wb.getSheetAt(0).getLastRowNum());
+        
+        // NOTE: This is currently based on current behavior of POI, somehow
+        // what POI returns in the calls to collapsed/hidden is not fully matching 
+        // the examples in the spec or I did not fully understand how POI stores the data internally...
+        checkWorkbookGrouping(wb, 
+                new Boolean [] {
+                    // 0-4
+                    false, false, false, false, false, null, null, 
+                    // 7-11
+                    false, false, true, true, true, null, null, 
+                    // 14-18
+                    false, false, true, false, false, null,
+                    // 20-24
+                    false, false, true, true, false, null, null, 
+                    // 27-31
+                    false, false, false, true, false }, 
+                new boolean[] { 
+                    // 0-4
+                    false, false, false, false, false, false, false,  
+                    // 7-11
+                    true,  true, true, true, false, false, false, 
+                    // 14-18
+                    true, true, false, false, false, false,  
+                    // 20-24
+                    true, true, true, false, false, false, false, 
+                    // 27-31
+                    true, true, true, true, false },
+                // outlineLevel
+                new int[] { 
+                    // 0-4
+                    3, 3, 2, 1, 0, 0, 0,
+                    // 7-11
+                    3, 3, 2, 1, 0, 0, 0,
+                    // 14-18
+                    3, 3, 2, 1, 0, 0,
+                    // 20-24
+                    3, 3, 2, 1, 0, 0, 0, 
+                    // 27-31
+                    3, 3, 2, 1, 0,
+                }
+                );
+        /*
+Row: 0: Level: 3 Collapsed: false Hidden: false
+Row: 1: Level: 3 Collapsed: false Hidden: false
+Row: 2: Level: 2 Collapsed: false Hidden: false
+Row: 3: Level: 1 Collapsed: false Hidden: false
+Row: 4: Level: 0 Collapsed: false Hidden: false
+Row: 7: Level: 3 Collapsed: false Hidden: true
+Row: 8: Level: 3 Collapsed: false Hidden: true
+Row: 9: Level: 2 Collapsed: true Hidden: true
+Row: 10: Level: 1 Collapsed: true Hidden: true
+Row: 11: Level: 0 Collapsed: true Hidden: false
+Row: 14: Level: 3 Collapsed: false Hidden: true
+Row: 15: Level: 3 Collapsed: false Hidden: true
+Row: 16: Level: 2 Collapsed: true Hidden: false
+Row: 17: Level: 1 Collapsed: false Hidden: false
+Row: 18: Level: 0 Collapsed: false Hidden: false
+Row: 20: Level: 3 Collapsed: false Hidden: true
+Row: 21: Level: 3 Collapsed: false Hidden: true
+Row: 22: Level: 2 Collapsed: true Hidden: true
+Row: 23: Level: 1 Collapsed: true Hidden: false
+Row: 24: Level: 0 Collapsed: false Hidden: false
+Row: 27: Level: 3 Collapsed: false Hidden: true
+Row: 28: Level: 3 Collapsed: false Hidden: true
+Row: 29: Level: 2 Collapsed: false Hidden: true
+Row: 30: Level: 1 Collapsed: true Hidden: true
+Row: 31: Level: 0 Collapsed: true Hidden: false
+         */
+    }
+}
\ No newline at end of file
diff --git a/test-data/spreadsheet/55640.xlsx b/test-data/spreadsheet/55640.xlsx
new file mode 100644 (file)
index 0000000..d353bb3
Binary files /dev/null and b/test-data/spreadsheet/55640.xlsx differ
diff --git a/test-data/spreadsheet/GroupTest.xlsx b/test-data/spreadsheet/GroupTest.xlsx
new file mode 100644 (file)
index 0000000..5feb37e
Binary files /dev/null and b/test-data/spreadsheet/GroupTest.xlsx differ