You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by jo...@apache.org on 2008/11/24 23:40:47 UTC

svn commit: r720318 - in /poi/trunk/src: documentation/content/xdocs/ java/org/apache/poi/hssf/model/ java/org/apache/poi/hssf/record/ java/org/apache/poi/hssf/record/aggregates/ testcases/org/apache/poi/hssf/model/ testcases/org/apache/poi/hssf/record...

Author: josh
Date: Mon Nov 24 14:40:46 2008
New Revision: 720318

URL: http://svn.apache.org/viewvc?rev=720318&view=rev
Log:
Fix for bug 46280 - RowRecordsAggregate should allow for ContinueRecords after UnkownRecords, and PivotTable records should not get in the RowRecordsAggregate at all

Added:
    poi/trunk/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java
Modified:
    poi/trunk/src/documentation/content/xdocs/changes.xml
    poi/trunk/src/documentation/content/xdocs/status.xml
    poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java
    poi/trunk/src/java/org/apache/poi/hssf/model/RowBlocksReader.java
    poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java
    poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java
    poi/trunk/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
    poi/trunk/src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java

Modified: poi/trunk/src/documentation/content/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/changes.xml?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/changes.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/changes.xml Mon Nov 24 14:40:46 2008
@@ -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>

Modified: poi/trunk/src/documentation/content/xdocs/status.xml
URL: http://svn.apache.org/viewvc/poi/trunk/src/documentation/content/xdocs/status.xml?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/documentation/content/xdocs/status.xml (original)
+++ poi/trunk/src/documentation/content/xdocs/status.xml Mon Nov 24 14:40:46 2008
@@ -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>

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/RecordOrderer.java Mon Nov 24 14:40:46 2008
@@ -85,12 +85,12 @@
 	 * 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 @@
 		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 @@
 	/**
 	 * 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 @@
 		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 @@
 	 * 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 @@
 	}
 
 
-	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 @@
 	/**
 	 * 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 @@
 		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 @@
 		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 @@
 	 */
 	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:

Modified: poi/trunk/src/java/org/apache/poi/hssf/model/RowBlocksReader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/model/RowBlocksReader.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/model/RowBlocksReader.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/model/RowBlocksReader.java Mon Nov 24 14:40:46 2008
@@ -45,11 +45,11 @@
 	 * 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 @@
 			
 			}
 			Record rec = rs.getNext();
-			List dest;
+			List<Record> dest;
 			switch (rec.getSid()) {
 				case MergeCellsRecord.sid:    dest = mergeCellRecords; break;
 				case SharedFormulaRecord.sid: dest = shFrmRecords;     break;

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/UnknownRecord.java Mon Nov 24 14:40:46 2008
@@ -39,6 +39,7 @@
 	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 @@
 			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 @@
 
 			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";

Modified: poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java (original)
+++ poi/trunk/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java Mon Nov 24 14:40:46 2008
@@ -26,6 +26,7 @@
 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 @@
  * @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);
+	}
 }

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/model/AllModelTests.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/model/AllModelTests.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/model/AllModelTests.java Mon Nov 24 14:40:46 2008
@@ -35,6 +35,7 @@
 		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);

Added: poi/trunk/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java?rev=720318&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/model/TestRowBlocksReader.java Mon Nov 24 14:40:46 2008
@@ -0,0 +1,60 @@
+/* ====================================================================
+   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.hssf.model;
+
+import java.util.Arrays;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+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.UnknownRecord;
+import org.apache.poi.hssf.record.WindowTwoRecord;
+
+/**
+ * Tests for {@link RowBlocksReader}
+ * 
+ * @author Josh Micich
+ */
+public final class TestRowBlocksReader extends TestCase {
+	public void testAbnormalPivotTableRecords_bug46280() {
+		int SXVIEW_SID = 0x00B0;
+		Record[] inRecs = {
+			new RowRecord(0),
+			new NumberRecord(),
+			// normally MSODRAWING(0x00EC) would come here before SXVIEW
+			new UnknownRecord(SXVIEW_SID, "dummydata (SXVIEW: View Definition)".getBytes()),
+			new WindowTwoRecord(),
+		};
+		RecordStream rs = new RecordStream(Arrays.asList(inRecs), 0);
+		RowBlocksReader rbr = new RowBlocksReader(rs);
+		if (rs.peekNextClass() == WindowTwoRecord.class) {
+			// Should have stopped at the SXVIEW record 
+			throw new AssertionFailedError("Identified bug 46280b");
+		}
+		RecordStream rbStream = rbr.getPlainRecordStream();
+		assertEquals(inRecs[0], rbStream.getNext());
+		assertEquals(inRecs[1], rbStream.getNext());
+		assertFalse(rbStream.hasNext());
+		assertTrue(rs.hasNext());
+		assertEquals(inRecs[2], rs.getNext());
+		assertEquals(inRecs[3], rs.getNext());
+	}
+}

Modified: poi/trunk/src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java?rev=720318&r1=720317&r2=720318&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java (original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/record/aggregates/TestRowRecordsAggregate.java Mon Nov 24 14:40:46 2008
@@ -21,24 +21,30 @@
 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 @@
 		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);
+	}
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org