]> source.dussan.org Git - poi.git/commitdiff
Fix for bug 46280 - RowRecordsAggregate should allow for ContinueRecords after Unkown...
authorJosh Micich <josh@apache.org>
Mon, 24 Nov 2008 22:40:46 +0000 (22:40 +0000)
committerJosh Micich <josh@apache.org>
Mon, 24 Nov 2008 22:40:46 +0000 (22:40 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@720318 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/RecordOrderer.java
src/java/org/apache/poi/hssf/model/RowBlocksReader.java
src/java/org/apache/poi/hssf/record/UnknownRecord.java
src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java
src/testcases/org/apache/poi/hssf/model/AllModelTests.java
src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java

index 1580f7ed7c2198d52f8ace7dad9609f5af2f5580..784f19875e3e29af0b551cd1a410cb2b832c725c 100644 (file)
@@ -37,7 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.5-beta5" date="2008-??-??">
-          <action dev="POI-DEVELOPERS" type="fix"><!--remove me--></action>
+           <action dev="POI-DEVELOPERS" type="fix">46280 - Fixed RowRecordsAggregate etc to properly skip PivotTable records</action>
         </release>
         <release version="3.5-beta4" date="2008-11-29">
            <action dev="POI-DEVELOPERS" type="fix">46213 - Fixed FormulaRecordAggregate to gracefully ignore extra StringRecords</action>
index a6bc5997fa022f7d7f2eac0e9e56708f96730e0a..59d6b999c86cc435528ccbebdb3fc7135bb96f35 100644 (file)
@@ -34,7 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.5-beta5" date="2008-??-??">
-          <action dev="POI-DEVELOPERS" type="fix"><!--remove me--></action>
+           <action dev="POI-DEVELOPERS" type="fix">46280 - Fixed RowRecordsAggregate etc to properly skip PivotTable records</action>
         </release>
         <release version="3.5-beta4" date="2008-11-29">
            <action dev="POI-DEVELOPERS" type="fix">46213 - Fixed FormulaRecordAggregate to gracefully ignore extra StringRecords</action>
index 394825917d3065be37d131cb2725833c5e060729..6aaa92e8877a06b375074e74aa1547e247fc5896 100644 (file)
@@ -85,12 +85,12 @@ final class RecordOrderer {
         * Adds the specified new record in the correct place in sheet records list
         * 
         */
-       public static void addNewSheetRecord(List sheetRecords, RecordBase newRecord) {
+       public static void addNewSheetRecord(List<RecordBase> sheetRecords, RecordBase newRecord) {
                int index = findSheetInsertPos(sheetRecords, newRecord.getClass());
                sheetRecords.add(index, newRecord);
        }
 
-       private static int findSheetInsertPos(List records, Class recClass) {
+       private static int findSheetInsertPos(List<RecordBase> records, Class recClass) {
                if (recClass == DataValidityTable.class) {
                        return findDataValidationTableInsertPos(records);
                }
@@ -109,7 +109,7 @@ final class RecordOrderer {
                throw new RuntimeException("Unexpected record class (" + recClass.getName() + ")");
        }
 
-       private static int getPageBreakRecordInsertPos(List records) {
+       private static int getPageBreakRecordInsertPos(List<RecordBase> records) {
                int dimensionsIndex = getDimensionsIndex(records);
                int i = dimensionsIndex-1;
                while (i > 0) {
@@ -152,7 +152,7 @@ final class RecordOrderer {
        /**
         * Find correct position to add new CFHeader record
         */
-       private static int findInsertPosForNewCondFormatTable(List records) {
+       private static int findInsertPosForNewCondFormatTable(List<RecordBase> records) {
 
                for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record
                        Object rb = records.get(i);
@@ -175,7 +175,7 @@ final class RecordOrderer {
                throw new RuntimeException("Did not find Window2 record");
        }
 
-       private static int findInsertPosForNewMergedRecordTable(List records) {
+       private static int findInsertPosForNewMergedRecordTable(List<RecordBase> records) {
                for (int i = records.size() - 2; i >= 0; i--) { // -2 to skip EOF record
                        Object rb = records.get(i);
                        if (!(rb instanceof Record)) {
@@ -219,14 +219,14 @@ final class RecordOrderer {
         * o RANGEPROTECTION
         * + EOF
         */
-       private static int findDataValidationTableInsertPos(List records) {
+       private static int findDataValidationTableInsertPos(List<RecordBase> records) {
                int i = records.size() - 1;
                if (!(records.get(i) instanceof EOFRecord)) {
                        throw new IllegalStateException("Last sheet record should be EOFRecord");
                }
                while (i > 0) {
                        i--;
-                       Object rb = records.get(i);
+                       RecordBase rb = records.get(i);
                        if (isDVTPriorRecord(rb)) {
                                Record nextRec = (Record) records.get(i + 1);
                                if (!isDVTSubsequentRecord(nextRec.getSid())) {
@@ -245,7 +245,7 @@ final class RecordOrderer {
        }
 
 
-       private static boolean isDVTPriorRecord(Object rb) {
+       private static boolean isDVTPriorRecord(RecordBase rb) {
                if (rb instanceof MergedCellsTable || rb instanceof ConditionalFormattingTable) {
                        return true;
                }
@@ -280,7 +280,7 @@ final class RecordOrderer {
        /**
         * DIMENSIONS record is always present
         */
-       private static int getDimensionsIndex(List records) {
+       private static int getDimensionsIndex(List<RecordBase> records) {
                int nRecs = records.size();
                for(int i=0; i<nRecs; i++) {
                        if(records.get(i) instanceof DimensionsRecord) {
@@ -291,12 +291,12 @@ final class RecordOrderer {
                throw new RuntimeException("DimensionsRecord not found");
        }
 
-       private static int getGutsRecordInsertPos(List records) {
+       private static int getGutsRecordInsertPos(List<RecordBase> records) {
                int dimensionsIndex = getDimensionsIndex(records);
                int i = dimensionsIndex-1;
                while (i > 0) {
                        i--;
-                       Object rb = records.get(i);
+                       RecordBase rb = records.get(i);
                        if (isGutsPriorRecord(rb)) {
                                return i+1;
                        }
@@ -304,7 +304,7 @@ final class RecordOrderer {
                throw new RuntimeException("Did not find insert point for GUTS");
        }
 
-       private static boolean isGutsPriorRecord(Object rb) {
+       private static boolean isGutsPriorRecord(RecordBase rb) {
                if (rb instanceof Record) {
                        Record record = (Record) rb;
                        switch (record.getSid()) {
@@ -337,6 +337,8 @@ final class RecordOrderer {
         */
        public static boolean isEndOfRowBlock(int sid) {
                switch(sid) {
+                       case UnknownRecord.SXVIEW_00B0:
+                               // should have been prefixed with DrawingRecord (0x00EC), but bug 46280 seems to allow this
                        case DrawingRecord.sid:
                        case DrawingSelectionRecord.sid:
                        case ObjRecord.sid:
index 020d489417af4d4cedbff3c4cade139693738a34..038304916a913f8ffd0ad53143690534f67771ca 100644 (file)
@@ -45,11 +45,11 @@ public final class RowBlocksReader {
         * mergedCellsTable
         */
        public RowBlocksReader(RecordStream rs) {
-               List plainRecords = new ArrayList();
-               List shFrmRecords = new ArrayList();
-               List arrayRecords = new ArrayList();
-               List tableRecords = new ArrayList();
-               List mergeCellRecords = new ArrayList();
+               List<Record> plainRecords = new ArrayList<Record>();
+               List<Record> shFrmRecords = new ArrayList<Record>();
+               List<Record> arrayRecords = new ArrayList<Record>();
+               List<Record> tableRecords = new ArrayList<Record>();
+               List<Record> mergeCellRecords = new ArrayList<Record>();
 
                while(!RecordOrderer.isEndOfRowBlock(rs.peekNextSid())) {
                        // End of row/cell records for the current sheet
@@ -61,7 +61,7 @@ public final class RowBlocksReader {
                        
                        }
                        Record rec = rs.getNext();
-                       List dest;
+                       List<Record> dest;
                        switch (rec.getSid()) {
                                case MergeCellsRecord.sid:    dest = mergeCellRecords; break;
                                case SharedFormulaRecord.sid: dest = shFrmRecords;     break;
index 997c35642b8749690bef31c0571026cbfd843089..bd16fb60498e65569e1e93b66788e4106a09042f 100644 (file)
@@ -39,6 +39,7 @@ public final class UnknownRecord extends StandardRecord {
        public static final int SHEETPR_0081         = 0x0081;
        public static final int STANDARDWIDTH_0099   = 0x0099;
        public static final int SCL_00A0             = 0x00A0;
+       public static final int SXVIEW_00B0          = 0x00B0;
        public static final int BITMAP_00E9          = 0x00E9;
        public static final int PHONETICPR_00EF      = 0x00EF;
        public static final int LABELRANGES_015F     = 0x015F;
@@ -128,13 +129,22 @@ public final class UnknownRecord extends StandardRecord {
                        case 0x0094: return "LHRECORD";
                        case STANDARDWIDTH_0099: return "STANDARDWIDTH";
                        case 0x009D: return "AUTOFILTERINFO";
-                       case SCL_00A0: return "SCL";
+                       case SCL_00A0:     return "SCL";
                        case 0x00AE: return "SCENMAN";
+                       case SXVIEW_00B0: return "SXVIEW"; // (pivot table) View Definition
+                       case 0x00B1: return "SXVD";        // (pivot table) View Fields
+                       case 0x00B2: return "SXVI";        // (pivot table) View Item
+                       case 0x00B4: return "SXIVD";       // (pivot table) Row/Column Field IDs
+                       case 0x00B5: return "SXLI";        // (pivot table) Line Item Array
+                       case 0x00C5: return "SXDI";        // (pivot table) Data Item
+                       
                        case 0x00D3: return "OBPROJ";
                        case 0x00DC: return "PARAMQRY";
                        case 0x00DE: return "OLESIZE";
                        case BITMAP_00E9: return "BITMAP";
                        case PHONETICPR_00EF: return "PHONETICPR";
+                       case 0x00F1: return "SXEX";        // PivotTable View Extended Information
+                       case 0x0100: return "SXVDEX";      // Extended PivotTable View Fields
 
                        case LABELRANGES_015F: return "LABELRANGES";
                        case 0x01BA: return "CODENAME";
@@ -145,14 +155,16 @@ public final class UnknownRecord extends StandardRecord {
 
                        case 0x01C0: return "EXCEL9FILE";
 
-                       case 0x0802: return "QSISXTAG";
+                       case 0x0802: return "QSISXTAG";   // Pivot Table and Query Table Extensions
                        case 0x0803: return "DBQUERYEXT";
                        case 0x0805: return "TXTQUERY";
+                       case 0x0810: return "SXVIEWEX9";  // Pivot Table Extensions
 
                        case 0x0812: return "CONTINUEFRT";
                        case QUICKTIP_0800: return "QUICKTIP";
                        case SHEETEXT_0862: return "SHEETEXT";
                        case 0x0863: return "BOOKEXT";
+                       case 0x0864: return "SXADDL";    // Pivot Table Additional Info
                        case SHEETPROTECTION_0867: return "SHEETPROTECTION";
                        case RANGEPROTECTION_0868: return "RANGEPROTECTION";
                        case 0x086B: return "DATALABEXTCONTENTS";
index 075e3ae5d23dc123a5bab0607e91a0733c6586b3..5d249640ef1f5449ee7119ea416915176698a43e 100644 (file)
@@ -26,6 +26,7 @@ import java.util.TreeMap;
 import org.apache.poi.hssf.model.RecordStream;
 import org.apache.poi.hssf.record.ArrayRecord;
 import org.apache.poi.hssf.record.CellValueRecordInterface;
+import org.apache.poi.hssf.record.ContinueRecord;
 import org.apache.poi.hssf.record.DBCellRecord;
 import org.apache.poi.hssf.record.FormulaRecord;
 import org.apache.poi.hssf.record.IndexRecord;
@@ -43,473 +44,447 @@ import org.apache.poi.hssf.record.formula.FormulaShifter;
  * @author Jason Height (jheight at chariot dot net dot au)
  */
 public final class RowRecordsAggregate extends RecordAggregate {
-    private int _firstrow = -1;
-    private int _lastrow  = -1;
-    private final Map _rowRecords;
-    private final ValueRecordsAggregate _valuesAgg;
-    private final List _unknownRecords;
-    private final SharedValueManager _sharedValueManager;
-
-    /** Creates a new instance of ValueRecordsAggregate */
-    public RowRecordsAggregate() {
-        this(SharedValueManager.EMPTY);
-    }
-    private RowRecordsAggregate(SharedValueManager svm) {
-        _rowRecords = new TreeMap();
-        _valuesAgg = new ValueRecordsAggregate();
-        _unknownRecords = new ArrayList();
-        _sharedValueManager = svm;
-    }
-
-    /**
-     * @param rs record stream with all {@link SharedFormulaRecord}
-     * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed
-     */
-    public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) {
-        this(svm);
-        while(rs.hasNext()) {
-            Record rec = rs.getNext();
-            switch (rec.getSid()) {
-                case RowRecord.sid:
-                    insertRow((RowRecord) rec);
-                    continue;
-                case DBCellRecord.sid:
-                    // end of 'Row Block'.  Should only occur after cell records
-                    // ignore DBCELL records because POI generates them upon re-serialization
-                    continue;
-            }
-            if (rec instanceof UnknownRecord) {
-                addUnknownRecord((UnknownRecord)rec);
-                // might need to keep track of where exactly these belong
-                continue;
-            }
-            if (!(rec instanceof CellValueRecordInterface)) {
-                throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
-            }
-            _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm);
-        }
-    }
-    /**
-     * Handles UnknownRecords which appear within the row/cell records
-     */
-    private void addUnknownRecord(UnknownRecord rec) {
-        // ony a few distinct record IDs are encountered by the existing POI test cases:
-        // 0x1065 // many
-        // 0x01C2 // several
-        // 0x0034 // few
-        // No documentation could be found for these
-
-        // keep the unknown records for re-serialization
-        _unknownRecords.add(rec);
-    }
-    public void insertRow(RowRecord row) {
-        // Integer integer = new Integer(row.getRowNumber());
-        _rowRecords.put(new Integer(row.getRowNumber()), row);
-        if ((row.getRowNumber() < _firstrow) || (_firstrow == -1))
-        {
-            _firstrow = row.getRowNumber();
-        }
-        if ((row.getRowNumber() > _lastrow) || (_lastrow == -1))
-        {
-            _lastrow = row.getRowNumber();
-        }
-    }
-
-    public void removeRow(RowRecord row) {
-        int rowIndex = row.getRowNumber();
-        _valuesAgg.removeAllCellsValuesForRow(rowIndex);
-        Integer key = new Integer(rowIndex);
-        RowRecord rr = (RowRecord) _rowRecords.remove(key);
-        if (rr == null) {
-            throw new RuntimeException("Invalid row index (" + key.intValue() + ")");
-        }
-        if (row != rr) {
-            _rowRecords.put(key, rr);
-            throw new RuntimeException("Attempt to remove row that does not belong to this sheet");
-        }
-    }
-
-    public RowRecord getRow(int rowIndex) {
-        if (rowIndex < 0 || rowIndex > 65535) {
-            throw new IllegalArgumentException("The row number must be between 0 and 65535");
-        }
-        return (RowRecord) _rowRecords.get(new Integer(rowIndex));
-    }
-
-    public int getPhysicalNumberOfRows()
-    {
-        return _rowRecords.size();
-    }
-
-    public int getFirstRowNum()
-    {
-        return _firstrow;
-    }
-
-    public int getLastRowNum()
-    {
-        return _lastrow;
-    }
-
-    /** Returns the number of row blocks.
-     * <p/>The row blocks are goupings of rows that contain the DBCell record
-     * after them
-     */
-    public int getRowBlockCount() {
-      int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE;
-      if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0)
-          size++;
-      return size;
-    }
-
-    private int getRowBlockSize(int block) {
-      return RowRecord.ENCODED_SIZE * getRowCountForBlock(block);
-    }
-
-    /** Returns the number of physical rows within a block*/
-    public int getRowCountForBlock(int block) {
-      int startIndex = block * DBCellRecord.BLOCK_SIZE;
-      int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1;
-      if (endIndex >= _rowRecords.size())
-        endIndex = _rowRecords.size()-1;
-
-      return endIndex-startIndex+1;
-    }
-
-    /** Returns the physical row number of the first row in a block*/
-    private int getStartRowNumberForBlock(int block) {
-      //Given that we basically iterate through the rows in order,
-      // TODO - For a performance improvement, it would be better to return an instance of
-      //an iterator and use that instance throughout, rather than recreating one and
-      //having to move it to the right position.
-      int startIndex = block * DBCellRecord.BLOCK_SIZE;
-      Iterator rowIter = _rowRecords.values().iterator();
-      RowRecord row = null;
-      //Position the iterator at the start of the block
-      for (int i=0; i<=startIndex;i++) {
-        row = (RowRecord)rowIter.next();
-      }
-      if (row == null) {
-          throw new RuntimeException("Did not find start row for block " + block);
-      }
-
-      return row.getRowNumber();
-    }
-
-    /** Returns the physical row number of the end row in a block*/
-    private int getEndRowNumberForBlock(int block) {
-      int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1;
-      if (endIndex >= _rowRecords.size())
-        endIndex = _rowRecords.size()-1;
-
-      Iterator rowIter = _rowRecords.values().iterator();
-      RowRecord row = null;
-      for (int i=0; i<=endIndex;i++) {
-        row = (RowRecord)rowIter.next();
-      }
-      if (row == null) {
-          throw new RuntimeException("Did not find start row for block " + block);
-      }
-      return row.getRowNumber();
-    }
-
-    private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) {
-        final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE;
-        final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
-
-        Iterator rowIterator = _rowRecords.values().iterator();
-
-        //Given that we basically iterate through the rows in order,
-        //For a performance improvement, it would be better to return an instance of
-        //an iterator and use that instance throughout, rather than recreating one and
-        //having to move it to the right position.
-        int i=0;
-        for (;i<startIndex;i++)
-          rowIterator.next();
-        int result = 0;
-        while(rowIterator.hasNext() && (i++ < endIndex)) {
-          Record rec = (Record)rowIterator.next();
-          result += rec.getRecordSize();
-          rv.visitRecord(rec);
-        }
-        return result;
-    }
-
-    public void visitContainedRecords(RecordVisitor rv) {
-
-        PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0);
-        //DBCells are serialized before row records.
-        final int blockCount = getRowBlockCount();
-        for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
-            // Serialize a block of rows.
-            // Hold onto the position of the first row in the block
-            int pos=0;
-            // Hold onto the size of this block that was serialized
-            final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv);
-            pos += rowBlockSize;
-            // Serialize a block of cells for those rows
-            final int startRowNumber = getStartRowNumberForBlock(blockIndex);
-            final int endRowNumber = getEndRowNumberForBlock(blockIndex);
-            DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder();
-            // Note: Cell references start from the second row...
-            int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE);
-            for (int row = startRowNumber; row <= endRowNumber; row++) {
-                if (_valuesAgg.rowHasCells(row)) {
-                    stv.setPosition(0);
-                    _valuesAgg.visitCellsForRow(row, stv);
-                    int rowCellSize = stv.getPosition();
-                    pos += rowCellSize;
-                    // Add the offset to the first cell for the row into the
-                    // DBCellRecord.
-                    dbcrBuilder.addCellOffset(cellRefOffset);
-                    cellRefOffset = rowCellSize;
-                }
-            }
-            // Calculate Offset from the start of a DBCellRecord to the first Row
-            rv.visitRecord(dbcrBuilder.build(pos));
-        }
-        for (int i=0; i< _unknownRecords.size(); i++) {
-            // Potentially breaking the file here since we don't know exactly where to write these records
-            rv.visitRecord((Record) _unknownRecords.get(i));
-        }
-    }
-
-    public Iterator getIterator() {
-        return _rowRecords.values().iterator();
-    }
-
-    public Iterator getAllRecordsIterator() {
-        List result = new ArrayList(_rowRecords.size() * 2);
-        result.addAll(_rowRecords.values());
-        Iterator vi = _valuesAgg.getIterator();
-        while (vi.hasNext()) {
-            result.add(vi.next());
-        }
-        return result.iterator();
-    }
-
-    public int findStartOfRowOutlineGroup(int row)
-    {
-        // Find the start of the group.
-        RowRecord rowRecord = this.getRow( row );
-        int level = rowRecord.getOutlineLevel();
-        int currentRow = row;
-        while (this.getRow( currentRow ) != null)
-        {
-            rowRecord = this.getRow( currentRow );
-            if (rowRecord.getOutlineLevel() < level)
-                return currentRow + 1;
-            currentRow--;
-        }
-
-        return currentRow + 1;
-    }
-
-    public int findEndOfRowOutlineGroup( int row )
-    {
-        int level = getRow( row ).getOutlineLevel();
-        int currentRow;
-        for (currentRow = row; currentRow < this.getLastRowNum(); currentRow++)
-        {
-            if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level)
-            {
-                break;
-            }
-        }
-
-        return currentRow-1;
-    }
-
-    /**
-     * Hide all rows at or below the current outline level
-     * @return index of the <em>next<em> row after the last row that gets hidden
-     */
-    private int writeHidden(RowRecord pRowRecord, int row) {
-        int rowIx = row;
-        RowRecord rowRecord = pRowRecord;
-        int level = rowRecord.getOutlineLevel();
-        while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) {
-            rowRecord.setZeroHeight(true);
-            rowIx++;
-            rowRecord = getRow(rowIx);
-        }
-        return rowIx;
-    }
-
-    public void collapseRow(int rowNumber) {
-
-        // Find the start of the group.
-        int startRow = findStartOfRowOutlineGroup(rowNumber);
-        RowRecord rowRecord = getRow(startRow);
-
-        // Hide all the columns until the end of the group
-        int nextRowIx = writeHidden(rowRecord, startRow);
-
-        RowRecord row = getRow(nextRowIx);
-        if (row == null) {
-            row = createRow(nextRowIx);
-            insertRow(row);
-        }
-        // Write collapse field
-        row.setColapsed(true);
-    }
-
-    /**
-     * Create a row record.
-     *
-     * @param rowNumber row number
-     * @return RowRecord created for the passed in row number
-     * @see org.apache.poi.hssf.record.RowRecord
-     */
-    public static RowRecord createRow(int rowNumber) {
-        return new RowRecord(rowNumber);
-    }
-
-    public boolean isRowGroupCollapsed( int row )
-    {
-        int collapseRow = findEndOfRowOutlineGroup( row ) + 1;
-
-        if (getRow(collapseRow) == null)
-            return false;
-        else
-            return getRow( collapseRow ).getColapsed();
-    }
-
-    public void expandRow( int rowNumber )
-    {
-        int idx = rowNumber;
-        if (idx == -1)
-            return;
-
-        // If it is already expanded do nothing.
-        if (!isRowGroupCollapsed(idx))
-            return;
-
-        // Find the start of the group.
-        int startIdx = findStartOfRowOutlineGroup( idx );
-        RowRecord row = getRow( startIdx );
-
-        // Find the end of the group.
-        int endIdx = findEndOfRowOutlineGroup( idx );
-
-        // expand:
-        // collapsed bit must be unset
-        // hidden bit gets unset _if_ surrounding groups are expanded you can determine
-        //   this by looking at the hidden bit of the enclosing group.  You will have
-        //   to look at the start and the end of the current group to determine which
-        //   is the enclosing group
-        // hidden bit only is altered for this outline level.  ie.  don't un-collapse contained groups
-        if ( !isRowGroupHiddenByParent( idx ) )
-        {
-            for ( int i = startIdx; i <= endIdx; i++ )
-            {
-                if ( row.getOutlineLevel() == getRow( i ).getOutlineLevel() )
-                    getRow( i ).setZeroHeight( false );
-                else if (!isRowGroupCollapsed(i))
-                    getRow( i ).setZeroHeight( false );
-            }
-        }
-
-        // Write collapse field
-        getRow( endIdx + 1 ).setColapsed( false );
-    }
-
-    public boolean isRowGroupHiddenByParent( int row )
-    {
-        // Look out outline details of end
-        int endLevel;
-        boolean endHidden;
-        int endOfOutlineGroupIdx = findEndOfRowOutlineGroup( row );
-        if (getRow( endOfOutlineGroupIdx + 1 ) == null)
-        {
-            endLevel = 0;
-            endHidden = false;
-        }
-        else
-        {
-            endLevel = getRow( endOfOutlineGroupIdx + 1).getOutlineLevel();
-            endHidden = getRow( endOfOutlineGroupIdx + 1).getZeroHeight();
-        }
-
-        // Look out outline details of start
-        int startLevel;
-        boolean startHidden;
-        int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row );
-        if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null)
-        {
-            startLevel = 0;
-            startHidden = false;
-        }
-        else
-        {
-            startLevel = getRow( startOfOutlineGroupIdx - 1).getOutlineLevel();
-            startHidden = getRow( startOfOutlineGroupIdx - 1 ).getZeroHeight();
-        }
-
-        if (endLevel > startLevel)
-        {
-            return endHidden;
-        }
-        else
-        {
-            return startHidden;
-        }
-    }
-
-    public CellValueRecordInterface[] getValueRecords() {
-        return _valuesAgg.getValueRecords();
-    }
-
-    public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) {
-        IndexRecord result = new IndexRecord();
-        result.setFirstRow(_firstrow);
-        result.setLastRowAdd1(_lastrow + 1);
-        // Calculate the size of the records from the end of the BOF
-        // and up to the RowRecordsAggregate...
-
-        // Add the references to the DBCells in the IndexRecord (one for each block)
-        // Note: The offsets are relative to the Workbook BOF. Assume that this is
-        // 0 for now.....
-
-        int blockCount = getRowBlockCount();
-        // Calculate the size of this IndexRecord
-        int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount);
-
-        int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords;
-
-        for (int block = 0; block < blockCount; block++) {
-            // each row-block has a DBCELL record.
-            // The offset of each DBCELL record needs to be updated in the INDEX record
-
-            // account for row records in this row-block
-            currentOffset += getRowBlockSize(block);
-            // account for cell value records after those
-            currentOffset += _valuesAgg.getRowCellBlockSize(
-                    getStartRowNumberForBlock(block), getEndRowNumberForBlock(block));
-
-            // currentOffset is now the location of the DBCELL record for this row-block
-            result.addDbcell(currentOffset);
-            // Add space required to write the DBCELL record (whose reference was just added).
-            currentOffset += (8 + (getRowCountForBlock(block) * 2));
-        }
-        return result;
-    }
-    public void insertCell(CellValueRecordInterface cvRec) {
-        _valuesAgg.insertCell(cvRec);
-    }
-    public void removeCell(CellValueRecordInterface cvRec) {
-        if (cvRec instanceof FormulaRecordAggregate) {
-            ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
-        }
-        _valuesAgg.removeCell(cvRec);
-    }
-    public FormulaRecordAggregate createFormula(int row, int col) {
-        FormulaRecord fr = new FormulaRecord();
-        fr.setRow(row);
-        fr.setColumn((short) col);
-        return new FormulaRecordAggregate(fr, null, _sharedValueManager);
-    }
-    public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) {
-        _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex);
-    }
+       private int _firstrow = -1;
+       private int _lastrow  = -1;
+       private final Map<Integer, RowRecord> _rowRecords;
+       private final ValueRecordsAggregate _valuesAgg;
+       private final List<Record> _unknownRecords;
+       private final SharedValueManager _sharedValueManager;
+
+       /** Creates a new instance of ValueRecordsAggregate */
+       public RowRecordsAggregate() {
+               this(SharedValueManager.EMPTY);
+       }
+       private RowRecordsAggregate(SharedValueManager svm) {
+               _rowRecords = new TreeMap<Integer, RowRecord>();
+               _valuesAgg = new ValueRecordsAggregate();
+               _unknownRecords = new ArrayList<Record>();
+               _sharedValueManager = svm;
+       }
+
+       /**
+        * @param rs record stream with all {@link SharedFormulaRecord}
+        * {@link ArrayRecord}, {@link TableRecord} {@link MergeCellsRecord} Records removed
+        */
+       public RowRecordsAggregate(RecordStream rs, SharedValueManager svm) {
+               this(svm);
+               while(rs.hasNext()) {
+                       Record rec = rs.getNext();
+                       switch (rec.getSid()) {
+                               case RowRecord.sid:
+                                       insertRow((RowRecord) rec);
+                                       continue;
+                               case DBCellRecord.sid:
+                                       // end of 'Row Block'.  Should only occur after cell records
+                                       // ignore DBCELL records because POI generates them upon re-serialization
+                                       continue;
+                       }
+                       if (rec instanceof UnknownRecord) {
+                               // might need to keep track of where exactly these belong
+                               addUnknownRecord(rec);
+                               while (rs.peekNextSid() == ContinueRecord.sid) {
+                                       addUnknownRecord(rs.getNext());
+                               }
+                               continue;
+                       }
+                       if (!(rec instanceof CellValueRecordInterface)) {
+                               throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
+                       }
+                       _valuesAgg.construct((CellValueRecordInterface)rec, rs, svm);
+               }
+       }
+       /**
+        * Handles UnknownRecords which appear within the row/cell records
+        */
+       private void addUnknownRecord(Record rec) {
+               // ony a few distinct record IDs are encountered by the existing POI test cases:
+               // 0x1065 // many
+               // 0x01C2 // several
+               // 0x0034 // few
+               // No documentation could be found for these
+
+               // keep the unknown records for re-serialization
+               _unknownRecords.add(rec);
+       }
+       public void insertRow(RowRecord row) {
+               // Integer integer = new Integer(row.getRowNumber());
+               _rowRecords.put(new Integer(row.getRowNumber()), row);
+               if ((row.getRowNumber() < _firstrow) || (_firstrow == -1)) {
+                       _firstrow = row.getRowNumber();
+               }
+               if ((row.getRowNumber() > _lastrow) || (_lastrow == -1)) {
+                       _lastrow = row.getRowNumber();
+               }
+       }
+
+       public void removeRow(RowRecord row) {
+               int rowIndex = row.getRowNumber();
+               _valuesAgg.removeAllCellsValuesForRow(rowIndex);
+               Integer key = new Integer(rowIndex);
+               RowRecord rr = _rowRecords.remove(key);
+               if (rr == null) {
+                       throw new RuntimeException("Invalid row index (" + key.intValue() + ")");
+               }
+               if (row != rr) {
+                       _rowRecords.put(key, rr);
+                       throw new RuntimeException("Attempt to remove row that does not belong to this sheet");
+               }
+       }
+
+       public RowRecord getRow(int rowIndex) {
+               if (rowIndex < 0 || rowIndex > 65535) {
+                       throw new IllegalArgumentException("The row number must be between 0 and 65535");
+               }
+               return _rowRecords.get(new Integer(rowIndex));
+       }
+
+       public int getPhysicalNumberOfRows()
+       {
+               return _rowRecords.size();
+       }
+
+       public int getFirstRowNum()
+       {
+               return _firstrow;
+       }
+
+       public int getLastRowNum()
+       {
+               return _lastrow;
+       }
+
+       /** Returns the number of row blocks.
+        * <p/>The row blocks are goupings of rows that contain the DBCell record
+        * after them
+        */
+       public int getRowBlockCount() {
+         int size = _rowRecords.size()/DBCellRecord.BLOCK_SIZE;
+         if ((_rowRecords.size() % DBCellRecord.BLOCK_SIZE) != 0)
+                 size++;
+         return size;
+       }
+
+       private int getRowBlockSize(int block) {
+         return RowRecord.ENCODED_SIZE * getRowCountForBlock(block);
+       }
+
+       /** Returns the number of physical rows within a block*/
+       public int getRowCountForBlock(int block) {
+         int startIndex = block * DBCellRecord.BLOCK_SIZE;
+         int endIndex = startIndex + DBCellRecord.BLOCK_SIZE - 1;
+         if (endIndex >= _rowRecords.size())
+               endIndex = _rowRecords.size()-1;
+
+         return endIndex-startIndex+1;
+       }
+
+       /** Returns the physical row number of the first row in a block*/
+       private int getStartRowNumberForBlock(int block) {
+         //Given that we basically iterate through the rows in order,
+         // TODO - For a performance improvement, it would be better to return an instance of
+         //an iterator and use that instance throughout, rather than recreating one and
+         //having to move it to the right position.
+         int startIndex = block * DBCellRecord.BLOCK_SIZE;
+         Iterator rowIter = _rowRecords.values().iterator();
+         RowRecord row = null;
+         //Position the iterator at the start of the block
+         for (int i=0; i<=startIndex;i++) {
+               row = (RowRecord)rowIter.next();
+         }
+         if (row == null) {
+                 throw new RuntimeException("Did not find start row for block " + block);
+         }
+
+         return row.getRowNumber();
+       }
+
+       /** Returns the physical row number of the end row in a block*/
+       private int getEndRowNumberForBlock(int block) {
+         int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1;
+         if (endIndex >= _rowRecords.size())
+               endIndex = _rowRecords.size()-1;
+
+         Iterator rowIter = _rowRecords.values().iterator();
+         RowRecord row = null;
+         for (int i=0; i<=endIndex;i++) {
+               row = (RowRecord)rowIter.next();
+         }
+         if (row == null) {
+                 throw new RuntimeException("Did not find start row for block " + block);
+         }
+         return row.getRowNumber();
+       }
+
+       private int visitRowRecordsForBlock(int blockIndex, RecordVisitor rv) {
+               final int startIndex = blockIndex*DBCellRecord.BLOCK_SIZE;
+               final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
+
+               Iterator rowIterator = _rowRecords.values().iterator();
+
+               //Given that we basically iterate through the rows in order,
+               //For a performance improvement, it would be better to return an instance of
+               //an iterator and use that instance throughout, rather than recreating one and
+               //having to move it to the right position.
+               int i=0;
+               for (;i<startIndex;i++)
+                 rowIterator.next();
+               int result = 0;
+               while(rowIterator.hasNext() && (i++ < endIndex)) {
+                 Record rec = (Record)rowIterator.next();
+                 result += rec.getRecordSize();
+                 rv.visitRecord(rec);
+               }
+               return result;
+       }
+
+       public void visitContainedRecords(RecordVisitor rv) {
+
+               PositionTrackingVisitor stv = new PositionTrackingVisitor(rv, 0);
+               //DBCells are serialized before row records.
+               final int blockCount = getRowBlockCount();
+               for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
+                       // Serialize a block of rows.
+                       // Hold onto the position of the first row in the block
+                       int pos=0;
+                       // Hold onto the size of this block that was serialized
+                       final int rowBlockSize = visitRowRecordsForBlock(blockIndex, rv);
+                       pos += rowBlockSize;
+                       // Serialize a block of cells for those rows
+                       final int startRowNumber = getStartRowNumberForBlock(blockIndex);
+                       final int endRowNumber = getEndRowNumberForBlock(blockIndex);
+                       DBCellRecord.Builder dbcrBuilder = new DBCellRecord.Builder();
+                       // Note: Cell references start from the second row...
+                       int cellRefOffset = (rowBlockSize - RowRecord.ENCODED_SIZE);
+                       for (int row = startRowNumber; row <= endRowNumber; row++) {
+                               if (_valuesAgg.rowHasCells(row)) {
+                                       stv.setPosition(0);
+                                       _valuesAgg.visitCellsForRow(row, stv);
+                                       int rowCellSize = stv.getPosition();
+                                       pos += rowCellSize;
+                                       // Add the offset to the first cell for the row into the
+                                       // DBCellRecord.
+                                       dbcrBuilder.addCellOffset(cellRefOffset);
+                                       cellRefOffset = rowCellSize;
+                               }
+                       }
+                       // Calculate Offset from the start of a DBCellRecord to the first Row
+                       rv.visitRecord(dbcrBuilder.build(pos));
+               }
+               for (int i=0; i< _unknownRecords.size(); i++) {
+                       // Potentially breaking the file here since we don't know exactly where to write these records
+                       rv.visitRecord(_unknownRecords.get(i));
+               }
+       }
+
+       public Iterator getIterator() {
+               return _rowRecords.values().iterator();
+       }
+
+       public int findStartOfRowOutlineGroup(int row) {
+               // Find the start of the group.
+               RowRecord rowRecord = this.getRow( row );
+               int level = rowRecord.getOutlineLevel();
+               int currentRow = row;
+               while (this.getRow( currentRow ) != null) {
+                       rowRecord = this.getRow( currentRow );
+                       if (rowRecord.getOutlineLevel() < level) {
+                               return currentRow + 1;
+                       }
+                       currentRow--;
+               }
+
+               return currentRow + 1;
+       }
+
+       public int findEndOfRowOutlineGroup(int row) {
+               int level = getRow( row ).getOutlineLevel();
+               int currentRow;
+               for (currentRow = row; currentRow < getLastRowNum(); currentRow++) {
+                       if (getRow(currentRow) == null || getRow(currentRow).getOutlineLevel() < level) {
+                               break;
+                       }
+               }
+
+               return currentRow-1;
+       }
+
+       /**
+        * Hide all rows at or below the current outline level
+        * @return index of the <em>next<em> row after the last row that gets hidden
+        */
+       private int writeHidden(RowRecord pRowRecord, int row) {
+               int rowIx = row;
+               RowRecord rowRecord = pRowRecord;
+               int level = rowRecord.getOutlineLevel();
+               while (rowRecord != null && getRow(rowIx).getOutlineLevel() >= level) {
+                       rowRecord.setZeroHeight(true);
+                       rowIx++;
+                       rowRecord = getRow(rowIx);
+               }
+               return rowIx;
+       }
+
+       public void collapseRow(int rowNumber) {
+
+               // Find the start of the group.
+               int startRow = findStartOfRowOutlineGroup(rowNumber);
+               RowRecord rowRecord = getRow(startRow);
+
+               // Hide all the columns until the end of the group
+               int nextRowIx = writeHidden(rowRecord, startRow);
+
+               RowRecord row = getRow(nextRowIx);
+               if (row == null) {
+                       row = createRow(nextRowIx);
+                       insertRow(row);
+               }
+               // Write collapse field
+               row.setColapsed(true);
+       }
+
+       /**
+        * Create a row record.
+        *
+        * @param rowNumber row number
+        * @return RowRecord created for the passed in row number
+        * @see org.apache.poi.hssf.record.RowRecord
+        */
+       public static RowRecord createRow(int rowNumber) {
+               return new RowRecord(rowNumber);
+       }
+
+       public boolean isRowGroupCollapsed(int row) {
+               int collapseRow = findEndOfRowOutlineGroup(row) + 1;
+
+               if (getRow(collapseRow) == null) {
+                       return false;
+               }
+               return getRow( collapseRow ).getColapsed();
+       }
+
+       public void expandRow(int rowNumber) {
+               int idx = rowNumber;
+               if (idx == -1)
+                       return;
+
+               // If it is already expanded do nothing.
+               if (!isRowGroupCollapsed(idx)) {
+                       return;
+               }
+
+               // Find the start of the group.
+               int startIdx = findStartOfRowOutlineGroup(idx);
+               RowRecord row = getRow(startIdx);
+
+               // Find the end of the group.
+               int endIdx = findEndOfRowOutlineGroup(idx);
+
+               // expand:
+               // collapsed bit must be unset
+               // hidden bit gets unset _if_ surrounding groups are expanded you can determine
+               //   this by looking at the hidden bit of the enclosing group.  You will have
+               //   to look at the start and the end of the current group to determine which
+               //   is the enclosing group
+               // hidden bit only is altered for this outline level.  ie.  don't un-collapse contained groups
+               if (!isRowGroupHiddenByParent(idx)) {
+                       for (int i = startIdx; i <= endIdx; i++) {
+                               RowRecord otherRow = getRow(i);
+                               if (row.getOutlineLevel() == otherRow.getOutlineLevel() || !isRowGroupCollapsed(i)) {
+                                       otherRow.setZeroHeight(false);
+                               }
+                       }
+               }
+
+               // Write collapse field
+               getRow(endIdx + 1).setColapsed(false);
+       }
+
+       public boolean isRowGroupHiddenByParent(int row) {
+               // Look out outline details of end
+               int endLevel;
+               boolean endHidden;
+               int endOfOutlineGroupIdx = findEndOfRowOutlineGroup(row);
+               if (getRow(endOfOutlineGroupIdx + 1) == null) {
+                       endLevel = 0;
+                       endHidden = false;
+               } else {
+                       endLevel = getRow(endOfOutlineGroupIdx + 1).getOutlineLevel();
+                       endHidden = getRow(endOfOutlineGroupIdx + 1).getZeroHeight();
+               }
+
+               // Look out outline details of start
+               int startLevel;
+               boolean startHidden;
+               int startOfOutlineGroupIdx = findStartOfRowOutlineGroup( row );
+               if (startOfOutlineGroupIdx - 1 < 0 || getRow(startOfOutlineGroupIdx - 1) == null) {
+                       startLevel = 0;
+                       startHidden = false;
+               } else {
+                       startLevel = getRow(startOfOutlineGroupIdx - 1).getOutlineLevel();
+                       startHidden = getRow(startOfOutlineGroupIdx - 1).getZeroHeight();
+               }
+
+               if (endLevel > startLevel) {
+                       return endHidden;
+               }
+
+               return startHidden;
+       }
+
+       public CellValueRecordInterface[] getValueRecords() {
+               return _valuesAgg.getValueRecords();
+       }
+
+       public IndexRecord createIndexRecord(int indexRecordOffset, int sizeOfInitialSheetRecords) {
+               IndexRecord result = new IndexRecord();
+               result.setFirstRow(_firstrow);
+               result.setLastRowAdd1(_lastrow + 1);
+               // Calculate the size of the records from the end of the BOF
+               // and up to the RowRecordsAggregate...
+
+               // Add the references to the DBCells in the IndexRecord (one for each block)
+               // Note: The offsets are relative to the Workbook BOF. Assume that this is
+               // 0 for now.....
+
+               int blockCount = getRowBlockCount();
+               // Calculate the size of this IndexRecord
+               int indexRecSize = IndexRecord.getRecordSizeForBlockCount(blockCount);
+
+               int currentOffset = indexRecordOffset + indexRecSize + sizeOfInitialSheetRecords;
+
+               for (int block = 0; block < blockCount; block++) {
+                       // each row-block has a DBCELL record.
+                       // The offset of each DBCELL record needs to be updated in the INDEX record
+
+                       // account for row records in this row-block
+                       currentOffset += getRowBlockSize(block);
+                       // account for cell value records after those
+                       currentOffset += _valuesAgg.getRowCellBlockSize(
+                                       getStartRowNumberForBlock(block), getEndRowNumberForBlock(block));
+
+                       // currentOffset is now the location of the DBCELL record for this row-block
+                       result.addDbcell(currentOffset);
+                       // Add space required to write the DBCELL record (whose reference was just added).
+                       currentOffset += (8 + (getRowCountForBlock(block) * 2));
+               }
+               return result;
+       }
+       public void insertCell(CellValueRecordInterface cvRec) {
+               _valuesAgg.insertCell(cvRec);
+       }
+       public void removeCell(CellValueRecordInterface cvRec) {
+               if (cvRec instanceof FormulaRecordAggregate) {
+                       ((FormulaRecordAggregate)cvRec).notifyFormulaChanging();
+               }
+               _valuesAgg.removeCell(cvRec);
+       }
+       public FormulaRecordAggregate createFormula(int row, int col) {
+               FormulaRecord fr = new FormulaRecord();
+               fr.setRow(row);
+               fr.setColumn((short) col);
+               return new FormulaRecordAggregate(fr, null, _sharedValueManager);
+       }
+       public void updateFormulasAfterRowShift(FormulaShifter formulaShifter, int currentExternSheetIndex) {
+               _valuesAgg.updateFormulasAfterRowShift(formulaShifter, currentExternSheetIndex);
+       }
 }
index f157d3a0f2a0dc92d9e27448457ea787093ff77e..35565a7a6583a3a9e897c4e190ff50159a4d2f2b 100755 (executable)
@@ -35,6 +35,7 @@ public final class AllModelTests {
                result.addTestSuite(TestFormulaParserEval.class);
                result.addTestSuite(TestFormulaParserIf.class);
                result.addTestSuite(TestOperandClassTransformer.class);
+               result.addTestSuite(TestRowBlocksReader.class);
                result.addTestSuite(TestRVA.class);
                result.addTestSuite(TestSheet.class);
                result.addTestSuite(TestSheetAdditional.class);
diff --git a/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java b/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java
new file mode 100644 (file)
index 0000000..652bea1
--- /dev/null
@@ -0,0 +1,60 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.hssf.model;\r
+\r
+import java.util.Arrays;\r
+\r
+import junit.framework.AssertionFailedError;\r
+import junit.framework.TestCase;\r
+\r
+import org.apache.poi.hssf.record.NumberRecord;\r
+import org.apache.poi.hssf.record.Record;\r
+import org.apache.poi.hssf.record.RowRecord;\r
+import org.apache.poi.hssf.record.UnknownRecord;\r
+import org.apache.poi.hssf.record.WindowTwoRecord;\r
+\r
+/**\r
+ * Tests for {@link RowBlocksReader}\r
+ * \r
+ * @author Josh Micich\r
+ */\r
+public final class TestRowBlocksReader extends TestCase {\r
+       public void testAbnormalPivotTableRecords_bug46280() {\r
+               int SXVIEW_SID = 0x00B0;\r
+               Record[] inRecs = {\r
+                       new RowRecord(0),\r
+                       new NumberRecord(),\r
+                       // normally MSODRAWING(0x00EC) would come here before SXVIEW\r
+                       new UnknownRecord(SXVIEW_SID, "dummydata (SXVIEW: View Definition)".getBytes()),\r
+                       new WindowTwoRecord(),\r
+               };\r
+               RecordStream rs = new RecordStream(Arrays.asList(inRecs), 0);\r
+               RowBlocksReader rbr = new RowBlocksReader(rs);\r
+               if (rs.peekNextClass() == WindowTwoRecord.class) {\r
+                       // Should have stopped at the SXVIEW record \r
+                       throw new AssertionFailedError("Identified bug 46280b");\r
+               }\r
+               RecordStream rbStream = rbr.getPlainRecordStream();\r
+               assertEquals(inRecs[0], rbStream.getNext());\r
+               assertEquals(inRecs[1], rbStream.getNext());\r
+               assertFalse(rbStream.hasNext());\r
+               assertTrue(rs.hasNext());\r
+               assertEquals(inRecs[2], rs.getNext());\r
+               assertEquals(inRecs[3], rs.getNext());\r
+       }\r
+}\r
index 7921b2607cfe0b6de299859d176d927a95911691..6c0ae1d3ae2fc1c1eb6f4018bfabf41e153e9d91 100644 (file)
@@ -21,24 +21,30 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Arrays;
 
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.model.RecordStream;
 import org.apache.poi.hssf.record.ArrayRecord;
+import org.apache.poi.hssf.record.ContinueRecord;
 import org.apache.poi.hssf.record.FormulaRecord;
+import org.apache.poi.hssf.record.NumberRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RowRecord;
 import org.apache.poi.hssf.record.SharedFormulaRecord;
 import org.apache.poi.hssf.record.SharedValueRecordBase;
 import org.apache.poi.hssf.record.TableRecord;
+import org.apache.poi.hssf.record.UnknownRecord;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.hssf.usermodel.RecordInspector;
+import org.apache.poi.hssf.usermodel.RecordInspector.RecordCollector;
 import org.apache.poi.hssf.util.CellRangeAddress8Bit;
 
 /**
- * 
+ * Tests for {@link RowRecordsAggregate}
  */
 public final class TestRowRecordsAggregate extends TestCase {
 
@@ -113,4 +119,37 @@ public final class TestRowRecordsAggregate extends TestCase {
                assertEquals(range.getFirstRow(), firstFormula.getRow());
                assertEquals(range.getFirstColumn(), firstFormula.getColumn());
        }
+
+       /**
+        * This problem was noted as the overt symptom of bug 46280.  The logic for skipping {@link
+        * UnknownRecord}s in the constructor {@link RowRecordsAggregate} did not allow for the
+        * possibility of tailing {@link ContinueRecord}s.<br/>
+        * The functionality change being tested here is actually not critical to the overall fix
+        * for bug 46280, since the fix involved making sure the that offending <i>PivotTable</i>
+        * records do not get into {@link RowRecordsAggregate}.<br/>
+        * This fix in {@link RowRecordsAggregate} was implemented anyway since any {@link 
+        * UnknownRecord} has the potential of being 'continued'.
+        */
+       public void testUnknownContinue_bug46280() {
+               Record[] inRecs = {
+                       new RowRecord(0),
+                       new NumberRecord(),
+                       new UnknownRecord(0x5555, "dummydata".getBytes()),
+                       new ContinueRecord("moredummydata".getBytes()),
+               };
+               RecordStream rs = new RecordStream(Arrays.asList(inRecs), 0);
+               RowRecordsAggregate rra;
+               try {
+                       rra = new RowRecordsAggregate(rs, SharedValueManager.EMPTY);
+               } catch (RuntimeException e) {
+                       if (e.getMessage().startsWith("Unexpected record type")) {
+                               throw new AssertionFailedError("Identified bug 46280a");
+                       }
+                       throw e;
+               }
+               RecordCollector rv = new RecordCollector();
+               rra.visitContainedRecords(rv);
+               Record[] outRecs = rv.getRecords();
+               assertEquals(5, outRecs.length);
+       }
 }