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_3_xfs;
private short field_4_last_col; 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 * get the row number of the cells this represents
@ -127,9 +133,17 @@ public final class MulBlankRecord extends StandardRecord {
} }
public void serialize(LittleEndianOutput out) { 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() { 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 { public final class RecordFactory {
private static final int NUM_RECORDS = 512; 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/> * contains the classes for all the records we want to parse.<br/>
@ -189,7 +189,7 @@ public final class RecordFactory {
/** /**
* cache of the recordsToMap(); * cache of the recordsToMap();
*/ */
private static Map recordsMap = recordsToMap(recordClasses); private static Map<Short, Constructor<? extends Record>> recordsMap = recordsToMap(recordClasses);
private static short[] _allKnownRecordSIDs; private static short[] _allKnownRecordSIDs;
@ -210,21 +210,18 @@ public final class RecordFactory {
if (record instanceof MulRKRecord) { if (record instanceof MulRKRecord) {
return convertRKRecords((MulRKRecord)record); return convertRKRecords((MulRKRecord)record);
} }
if (record instanceof MulBlankRecord) {
return convertMulBlankRecords((MulBlankRecord)record);
}
return new Record[] { record, }; return new Record[] { record, };
} }
private static Record createSingleRecord(RecordInputStream in) { 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) { if (constructor == null) {
return new UnknownRecord(in); return new UnknownRecord(in);
} }
try { try {
return (Record) constructor.newInstance(new Object[] { in }); return constructor.newInstance(new Object[] { in });
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw new RecordFormatException("Unable to construct record instance" , e.getTargetException()); throw new RecordFormatException("Unable to construct record instance" , e.getTargetException());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -268,23 +265,6 @@ public final class RecordFactory {
return mulRecs; 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 * @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() ]; short[] results = new short[ recordsMap.size() ];
int i = 0; int i = 0;
for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) { for (Iterator<Short> iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) {
Short sid = (Short) iterator.next(); Short sid = iterator.next();
results[i++] = sid.shortValue(); results[i++] = sid.shortValue();
} }
@ -330,7 +310,7 @@ public final class RecordFactory {
short sid; short sid;
Constructor<? extends Record> constructor; Constructor<? extends Record> constructor;
try { try {
sid = recClass.getField("sid").getShort(null); sid = recClass.getField("sid").getShort(null);
constructor = recClass.getConstructor(CONSTRUCTOR_ARGS); constructor = recClass.getConstructor(CONSTRUCTOR_ARGS);
} catch (Exception illegalArgumentException) { } catch (Exception illegalArgumentException) {
throw new RecordFormatException( throw new RecordFormatException(
@ -338,7 +318,7 @@ public final class RecordFactory {
} }
Short key = new Short(sid); Short key = new Short(sid);
if (result.containsKey(key)) { 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() throw new RuntimeException("duplicate record sid 0x" + Integer.toHexString(sid).toUpperCase()
+ " for classes (" + recClass.getName() + ") and (" + prev.getName() + ")"); + " for classes (" + recClass.getName() + ") and (" + prev.getName() + ")");
} }
@ -356,7 +336,7 @@ public final class RecordFactory {
* *
* @exception RecordFormatException on error processing the InputStream * @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); List<Record> records = new ArrayList<Record>(NUM_RECORDS);
@ -384,10 +364,6 @@ public final class RecordFactory {
addAll(records, convertRKRecords((MulRKRecord)record)); addAll(records, convertRKRecords((MulRKRecord)record));
continue; continue;
} }
if (record instanceof MulBlankRecord) {
addAll(records, convertMulBlankRecords((MulBlankRecord)record));
continue;
}
if (record.getSid() == DrawingGroupRecord.sid if (record.getSid() == DrawingGroupRecord.sid
&& lastRecord instanceof DrawingGroupRecord) { && 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.FormulaRecord;
import org.apache.poi.hssf.record.IndexRecord; import org.apache.poi.hssf.record.IndexRecord;
import org.apache.poi.hssf.record.MergeCellsRecord; 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.Record;
import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.RowRecord;
import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.SharedFormulaRecord;
@ -88,6 +89,10 @@ public final class RowRecordsAggregate extends RecordAggregate {
} }
continue; continue;
} }
if (rec instanceof MulBlankRecord) {
_valuesAgg.addMultipleBlanks((MulBlankRecord) rec);
continue;
}
if (!(rec instanceof CellValueRecordInterface)) { if (!(rec instanceof CellValueRecordInterface)) {
throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")"); throw new RuntimeException("Unexpected record type (" + rec.getClass().getName() + ")");
} }

View File

@ -18,12 +18,13 @@
package org.apache.poi.hssf.record.aggregates; package org.apache.poi.hssf.record.aggregates;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.poi.hssf.model.RecordStream; 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.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord; 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.Record;
import org.apache.poi.hssf.record.RecordBase; import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.record.StringRecord;
@ -41,14 +42,20 @@ import org.apache.poi.hssf.record.formula.Ptg;
*/ */
public final class ValueRecordsAggregate { public final class ValueRecordsAggregate {
private static final int MAX_ROW_INDEX = 0XFFFF; private static final int MAX_ROW_INDEX = 0XFFFF;
private int firstcell = -1; private static final int INDEX_NOT_SET = -1;
private int lastcell = -1; private int firstcell = INDEX_NOT_SET;
private int lastcell = INDEX_NOT_SET;
private CellValueRecordInterface[][] records; private CellValueRecordInterface[][] records;
/** Creates a new instance of ValueRecordsAggregate */ /** Creates a new instance of ValueRecordsAggregate */
public 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) { public void insertCell(CellValueRecordInterface cell) {
@ -82,10 +89,10 @@ public final class ValueRecordsAggregate {
} }
rowCells[column] = cell; rowCells[column] = cell;
if ((column < firstcell) || (firstcell == -1)) { if (column < firstcell || firstcell == INDEX_NOT_SET) {
firstcell = column; firstcell = column;
} }
if ((column > lastcell) || (lastcell == -1)) { if (column > lastcell || lastcell == INDEX_NOT_SET) {
lastcell = column; lastcell = column;
} }
} }
@ -115,11 +122,11 @@ public final class ValueRecordsAggregate {
+ " is outside the allowable range (0.." +MAX_ROW_INDEX + ")"); + " is outside the allowable range (0.." +MAX_ROW_INDEX + ")");
} }
if (rowIndex >= records.length) { if (rowIndex >= records.length) {
// this can happen when the client code has created a row, // this can happen when the client code has created a row,
// and then removes/replaces it before adding any cells. (see bug 46312) // and then removes/replaces it before adding any cells. (see bug 46312)
return; return;
} }
records[rowIndex] = null; records[rowIndex] = null;
} }
@ -146,6 +153,17 @@ public final class ValueRecordsAggregate {
return lastcell; 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 * Processes a single cell value record
* @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet * @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; FormulaRecord formulaRec = (FormulaRecord)rec;
// read optional cached text value // read optional cached text value
StringRecord cachedText; StringRecord cachedText;
Class nextClass = rs.peekNextClass(); Class<? extends Record> nextClass = rs.peekNextClass();
if (nextClass == StringRecord.class) { if (nextClass == StringRecord.class) {
cachedText = (StringRecord) rs.getNext(); cachedText = (StringRecord) rs.getNext();
} else { } else {
@ -171,19 +189,11 @@ public final class ValueRecordsAggregate {
* that are attached to the rows in the range specified. * that are attached to the rows in the range specified.
*/ */
public int getRowCellBlockSize(int startRow, int endRow) { public int getRowCellBlockSize(int startRow, int endRow) {
MyIterator itr = new MyIterator(records, startRow, endRow); int result = 0;
int size = 0; for(int rowIx=startRow; rowIx<=endRow && rowIx<records.length; rowIx++) {
while (itr.hasNext()) { result += getRowSerializedSize(records[rowIx]);
CellValueRecordInterface cell = (CellValueRecordInterface) itr.next();
int row = cell.getRow();
if (row > endRow) {
break;
}
if ((row >= startRow) && (row <= endRow)) {
size += ((RecordBase) cell).getRecordSize();
}
} }
return size; return result;
} }
/** Returns true if the row has cells attached to it */ /** Returns true if the row has cells attached to it */
@ -191,7 +201,7 @@ public final class ValueRecordsAggregate {
if (row >= records.length) { if (row >= records.length) {
return false; return false;
} }
CellValueRecordInterface[] rowCells=records[row]; CellValueRecordInterface[] rowCells=records[row];
if(rowCells==null) return false; if(rowCells==null) return false;
for(int col=0;col<rowCells.length;col++) { for(int col=0;col<rowCells.length;col++) {
if(rowCells[col]!=null) return true; if(rowCells[col]!=null) return true;
@ -199,42 +209,79 @@ public final class ValueRecordsAggregate {
return false; return false;
} }
/** Serializes the cells that are allocated to a certain row range*/ private static int getRowSerializedSize(CellValueRecordInterface[] rowCells) {
public int serializeCellRow(final int row, int offset, byte [] data) if(rowCells == null) {
{ return 0;
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);
} }
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) { public void visitCellsForRow(int rowIndex, RecordVisitor rv) {
CellValueRecordInterface[] cellRecs = records[rowIndex]; CellValueRecordInterface[] rowCells = records[rowIndex];
if (cellRecs != null) { if(rowCells == null) {
for (int i = 0; i < cellRecs.length; i++) { throw new IllegalArgumentException("Row [" + rowIndex + "] is empty");
CellValueRecordInterface cvr = cellRecs[i]; }
if (cvr == null) {
continue;
} for (int i = 0; i < rowCells.length; i++) {
if (cvr instanceof RecordAggregate) { RecordBase cvr = (RecordBase) rowCells[i];
RecordAggregate agg = (RecordAggregate) cvr; if(cvr == null) {
agg.visitContainedRecords(rv); continue;
} else { }
Record rec = (Record) cvr; int nBlank = countBlanks(rowCells, i);
rv.visitRecord(rec); 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 {
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) { public void updateFormulasAfterRowShift(FormulaShifter shifter, int currentExternSheetIndex) {
for (int i = 0; i < records.length; i++) { for (int i = 0; i < records.length; i++) {
CellValueRecordInterface[] rowCells = records[i]; CellValueRecordInterface[] rowCells = records[i];
@ -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() { public CellValueRecordInterface[] getValueRecords() {
List<CellValueRecordInterface> temp = new ArrayList<CellValueRecordInterface>(); List<CellValueRecordInterface> temp = new ArrayList<CellValueRecordInterface>();
for (int i = 0; i < records.length; i++) { for (int rowIx = 0; rowIx < records.length; rowIx++) {
CellValueRecordInterface[] rowCells = records[i]; CellValueRecordInterface[] rowCells = records[rowIx];
if (rowCells == null) { if (rowCells == null) {
continue; continue;
} }
for (int j = 0; j < rowCells.length; j++) { for (int colIx = 0; colIx < rowCells.length; colIx++) {
CellValueRecordInterface cell = rowCells[j]; CellValueRecordInterface cell = rowCells[colIx];
if (cell != null) { if (cell != null) {
temp.add(cell); temp.add(cell);
} }
@ -274,57 +325,8 @@ public final class ValueRecordsAggregate {
temp.toArray(result); temp.toArray(result);
return 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.zip.CRC32; 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.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord; 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.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.SharedFormulaRecord; import org.apache.poi.hssf.record.SharedFormulaRecord;
import org.apache.poi.hssf.record.WindowTwoRecord; 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.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.util.HexRead;
/** /**
* Tests for {@link ValueRecordsAggregate} * Tests for {@link ValueRecordsAggregate}
@ -59,16 +61,16 @@ public final class TestValueRecordsAggregate extends TestCase {
records.add(new WindowTwoRecord()); records.add(new WindowTwoRecord());
constructValueRecord(records); constructValueRecord(records);
Iterator iterator = valueRecord.getIterator(); CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
RecordBase record = (RecordBase) iterator.next(); //Ensure that the SharedFormulaRecord has been converted
assertEquals(1, cvrs.length);
CellValueRecordInterface record = cvrs[0];
assertNotNull( "Row contains a value", record ); assertNotNull( "Row contains a value", record );
assertTrue( "First record is a FormulaRecordsAggregate", ( record instanceof FormulaRecordAggregate ) ); 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)); RowBlocksReader rbr = new RowBlocksReader(new RecordStream(records, 0));
SharedValueManager sfrh = rbr.getSharedFormulaManager(); SharedValueManager sfrh = rbr.getSharedFormulaManager();
RecordStream rs = rbr.getPlainRecordStream(); 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>(); List<Record> records = new ArrayList<Record>();
FormulaRecord formulaRecord = new FormulaRecord(); FormulaRecord formulaRecord = new FormulaRecord();
BlankRecord blankRecord = new BlankRecord(); BlankRecord blankRecord = new BlankRecord();
@ -93,13 +95,13 @@ public final class TestValueRecordsAggregate extends TestCase {
} }
public void testInsertCell() { public void testInsertCell() {
Iterator iterator = valueRecord.getIterator(); CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
assertFalse( iterator.hasNext() ); assertEquals(0, cvrs.length);
BlankRecord blankRecord = newBlankRecord(); BlankRecord blankRecord = newBlankRecord();
valueRecord.insertCell( blankRecord ); valueRecord.insertCell( blankRecord );
iterator = valueRecord.getIterator(); cvrs = valueRecord.getValueRecords();
assertTrue( iterator.hasNext() ); assertEquals(1, cvrs.length);
} }
public void testRemoveCell() { public void testRemoveCell() {
@ -107,8 +109,8 @@ public final class TestValueRecordsAggregate extends TestCase {
valueRecord.insertCell( blankRecord1 ); valueRecord.insertCell( blankRecord1 );
BlankRecord blankRecord2 = newBlankRecord(); BlankRecord blankRecord2 = newBlankRecord();
valueRecord.removeCell( blankRecord2 ); valueRecord.removeCell( blankRecord2 );
Iterator iterator = valueRecord.getIterator(); CellValueRecordInterface[] cvrs = valueRecord.getValueRecords();
assertFalse( iterator.hasNext() ); assertEquals(0, cvrs.length);
// removing an already empty cell just falls through // removing an already empty cell just falls through
valueRecord.removeCell( blankRecord2 ); valueRecord.removeCell( blankRecord2 );
@ -148,36 +150,46 @@ public final class TestValueRecordsAggregate extends TestCase {
} }
public void testSerialize() {
byte[] actualArray = new byte[36]; private static final class SerializerVisitor implements RecordVisitor {
byte[] expectedArray = new byte[] private final byte[] _buf;
{ private int _writeIndex;
(byte)0x06, (byte)0x00, (byte)0x16, (byte)0x00, public SerializerVisitor(byte[] buf) {
(byte)0x01, (byte)0x00, (byte)0x01, (byte)0x00, _buf = buf;
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, _writeIndex = 0;
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, }
(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, public void visitRecord(Record r) {
(byte)0x00, (byte)0x00, (byte)0x01, (byte)0x02, r.serialize(_writeIndex, _buf);
(byte)0x06, (byte)0x00, (byte)0x02, (byte)0x00, _writeIndex += r.getRecordSize();
(byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, }
}; public int getWriteIndex() {
List records = testData(); return _writeIndex;
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 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 ); 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 blankRecord = new BlankRecord();
blankRecord.setRow( row ); blankRecord.setRow( row );
blankRecord.setColumn( (short) col ); blankRecord.setColumn( (short) col );
@ -185,19 +197,19 @@ public final class TestValueRecordsAggregate extends TestCase {
} }
/** /**
* Sometimes the 'shared formula' flag (<tt>FormulaRecord.isSharedFormula()</tt>) is set when * Sometimes the 'shared formula' flag (<tt>FormulaRecord.isSharedFormula()</tt>) is set when
* there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do * there is no corresponding SharedFormulaRecord available. SharedFormulaRecord definitions do
* not span multiple sheets. They are are only defined within a sheet, and thus they do not * not span multiple sheets. They are are only defined within a sheet, and thus they do not
* have a sheet index field (only row and column range fields).<br/> * have a sheet index field (only row and column range fields).<br/>
* So it is important that the code which locates the SharedFormulaRecord for each * So it is important that the code which locates the SharedFormulaRecord for each
* FormulaRecord does not allow matches across sheets.</br> * FormulaRecord does not allow matches across sheets.</br>
* *
* Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt> * Prior to bugzilla 44449 (Feb 2008), POI <tt>ValueRecordsAggregate.construct(int, List)</tt>
* allowed <tt>SharedFormulaRecord</tt>s to be erroneously used across sheets. That incorrect * allowed <tt>SharedFormulaRecord</tt>s to be erroneously used across sheets. That incorrect
* behaviour is shown by this test.<p/> * behaviour is shown by this test.<p/>
* *
* <b>Notes on how to produce the test spreadsheet</b>:</p> * <b>Notes on how to produce the test spreadsheet</b>:</p>
* The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas * The setup for this test (AbnormalSharedFormulaFlag.xls) is rather fragile, insomuchas
* re-saving the file (either with Excel or POI) clears the flag.<br/> * re-saving the file (either with Excel or POI) clears the flag.<br/>
* <ol> * <ol>
* <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</li> * <li>A new spreadsheet was created in Excel (File | New | Blank Workbook).</li>
@ -207,15 +219,15 @@ public final class TestValueRecordsAggregate extends TestCase {
* <li>Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).</li> * <li>Four rows on Sheet1 "5" through "8" were deleted ('delete rows' alt-E D, not 'clear' Del).</li>
* <li>The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.</li> * <li>The spreadsheet was saved as AbnormalSharedFormulaFlag.xls.</li>
* </ol> * </ol>
* Prior to the row delete action the spreadsheet has two <tt>SharedFormulaRecord</tt>s. One * Prior to the row delete action the spreadsheet has two <tt>SharedFormulaRecord</tt>s. One
* for each sheet. To expose the bug, the shared formulas have been made to overlap.<br/> * for each sheet. To expose the bug, the shared formulas have been made to overlap.<br/>
* The row delete action (as described here) seems to to delete the * The row delete action (as described here) seems to to delete the
* <tt>SharedFormulaRecord</tt> from Sheet1 (but not clear the 'shared formula' flags.<br/> * <tt>SharedFormulaRecord</tt> from Sheet1 (but not clear the 'shared formula' flags.<br/>
* There are other variations on this theme to create the same effect. * There are other variations on this theme to create the same effect.
* *
*/ */
public void testSpuriousSharedFormulaFlag() { public void testSpuriousSharedFormulaFlag() {
long actualCRC = getFileCRC(HSSFTestDataSamples.openSampleFileStream(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE)); long actualCRC = getFileCRC(HSSFTestDataSamples.openSampleFileStream(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE));
long expectedCRC = 2277445406L; long expectedCRC = 2277445406L;
if(actualCRC != expectedCRC) { if(actualCRC != expectedCRC) {
@ -223,17 +235,17 @@ public final class TestValueRecordsAggregate extends TestCase {
throw failUnexpectedTestFileChange(); throw failUnexpectedTestFileChange();
} }
HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE); HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(ABNORMAL_SHARED_FORMULA_FLAG_TEST_FILE);
HSSFSheet s = wb.getSheetAt(0); // Sheet1 HSSFSheet s = wb.getSheetAt(0); // Sheet1
String cellFormula; String cellFormula;
cellFormula = getFormulaFromFirstCell(s, 0); // row "1" cellFormula = getFormulaFromFirstCell(s, 0); // row "1"
// the problem is not observable in the first row of the shared formula // the problem is not observable in the first row of the shared formula
if(!cellFormula.equals("\"first formula\"")) { if(!cellFormula.equals("\"first formula\"")) {
throw new RuntimeException("Something else wrong with this test case"); throw new RuntimeException("Something else wrong with this test case");
} }
// but the problem is observable in rows 2,3,4 // but the problem is observable in rows 2,3,4
cellFormula = getFormulaFromFirstCell(s, 1); // row "2" cellFormula = getFormulaFromFirstCell(s, 1); // row "2"
if(cellFormula.equals("\"second formula\"")) { if(cellFormula.equals("\"second formula\"")) {
throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used)."); throw new AssertionFailedError("found bug 44449 (Wrong SharedFormulaRecord was used).");
@ -260,8 +272,8 @@ public final class TestValueRecordsAggregate extends TestCase {
// A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord) // A breakpoint in ValueRecordsAggregate.handleMissingSharedFormulaRecord(FormulaRecord)
// should get hit during parsing of Sheet1. // should get hit during parsing of Sheet1.
// If the test spreadsheet is created as directed, this condition should occur. // If the test spreadsheet is created as directed, this condition should occur.
// It is easy to upset the test spreadsheet (for example re-saving will destroy the // It is easy to upset the test spreadsheet (for example re-saving will destroy the
// peculiar condition we are testing for). // peculiar condition we are testing for).
throw new RuntimeException(msg); throw new RuntimeException(msg);
} }
@ -283,13 +295,13 @@ public final class TestValueRecordsAggregate extends TestCase {
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
return crc.getValue(); return crc.getValue();
} }
public void testRemoveNewRow_bug46312() { public void testRemoveNewRow_bug46312() {
// To make bug occur, rowIndex needs to be >= ValueRecordsAggregate.records.length // To make bug occur, rowIndex needs to be >= ValueRecordsAggregate.records.length
int rowIndex = 30; int rowIndex = 30;
ValueRecordsAggregate vra = new ValueRecordsAggregate(); ValueRecordsAggregate vra = new ValueRecordsAggregate();
try { try {
vra.removeAllCellsValuesForRow(rowIndex); vra.removeAllCellsValuesForRow(rowIndex);
@ -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);
}
} }