diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 705e367b6..14bf40256 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -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 + * Title: DBCell Record + * Description: Used by Excel and other MS apps to quickly find rows in the sheets.

* REFERENCE: PG 299/440 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

* @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() { diff --git a/src/java/org/apache/poi/hssf/record/IndexRecord.java b/src/java/org/apache/poi/hssf/record/IndexRecord.java index 883c929e8..f010e4ac0 100644 --- a/src/java/org/apache/poi/hssf/record/IndexRecord.java +++ b/src/java/org/apache/poi/hssf/record/IndexRecord.java @@ -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() { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java index 32f25b312..85e98f62a 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/RowRecordsAggregate.java @@ -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. + *

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 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; - * } - * - * - * } - */ +} \ No newline at end of file