Reapplied DBCell etc etc patch to head after it had been clobbered during the move from the rel_2_branch to head.

Refixes bug 9576


git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353605 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jason Height 2004-10-07 03:37:16 +00:00
parent 49e1a3d294
commit f5b0a702a7
5 changed files with 361 additions and 295 deletions

View File

@ -46,7 +46,7 @@ import java.util.List;
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Glen Stampoultzis (glens at apache.org)
* @author Shawn Laubach (slaubach at apache dot org) Gridlines, Headers, Footers, and PrintSetup
* @author Jason Height (jheight at chariot dot net dot au) Clone support
* @author Jason Height (jheight at chariot dot net dot au) Clone support. DBCell & Index Record writing support
* @author Brian Sanders (kestrel at burdell dot org) Active Cell support
*
* @see org.apache.poi.hssf.model.Workbook
@ -272,6 +272,15 @@ public class Sheet implements Model
{
retval.windowTwo = (WindowTwoRecord) rec;
}
else if ( rec.getSid() == DBCellRecord.sid )
{
rec = null;
}
else if ( rec.getSid() == IndexRecord.sid )
{
rec = null;
}
else if ( rec.getSid() == ProtectRecord.sid )
{
retval.protect = (ProtectRecord) rec;
@ -719,51 +728,6 @@ public class Sheet implements Model
return preoffset;
}
/**
* Serializes all records in the sheet into one big byte array. Use this to write
* the sheet out.
*
* @return byte[] array containing the binary representation of the records in this sheet
*
*/
public byte [] serialize()
{
if (log.check( POILogger.DEBUG ))
log.log(POILogger.DEBUG, "Sheet.serialize");
// addDBCellRecords();
byte[] retval = null;
// ArrayList bytes = new ArrayList(4096);
int arraysize = getSize();
int pos = 0;
// for (int k = 0; k < records.size(); k++)
// {
// bytes.add((( Record ) records.get(k)).serialize());
//
// }
// for (int k = 0; k < bytes.size(); k++)
// {
// arraysize += (( byte [] ) bytes.get(k)).length;
// POILogger.DEBUG((new StringBuffer("arraysize=")).append(arraysize)
// .toString());
// }
retval = new byte[ arraysize ];
for (int k = 0; k < records.size(); k++)
{
// byte[] rec = (( byte [] ) bytes.get(k));
// System.arraycopy(rec, 0, retval, pos, rec.length);
pos += (( Record ) records.get(k)).serialize(pos,
retval); // rec.length;
}
if (log.check( POILogger.DEBUG ))
log.log(POILogger.DEBUG, "Sheet.serialize returning " + retval);
return retval;
}
/**
* Serializes all records in the sheet into one big byte array. Use this to write
* the sheet out.
@ -778,47 +742,75 @@ public class Sheet implements Model
if (log.check( POILogger.DEBUG ))
log.log(POILogger.DEBUG, "Sheet.serialize using offsets");
// addDBCellRecords();
// ArrayList bytes = new ArrayList(4096);
// int arraysize = getSize(); // 0;
int pos = 0;
int pos = offset;
boolean haveSerializedIndex = false;
// for (int k = 0; k < records.size(); k++)
// {
// bytes.add((( Record ) records.get(k)).serialize());
//
// }
// for (int k = 0; k < bytes.size(); k++)
// {
// arraysize += (( byte [] ) bytes.get(k)).length;
// POILogger.DEBUG((new StringBuffer("arraysize=")).append(arraysize)
// .toString());
// }
for (int k = 0; k < records.size(); k++)
{
// byte[] rec = (( byte [] ) bytes.get(k));
// System.arraycopy(rec, 0, data, offset + pos, rec.length);
Record record = (( Record ) records.get(k));
int startPos = pos;
//Once the rows have been found in the list of records, start
//writing out the blocked row information. This includes the DBCell references
if (record instanceof RowRecordsAggregate) {
pos += ((RowRecordsAggregate)record).serialize(pos, data, cells); // rec.length;
} else if (record instanceof ValueRecordsAggregate) {
//Do nothing here. The records were serialized during the RowRecordAggregate block serialization
} else {
pos += record.serialize(pos, data ); // rec.length;
}
//If the BOF record was just serialized then add the IndexRecord
if (record.getSid() == BOFRecord.sid) {
//Can there be more than one BOF for a sheet? If not then we can
//remove this guard. So be safe it is left here.
if (!haveSerializedIndex) {
haveSerializedIndex = true;
pos += serializeIndexRecord(k, pos, data);
}
}
//// uncomment to test record sizes ////
// System.out.println( record.getClass().getName() );
// byte[] data2 = new byte[record.getRecordSize()];
// record.serialize(0, data2 ); // rec.length;
// if (LittleEndian.getUShort(data2, 2) != record.getRecordSize() - 4
// && record instanceof RowRecordsAggregate == false
// && record instanceof ValueRecordsAggregate == false
// && record instanceof EscherAggregate == false)
// {
// throw new RuntimeException("Blah!!! Size off by " + ( LittleEndian.getUShort(data2, 2) - record.getRecordSize() - 4) + " records.");
// }
pos += record.serialize(pos + offset, data ); // rec.length;
}
if (log.check( POILogger.DEBUG ))
log.log(POILogger.DEBUG, "Sheet.serialize returning ");
return pos;
return pos-offset;
}
private int serializeIndexRecord(final int BOFRecordIndex, final int offset, byte[] data) {
IndexRecord index = new IndexRecord();
index.setFirstRow(rows.getFirstRowNum());
index.setLastRowAdd1(rows.getLastRowNum()+1);
//Calculate the size of the records from the end of the BOF
//and up to the RowRecordsAggregate...
int sheetRecSize = 0;
for (int j = BOFRecordIndex+1; j < records.size(); j++)
{
Record tmpRec = (( Record ) records.get(j));
if (tmpRec instanceof RowRecordsAggregate)
break;
sheetRecSize+= tmpRec.getRecordSize();
}
//Add the references to the DBCells in the IndexRecord (one for each block)
int blockCount = rows.getRowBlockCount();
//Calculate the size of this IndexRecord
int indexRecSize = index.getRecordSizeForBlockCount(blockCount);
int rowBlockOffset = 0;
int cellBlockOffset = 0;
int dbCellOffset = 0;
for (int block=0;block<blockCount;block++) {
rowBlockOffset += rows.getRowBlockSize(block);
cellBlockOffset += cells.getRowCellBlockSize(rows.getStartRowNumberForBlock(block),
rows.getEndRowNumberForBlock(block));
//Note: The offsets are relative to the Workbook BOF. Assume that this is
//0 for now.....
index.addDbcell(offset + indexRecSize + sheetRecSize + dbCellOffset + rowBlockOffset + cellBlockOffset);
//Add space required to write the dbcell record(s) (whose references were just added).
dbCellOffset += (8 + (rows.getRowCountForBlock(block) * 2));
}
return index.serialize(offset, data);
}
/**
* Create a row record. (does not add it to the records contained in this sheet)
@ -1378,118 +1370,6 @@ public class Sheet implements Model
// return null;
}
/**
* Not currently used method to calculate and add dbcell records
*
*/
public void addDBCellRecords()
{
int offset = 0;
int recnum = 0;
int rownum = 0;
//int lastrow = 0;
//long lastrowoffset = 0;
IndexRecord index = null;
// ArrayList rowOffsets = new ArrayList();
IntList rowOffsets = new IntList();
for (recnum = 0; recnum < records.size(); recnum++)
{
Record rec = ( Record ) records.get(recnum);
if (rec.getSid() == IndexRecord.sid)
{
index = ( IndexRecord ) rec;
}
if (rec.getSid() != RowRecord.sid)
{
offset += rec.serialize().length;
}
else
{
break;
}
}
// First Row Record
for (; recnum < records.size(); recnum++)
{
Record rec = ( Record ) records.get(recnum);
if (rec.getSid() == RowRecord.sid)
{
rownum++;
rowOffsets.add(offset);
if ((rownum % 32) == 0)
{
// if this is the last rec in a dbcell block
// find the next row or last value record
for (int rn = recnum; rn < records.size(); rn++)
{
rec = ( Record ) records.get(rn);
if ((!rec.isInValueSection())
|| (rec.getSid() == RowRecord.sid))
{
// here is the next row or last value record
records.add(rn,
createDBCell(offset, rowOffsets,
index));
recnum = rn;
break;
}
}
}
else
{
}
}
if (!rec.isInValueSection())
{
records.add(recnum, createDBCell(offset, rowOffsets, index));
break;
}
offset += rec.serialize().length;
}
}
/** not currently used */
private DBCellRecord createDBCell(int offset, IntList rowoffsets,
IndexRecord index)
{
DBCellRecord rec = new DBCellRecord();
rec.setRowOffset(offset - rowoffsets.get(0));
// test hack
rec.addCellOffset(( short ) 0x0);
// end test hack
addDbCellToIndex(offset, index);
return rec;
}
/** not currently used */
private void addDbCellToIndex(int offset, IndexRecord index)
{
int numdbcells = index.getNumDbcells() + 1;
index.addDbcell(offset + preoffset);
// stupid but whenever we add an offset that causes everything to be shifted down 4
for (int k = 0; k < numdbcells; k++)
{
int dbval = index.getDbcellAt(k);
index.setDbcell(k, dbval + 4);
}
}
/**
* creates the BOF record
* @see org.apache.poi.hssf.record.BOFRecord
@ -2148,6 +2028,21 @@ public class Sheet implements Model
{
retval += (( Record ) records.get(k)).getRecordSize();
}
//Add space for the IndexRecord
final int blocks = rows.getRowBlockCount();
retval += IndexRecord.getRecordSizeForBlockCount(blocks);
//Add space for the DBCell records
//Once DBCell per block.
//8 bytes per DBCell (non variable section)
//2 bytes per row reference
int startRetVal = retval;
retval += (8 * blocks);
for (Iterator itr = rows.getIterator(); itr.hasNext();) {
RowRecord row = (RowRecord)itr.next();
if (cells.rowHasCells(row.getRowNumber()))
retval += 2;
}
return retval;
}

View File

@ -21,16 +21,18 @@ package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
/**
* Title: DBCell Record (Currently read only. Not required.)
* Description: Used to find rows in blocks...TODO<P>
* Title: DBCell Record
* Description: Used by Excel and other MS apps to quickly find rows in the sheets.<P>
* REFERENCE: PG 299/440 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P>
* @author Andrew C. Oliver (acoliver at apache dot org)
* @author Jason Height
* @version 2.0-pre
*/
public class DBCellRecord
extends Record
{
public final static int BLOCK_SIZE = 32;
public final static short sid = 0xd7;
private int field_1_row_offset;
private short[] field_2_cell_offsets;
@ -180,7 +182,7 @@ public class DBCellRecord
LittleEndian.putInt(data, 4 + offset, getRowOffset());
for (int k = 0; k < getNumCellOffsets(); k++)
{
LittleEndian.putShort(data, 8 + k + offset, getCellOffsetAt(k));
LittleEndian.putShort(data, 8 + 2*k + offset, getCellOffsetAt(k));
}
return getRecordSize();
}
@ -189,6 +191,11 @@ public class DBCellRecord
{
return 8 + (getNumCellOffsets() * 2);
}
/** Returns the size of a DBCellRecord when it needs to reference a certain number of rows*/
public static int getRecordSizeForRows(int rows) {
return 8 + (rows * 2);
}
public short getSid()
{

View File

@ -184,6 +184,13 @@ public class IndexRecord
{
return 20 + (getNumDbcells() * 4);
}
/** Returns the size of an INdexRecord when it needs to index the specified number of blocks
*
*/
public static int getRecordSizeForBlockCount(int blockCount) {
return 20 + (4 * blockCount);
}
public short getSid()
{

View File

@ -18,6 +18,7 @@
package org.apache.poi.hssf.record.aggregates;
import org.apache.poi.hssf.record.DBCellRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RowRecord;
@ -94,30 +95,90 @@ public class RowRecordsAggregate
{
return lastrow;
}
/*
* No need to go through all the records as we're just collecting RowRecords
public int construct(int offset, List records)
{
int k = 0;
for (k = offset; k < records.size(); k++)
{
Record rec = ( Record ) records.get(k);
if (!rec.isInValueSection() && !(rec instanceof UnknownRecord))
{
break;
}
if (rec.getSid() == RowRecord.sid)
{
insertRow(( RowRecord ) rec);
}
}
return k;
/** 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 = records.size()/DBCellRecord.BLOCK_SIZE;
if ((records.size() % DBCellRecord.BLOCK_SIZE) != 0)
size++;
return size;
}
*/
public int getRowBlockSize(int block) {
return 20 * 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 >= records.size())
endIndex = records.size()-1;
return endIndex-startIndex+1;
}
/** Returns the physical row number of the first row in a block*/
public int getStartRowNumberForBlock(int block) {
//JMH 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 startIndex = block * DBCellRecord.BLOCK_SIZE;
Iterator rowIter = records.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();
}
return row.getRowNumber();
}
/** Returns the physical row number of the end row in a block*/
public int getEndRowNumberForBlock(int block) {
int endIndex = ((block + 1)*DBCellRecord.BLOCK_SIZE)-1;
if (endIndex >= records.size())
endIndex = records.size()-1;
Iterator rowIter = records.values().iterator();
RowRecord row = null;
for (int i=0; i<=endIndex;i++) {
row = (RowRecord)rowIter.next();
}
return row.getRowNumber();
}
/** Serializes a block of the rows */
private int serializeRowBlock(final int block, final int offset, byte[] data) {
final int startIndex = block*DBCellRecord.BLOCK_SIZE;
final int endIndex = startIndex + DBCellRecord.BLOCK_SIZE;
Iterator rowIterator = records.values().iterator();
int pos = offset;
//JMH 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();
while(rowIterator.hasNext() && (i++ < endIndex)) {
RowRecord row = (RowRecord)rowIterator.next();
pos += row.serialize(pos, data);
}
return pos - offset;
}
public int serialize(int offset, byte [] data) {
throw new RuntimeException("The serialize method that passes in cells should be used");
}
/**
* called by the class that is responsible for writing this sucker.
@ -129,14 +190,38 @@ public class RowRecordsAggregate
* @return number of bytes written
*/
public int serialize(int offset, byte [] data)
public int serialize(int offset, byte [] data, ValueRecordsAggregate cells)
{
Iterator itr = records.values().iterator();
int pos = offset;
int pos = offset;
//DBCells are serialized before row records.
final int blockCount = getRowBlockCount();
for (int block=0;block<blockCount;block++) {
//Serialize a block of rows.
//Hold onto the position of the first row in the block
final int rowStartPos = pos;
//Hold onto the size of this block that was serialized
final int rowBlockSize = serializeRowBlock(block, pos, data);
pos += rowBlockSize;
//Serialize a block of cells for those rows
final int startRowNumber = getStartRowNumberForBlock(block);
final int endRowNumber = getEndRowNumberForBlock(block);
DBCellRecord cellRecord = new DBCellRecord();
//Note: Cell references start from the second row...
int cellRefOffset = (rowBlockSize-20);
for (int row=startRowNumber;row<=endRowNumber;row++) {
if (cells.rowHasCells(row)) {
final int rowCellSize = cells.serializeCellRow(row, pos, data);
pos += rowCellSize;
//Add the offset to the first cell for the row into the DBCellRecord.
cellRecord.addCellOffset((short)cellRefOffset);
cellRefOffset = rowCellSize;
}
}
//Calculate Offset from the start of a DBCellRecord to the first Row
cellRecord.setRowOffset(pos - rowStartPos);
pos += cellRecord.serialize(pos, data);
while (itr.hasNext())
{
pos += (( Record ) itr.next()).serialize(pos, data);
}
return pos - offset;
}

View File

@ -22,8 +22,10 @@ import org.apache.poi.hssf.record.*;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
/**
*
* Aggregate value records together. Things are easier to handle that way.
@ -40,8 +42,114 @@ public class ValueRecordsAggregate
int firstcell = -1;
int lastcell = -1;
TreeMap records = null;
// int size = 0;
/** This class is used to find a row in the TreeMap.
*
* This instance of which is used by the rowHasCells method as the key.
*/
private class RowComparator implements CellValueRecordInterface, Comparable {
private int row;
public void setRow(int row) {
this.row = row;
}
public int compareTo(Object obj) {
CellValueRecordInterface cell = (CellValueRecordInterface) obj;
if (row == cell.getRow()) {
return 0;
}
else if (row < cell.getRow()) {
return -1;
}
else if (row > cell.getRow()){
return 1;
}
return -1;
}
public int getRow() { return row;}
public short getColumn() { return 0;}
public void setColumn(short col){}
public void setXFIndex(short xf){}
public short getXFIndex(){return 0;}
public boolean isBefore(CellValueRecordInterface i){ return false; }
public boolean isAfter(CellValueRecordInterface i){ return false; }
public boolean isEqual(CellValueRecordInterface i){ return false; }
public Object clone(){ return null;}
}
/**
* Iterates the cell records that exist between the startRow and endRow (inclusive).
*
* User must ensure that hasNext & next are called insequence for correct
* operation. Could fix, but since this is only used internally to the
* ValueRecordsAggregate class there doesnt seem much point.
*/
private class RowCellIterator implements Iterator {
private int startRow;
private int endRow;
private Iterator internalIterator;
private CellValueRecordInterface atCell;
public class RowCellComparator extends RowComparator {
public int compareTo(Object obj) {
CellValueRecordInterface cell = (CellValueRecordInterface) obj;
if (getRow() == cell.getRow() && cell.getColumn() == 0) {
return 0;
}
else if (getRow() < cell.getRow()) {
return -1;
}
else if (getRow() > cell.getRow()){
return 1;
}
if (cell.getColumn() > 0)
{
return -1;
}
if (cell.getColumn() < 0)
{
return 1;
}
return -1;
}
}
private RowCellComparator rowCellCompare;
public RowCellIterator(int startRow, int endRow) {
this.startRow = startRow;
this.endRow = endRow;
rowCellCompare = new RowCellComparator();
rowCellCompare.setRow(startRow);
}
public boolean hasNext() {
if (internalIterator == null) {
internalIterator = records.tailMap(rowCellCompare).values().iterator();
}
if (internalIterator.hasNext()) {
atCell = (CellValueRecordInterface) internalIterator.next();
return (atCell.getRow() <= endRow);
} else return false;
}
public Object next() {
return atCell;
}
public void remove() {
//Do Nothing (Not called)
}
}
//Only need a single instance of this class, but the row fields
//will probably change each use. Instance is only used in the rowHasCells method.
public final RowComparator compareRow = new RowComparator();
/** Creates a new instance of ValueRecordsAggregate */
public ValueRecordsAggregate()
@ -51,17 +159,6 @@ public class ValueRecordsAggregate
public void insertCell(CellValueRecordInterface cell)
{
/* if (records.get(cell) == null)
{
size += (( Record ) cell).getRecordSize();
}
else
{
size += (( Record ) cell).getRecordSize()
- (( Record ) records.get(cell)).getRecordSize();
}*/
// XYLocator xy = new XYLocator(cell.getRow(), cell.getColumn());
Object o = records.put(cell, cell);
if ((cell.getColumn() < firstcell) || (firstcell == -1))
@ -76,9 +173,6 @@ public class ValueRecordsAggregate
public void removeCell(CellValueRecordInterface cell)
{
// size -= (( Record ) cell).getRecordSize();
// XYLocator xy = new XYLocator(cell.getRow(), cell.getColumn());
records.remove(cell);
}
@ -145,15 +239,49 @@ public class ValueRecordsAggregate
public int serialize(int offset, byte [] data)
{
Iterator itr = records.values().iterator();
throw new RuntimeException("This method shouldnt be called. ValueRecordsAggregate.serializeCellRow() should be called from RowRecordsAggregate.");
}
/** Tallies a count of the size of the cell records
* that are attached to the rows in the range specified.
*/
public int getRowCellBlockSize(int startRow, int endRow) {
RowCellIterator itr = new RowCellIterator(startRow, endRow);
int size = 0;
while (itr.hasNext()) {
CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
int row = cell.getRow();
if (row > endRow)
break;
if ((row >=startRow) && (row <= endRow))
size += ((Record)cell).getRecordSize();
}
return size;
}
/** Returns true if the row has cells attached to it */
public boolean rowHasCells(int row) {
compareRow.setRow(row);
return records.containsKey(compareRow);
}
/** Serializes the cells that are allocated to a certain row range*/
public int serializeCellRow(final int row, int offset, byte [] data)
{
RowCellIterator itr = new RowCellIterator(row, row);
int pos = offset;
while (itr.hasNext())
{
pos += (( Record ) itr.next()).serialize(pos, data);
CellValueRecordInterface cell = (CellValueRecordInterface)itr.next();
if (cell.getRow() != row)
break;
pos += (( Record ) cell).serialize(pos, data);
}
return pos - offset;
}
/**
* called by the constructor, should set class level fields. Should throw
* runtime exception for bad/icomplete data.
@ -197,7 +325,6 @@ public class ValueRecordsAggregate
}
return size;
// return size;
}
public Iterator getIterator()
@ -214,59 +341,4 @@ public class ValueRecordsAggregate
}
return rec;
}
}
/*
* class XYLocator implements Comparable {
* private int row = 0;
* private int col = 0;
* public XYLocator(int row, int col) {
* this.row = row;
* this.col = col;
* }
*
* public int getRow() {
* return row;
* }
*
* public int getCol() {
* return col;
* }
*
* public int compareTo(Object obj) {
* XYLocator loc = (XYLocator)obj;
*
* if (this.getRow() == loc.getRow() &&
* this.getCol() == loc.getCol() )
* return 0;
*
* if (this.getRow() < loc.getRow())
* return -1;
*
* if (this.getRow() > loc.getRow())
* return 1;
*
* if (this.getCol() < loc.getCol())
* return -1;
*
* if (this.getCol() > loc.getCol())
* return 1;
*
* return -1;
*
* }
*
* public boolean equals(Object obj) {
* if (!(obj instanceof XYLocator)) return false;
*
* XYLocator loc = (XYLocator)obj;
* if (this.getRow() == loc.getRow()
* &&
* this.getCol() == loc.getCol()
* ) return true;
* return false;
* }
*
*
* }
*/
}