Fixed serialization of multiple blank records (should get aggregated into MulBlankRecord)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@741850 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-02-07 07:39:23 +00:00
parent 23cf469315
commit 771369c409
5 changed files with 287 additions and 202 deletions

View File

@ -37,6 +37,12 @@ public final class MulBlankRecord extends StandardRecord {
private short[] field_3_xfs;
private short field_4_last_col;
public MulBlankRecord(int row, int firstCol, short[] xfs) {
field_1_row = row;
field_2_first_col = (short)firstCol;
field_3_xfs = xfs;
field_4_last_col = (short) (firstCol + xfs.length - 1);
}
/**
* get the row number of the cells this represents
@ -127,9 +133,17 @@ public final class MulBlankRecord extends StandardRecord {
}
public void serialize(LittleEndianOutput out) {
throw new RecordFormatException( "Sorry, you can't serialize MulBlank in this release");
out.writeShort(field_1_row);
out.writeShort(field_2_first_col);
int nItems = field_3_xfs.length;
for (int i = 0; i < nItems; i++) {
out.writeShort(field_3_xfs[i]);
}
out.writeShort(field_4_last_col);
}
protected int getDataSize() {
throw new RecordFormatException( "Sorry, you can't serialize MulBlank in this release");
// 3 short fields + array of shorts
return 6 + field_3_xfs.length * 2;
}
}

View File

@ -46,7 +46,7 @@ import org.apache.poi.hssf.record.pivottable.*;
public final class RecordFactory {
private static final int NUM_RECORDS = 512;
private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
private static final Class<?>[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
/**
* contains the classes for all the records we want to parse.<br/>
@ -189,7 +189,7 @@ public final class RecordFactory {
/**
* cache of the recordsToMap();
*/
private static Map recordsMap = recordsToMap(recordClasses);
private static Map<Short, Constructor<? extends Record>> recordsMap = recordsToMap(recordClasses);
private static short[] _allKnownRecordSIDs;
@ -210,21 +210,18 @@ public final class RecordFactory {
if (record instanceof MulRKRecord) {
return convertRKRecords((MulRKRecord)record);
}
if (record instanceof MulBlankRecord) {
return convertMulBlankRecords((MulBlankRecord)record);
}
return new Record[] { record, };
}
private static Record createSingleRecord(RecordInputStream in) {
Constructor constructor = (Constructor) recordsMap.get(new Short(in.getSid()));
Constructor<? extends Record> constructor = recordsMap.get(new Short(in.getSid()));
if (constructor == null) {
return new UnknownRecord(in);
}
try {
return (Record) constructor.newInstance(new Object[] { in });
return constructor.newInstance(new Object[] { in });
} catch (InvocationTargetException e) {
throw new RecordFormatException("Unable to construct record instance" , e.getTargetException());
} catch (IllegalArgumentException e) {
@ -268,23 +265,6 @@ public final class RecordFactory {
return mulRecs;
}
/**
* Converts a {@link MulBlankRecord} into an equivalent array of {@link BlankRecord}s
*/
private static BlankRecord[] convertMulBlankRecords(MulBlankRecord mb) {
BlankRecord[] mulRecs = new BlankRecord[mb.getNumColumns()];
for (int k = 0; k < mb.getNumColumns(); k++) {
BlankRecord br = new BlankRecord();
br.setColumn((short) (k + mb.getFirstColumn()));
br.setRow(mb.getRow());
br.setXFIndex(mb.getXFAt(k));
mulRecs[k] = br;
}
return mulRecs;
}
/**
* @return an array of all the SIDS for all known records
*/
@ -293,8 +273,8 @@ public final class RecordFactory {
short[] results = new short[ recordsMap.size() ];
int i = 0;
for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
Short sid = (Short) iterator.next();
for (Iterator<Short> iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
Short sid = iterator.next();
results[i++] = sid.shortValue();
}
@ -338,7 +318,7 @@ public final class RecordFactory {
}
Short key = new Short(sid);
if (result.containsKey(key)) {
Class prev = result.get(key).getDeclaringClass();
Class<? extends Record> prev = result.get(key).getDeclaringClass();
throw new RuntimeException("duplicate record sid 0x" + Integer.toHexString(sid).toUpperCase()
+ " for classes (" + recClass.getName() + ") and (" + prev.getName() + ")");
}
@ -356,7 +336,7 @@ public final class RecordFactory {
*
* @exception RecordFormatException on error processing the InputStream
*/
public static List createRecords(InputStream in) throws RecordFormatException {
public static List<Record> createRecords(InputStream in) throws RecordFormatException {
List<Record> records = new ArrayList<Record>(NUM_RECORDS);
@ -384,10 +364,6 @@ public final class RecordFactory {
addAll(records, convertRKRecords((MulRKRecord)record));
continue;
}
if (record instanceof MulBlankRecord) {
addAll(records, convertMulBlankRecords((MulBlankRecord)record));
continue;
}
if (record.getSid() == DrawingGroupRecord.sid
&& lastRecord instanceof DrawingGroupRecord) {

View File

@ -32,6 +32,7 @@ import org.apache.poi.hssf.record.DimensionsRecord;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.IndexRecord;
import org.apache.poi.hssf.record.MergeCellsRecord;
import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.record.SharedFormulaRecord;
@ -88,6 +89,10 @@ public final class RowRecordsAggregate extends RecordAggregate {
}
continue;
}
if (rec instanceof MulBlankRecord) {
_valuesAgg.addMultipleBlanks((MulBlankRecord) rec);
continue;
}
if (!(rec instanceof CellValueRecordInterface)) {
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
}

View File

@ -18,12 +18,13 @@
package org.apache.poi.hssf.record.aggregates;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.poi.hssf.model.RecordStream;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StringRecord;
@ -41,14 +42,20 @@ import org.apache.poi.hssf.record.formula.Ptg;
*/
public final class ValueRecordsAggregate {
private static final int MAX_ROW_INDEX = 0XFFFF;
private int firstcell = -1;
private int lastcell = -1;
private static final int INDEX_NOT_SET = -1;
private int firstcell = INDEX_NOT_SET;
private int lastcell = INDEX_NOT_SET;
private CellValueRecordInterface[][] records;
/** Creates a new instance of ValueRecordsAggregate */
public ValueRecordsAggregate() {
records = new CellValueRecordInterface[30][]; // We start with 30 Rows.
this(INDEX_NOT_SET, INDEX_NOT_SET, new CellValueRecordInterface[30][]); // We start with 30 Rows.
}
private ValueRecordsAggregate(int firstCellIx, int lastCellIx, CellValueRecordInterface[][] pRecords) {
firstcell = firstCellIx;
lastcell = lastCellIx;
records = pRecords;
}
public void insertCell(CellValueRecordInterface cell) {
@ -82,10 +89,10 @@ public final class ValueRecordsAggregate {
}
rowCells[column] = cell;
if ((column < firstcell) || (firstcell == -1)) {
if (column < firstcell || firstcell == INDEX_NOT_SET) {
firstcell = column;
}
if ((column > lastcell) || (lastcell == -1)) {
if (column > lastcell || lastcell == INDEX_NOT_SET) {
lastcell = column;
}
}
@ -146,6 +153,17 @@ public final class ValueRecordsAggregate {
return lastcell;
}
public void addMultipleBlanks(MulBlankRecord mbr) {
for (int j = 0; j < mbr.getNumColumns(); j++) {
BlankRecord br = new BlankRecord();
br.setColumn(( short ) (j + mbr.getFirstColumn()));
br.setRow(mbr.getRow());
br.setXFIndex(mbr.getXFAt(j));
insertCell(br);
}
}
/**
* Processes a single cell value record
* @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet
@ -155,7 +173,7 @@ public final class ValueRecordsAggregate {
FormulaRecord formulaRec = (FormulaRecord)rec;
// read optional cached text value
StringRecord cachedText;
Class nextClass = rs.peekNextClass();
Class<? extends Record> nextClass = rs.peekNextClass();
if (nextClass == StringRecord.class) {
cachedText = (StringRecord) rs.getNext();
} else {
@ -171,19 +189,11 @@ public final class ValueRecordsAggregate {
* that are attached to the rows in the range specified.
*/
public int getRowCellBlockSize(int startRow, int endRow) {
MyIterator itr = new MyIterator(records, startRow, endRow);
int size = 0;
while (itr.hasNext()) {
CellValueRecordInterface cell = (CellValueRecordInterface) itr.next();
int row = cell.getRow();
if (row > endRow) {
break;
int result = 0;
for(int rowIx=startRow; rowIx<=endRow && rowIx<records.length; rowIx++) {
result += getRowSerializedSize(records[rowIx]);
}
if ((row >= startRow) && (row <= endRow)) {
size += ((RecordBase) cell).getRecordSize();
}
}
return size;
return result;
}
/** Returns true if the row has cells attached to it */
@ -199,40 +209,77 @@ public final class ValueRecordsAggregate {
return false;
}
/** Serializes the cells that are allocated to a certain row range*/
public int serializeCellRow(final int row, int offset, byte [] data)
{
MyIterator itr = new MyIterator(records, row, row);
int pos = offset;
while (itr.hasNext())
{
CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
if (cell.getRow() != row)
break;
pos += (( RecordBase ) cell).serialize(pos, data);
private static int getRowSerializedSize(CellValueRecordInterface[] rowCells) {
if(rowCells == null) {
return 0;
}
return pos - offset;
int result = 0;
for (int i = 0; i < rowCells.length; i++) {
RecordBase cvr = (RecordBase) rowCells[i];
if(cvr == null) {
continue;
}
int nBlank = countBlanks(rowCells, i);
if (nBlank > 1) {
result += (10 + 2*nBlank);
i+=nBlank-1;
} else {
result += cvr.getRecordSize();
}
}
return result;
}
public void visitCellsForRow(int rowIndex, RecordVisitor rv) {
CellValueRecordInterface[] cellRecs = records[rowIndex];
if (cellRecs != null) {
for (int i = 0; i < cellRecs.length; i++) {
CellValueRecordInterface cvr = cellRecs[i];
if (cvr == null) {
CellValueRecordInterface[] rowCells = records[rowIndex];
if(rowCells == null) {
throw new IllegalArgumentException("Row [" + rowIndex + "] is empty");
}
for (int i = 0; i < rowCells.length; i++) {
RecordBase cvr = (RecordBase) rowCells[i];
if(cvr == null) {
continue;
}
if (cvr instanceof RecordAggregate) {
int nBlank = countBlanks(rowCells, i);
if (nBlank > 1) {
rv.visitRecord(createMBR(rowCells, i, nBlank));
i+=nBlank-1;
} else if (cvr instanceof RecordAggregate) {
RecordAggregate agg = (RecordAggregate) cvr;
agg.visitContainedRecords(rv);
} else {
Record rec = (Record) cvr;
rv.visitRecord(rec);
rv.visitRecord((Record) cvr);
}
}
}
/**
* @return the number of <em>consecutive</em> {@link BlankRecord}s in the specified row
* starting from startIx.
*/
private static int countBlanks(CellValueRecordInterface[] rowCellValues, int startIx) {
int i = startIx;
while(i < rowCellValues.length) {
CellValueRecordInterface cvr = rowCellValues[i];
if (!(cvr instanceof BlankRecord)) {
break;
}
i++;
}
return i - startIx;
}
private MulBlankRecord createMBR(CellValueRecordInterface[] cellValues, int startIx, int nBlank) {
short[] xfs = new short[nBlank];
for (int i = 0; i < xfs.length; i++) {
xfs[i] = ((BlankRecord)cellValues[startIx + i]).getXFIndex();
}
int rowIx = cellValues[startIx].getRow();
return new MulBlankRecord(rowIx, startIx, xfs);
}
public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) {
@ -254,16 +301,20 @@ public final class ValueRecordsAggregate {
}
}
/**
* Gets all the cell records contained in this aggregate.
* Note {@link BlankRecord}s appear separate (not in {@link MulBlankRecord}s).
*/
public CellValueRecordInterface[] getValueRecords() {
List<CellValueRecordInterface> temp = new ArrayList<CellValueRecordInterface>();
for (int i = 0; i < records.length; i++) {
CellValueRecordInterface[] rowCells = records[i];
for (int rowIx = 0; rowIx < records.length; rowIx++) {
CellValueRecordInterface[] rowCells = records[rowIx];
if (rowCells == null) {
continue;
}
for (int j = 0; j < rowCells.length; j++) {
CellValueRecordInterface cell = rowCells[j];
for (int colIx = 0; colIx < rowCells.length; colIx++) {
CellValueRecordInterface cell = rowCells[colIx];
if (cell != null) {
temp.add(cell);
}
@ -274,57 +325,8 @@ public final class ValueRecordsAggregate {
temp.toArray(result);
return result;
}
public Iterator getIterator() {
return new MyIterator(records);
}
private static final class MyIterator implements Iterator {
private final CellValueRecordInterface[][] records;
private short nextColumn = -1;
private int nextRow, lastRow;
public MyIterator(CellValueRecordInterface[][] pRecords) {
this(pRecords, 0, pRecords.length - 1);
}
public MyIterator(CellValueRecordInterface[][] pRecords, int firstRow, int lastRow) {
records = pRecords;
this.nextRow = firstRow;
this.lastRow = lastRow;
findNext();
}
public boolean hasNext() {
return nextRow <= lastRow;
}
public Object next() {
Object o = records[nextRow][nextColumn];
findNext();
return o;
}
public void remove() {
throw new UnsupportedOperationException("gibt's noch nicht");
}
private void findNext() {
nextColumn++;
for (; nextRow <= lastRow; nextRow++) {
// previously this threw array out of bounds...
CellValueRecordInterface[] rowCells = (nextRow < records.length) ? records[nextRow]
: null;
if (rowCells == null) { // This row is empty
nextColumn = 0;
continue;
}
for (; nextColumn < rowCells.length; nextColumn++) {
if (rowCells[nextColumn] != null)
return;
}
nextColumn = 0;
}
}
public Object clone() {
throw new RuntimeException("clone() should not be called. ValueRecordsAggregate should be copied via Sheet.cloneSheet()");
}
}

View File

@ -20,7 +20,7 @@ package org.apache.poi.hssf.record.aggregates;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
@ -33,13 +33,15 @@ import org.apache.poi.hssf.model.RowBlocksReader;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.util.HexRead;
/**
* Tests for {@link ValueRecordsAggregate}
@ -59,16 +61,16 @@ public final class TestValueRecordsAggregate extends TestCase {
records.add(new WindowTwoRecord());
constructValueRecord(records);
Iterator iterator = valueRecord.getIterator();
RecordBase record = (RecordBase) iterator.next();
CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
//Ensure that the SharedFormulaRecord has been converted
assertEquals(1, cvrs.length);
CellValueRecordInterface record = cvrs[0];
assertNotNull( "Row contains a value", record );
assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) );
//Ensure that the SharedFormulaRecord has been converted
assertFalse( "SharedFormulaRecord is null", iterator.hasNext() );
}
private void constructValueRecord(List records) {
private void constructValueRecord(List<Record> records) {
RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0));
SharedValueManager sfrh = rbr.getSharedFormulaManager();
RecordStream rs = rbr.getPlainRecordStream();
@ -78,7 +80,7 @@ public final class TestValueRecordsAggregate extends TestCase {
}
}
private static List testData() {
private static List<Record> testData() {
List<Record> records = new ArrayList<Record>();
FormulaRecord formulaRecord = new FormulaRecord();
BlankRecord blankRecord = new BlankRecord();
@ -93,13 +95,13 @@ public final class TestValueRecordsAggregate extends TestCase {
}
public void testInsertCell() {
Iterator iterator = valueRecord.getIterator();
assertFalse( iterator.hasNext() );
CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
assertEquals(0, cvrs.length);
BlankRecord blankRecord = newBlankRecord();
valueRecord.insertCell( blankRecord );
iterator = valueRecord.getIterator();
assertTrue( iterator.hasNext() );
cvrs = valueRecord.getValueRecords();
assertEquals(1, cvrs.length);
}
public void testRemoveCell() {
@ -107,8 +109,8 @@ public final class TestValueRecordsAggregate extends TestCase {
valueRecord.insertCell( blankRecord1 );
BlankRecord blankRecord2 = newBlankRecord();
valueRecord.removeCell( blankRecord2 );
Iterator iterator = valueRecord.getIterator();
assertFalse( iterator.hasNext() );
CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
assertEquals(0, cvrs.length);
// removing an already empty cell just falls through
valueRecord.removeCell( blankRecord2 );
@ -148,36 +150,46 @@ public final class TestValueRecordsAggregate extends TestCase {
}
public void testSerialize() {
byte[] actualArray = new byte[36];
byte[] expectedArray = new byte[]
{
(byte)0x06, (byte)0x00, (byte)0x16, (byte)0x00,
(byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x02,
(byte)0x06, (byte)0x00, (byte)0x02, (byte)0x00,
(byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00,
};
List records = testData();
constructValueRecord(records);
int bytesWritten = valueRecord.serializeCellRow(1, 0, actualArray );
bytesWritten += valueRecord.serializeCellRow(2, bytesWritten, actualArray );
assertEquals( 36, bytesWritten );
for (int i = 0; i < 36; i++)
assertEquals( expectedArray[i], actualArray[i] );
private static final class SerializerVisitor implements RecordVisitor {
private final byte[] _buf;
private int _writeIndex;
public SerializerVisitor(byte[] buf) {
_buf = buf;
_writeIndex = 0;
}
public void visitRecord(Record r) {
r.serialize(_writeIndex, _buf);
_writeIndex += r.getRecordSize();
}
public int getWriteIndex() {
return _writeIndex;
}
}
private static BlankRecord newBlankRecord()
{
public void testSerialize() {
byte[] expectedArray = HexRead.readFromString(""
+ "06 00 16 00 " // Formula
+ "01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
+ "01 02 06 00 " // Blank
+ "02 00 02 00 00 00");
byte[] actualArray = new byte[expectedArray.length];
List<Record> records = testData();
constructValueRecord(records);
SerializerVisitor sv = new SerializerVisitor(actualArray);
valueRecord.visitCellsForRow(1, sv);
valueRecord.visitCellsForRow(2, sv);
assertEquals(actualArray.length, sv.getWriteIndex());
assertTrue(Arrays.equals(expectedArray, actualArray));
}
private static BlankRecord newBlankRecord() {
return newBlankRecord( 2, 2 );
}
private static BlankRecord newBlankRecord( int col, int row)
{
private static BlankRecord newBlankRecord(int col, int row) {
BlankRecord blankRecord = new BlankRecord();
blankRecord.setRow( row );
blankRecord.setColumn( (short) col );
@ -315,4 +327,80 @@ public final class TestValueRecordsAggregate extends TestCase {
}
}
}
/**
* Tests various manipulations of blank cells, to make sure that {@link MulBlankRecord}s
* are use appropriately
*/
public void testMultipleBlanks() {
BlankRecord brA2 = newBlankRecord(0, 1);
BlankRecord brB2 = newBlankRecord(1, 1);
BlankRecord brC2 = newBlankRecord(2, 1);
BlankRecord brD2 = newBlankRecord(3, 1);
BlankRecord brE2 = newBlankRecord(4, 1);
BlankRecord brB3 = newBlankRecord(1, 2);
BlankRecord brC3 = newBlankRecord(2, 2);
valueRecord.insertCell(brA2);
valueRecord.insertCell(brB2);
valueRecord.insertCell(brD2);
confirmMulBlank(3, 1, 1);
valueRecord.insertCell(brC3);
confirmMulBlank(4, 1, 2);
valueRecord.insertCell(brB3);
valueRecord.insertCell(brE2);
confirmMulBlank(6, 3, 0);
valueRecord.insertCell(brC2);
confirmMulBlank(7, 2, 0);
valueRecord.removeCell(brA2);
confirmMulBlank(6, 2, 0);
valueRecord.removeCell(brC2);
confirmMulBlank(5, 2, 1);
valueRecord.removeCell(brC3);
confirmMulBlank(4, 1, 2);
}
private void confirmMulBlank(int expectedTotalBlankCells,
int expectedNumberOfMulBlankRecords, int expectedNumberOfSingleBlankRecords) {
// assumed row ranges set-up by caller:
final int firstRow = 1;
final int lastRow = 2;
final class BlankStats {
public int countBlankCells;
public int countMulBlankRecords;
public int countSingleBlankRecords;
}
final BlankStats bs = new BlankStats();
RecordVisitor rv = new RecordVisitor() {
public void visitRecord(Record r) {
if (r instanceof MulBlankRecord) {
MulBlankRecord mbr = (MulBlankRecord) r;
bs.countMulBlankRecords++;
bs.countBlankCells += mbr.getNumColumns();
} else if (r instanceof BlankRecord) {
bs.countSingleBlankRecords++;
bs.countBlankCells++;
}
}
};
for (int rowIx = firstRow; rowIx <=lastRow; rowIx++) {
if (valueRecord.rowHasCells(rowIx)) {
valueRecord.visitCellsForRow(rowIx, rv);
}
}
assertEquals(expectedTotalBlankCells, bs.countBlankCells);
assertEquals(expectedNumberOfMulBlankRecords, bs.countMulBlankRecords);
assertEquals(expectedNumberOfSingleBlankRecords, bs.countSingleBlankRecords);
}
}