Fix for bug 45698 - allow LinkTable to read EXTERNSHEET records

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@689704 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-08-28 02:54:47 +00:00
parent 12e76ef706
commit 7000eed3a9
6 changed files with 248 additions and 198 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45698 - Fix LinkTable to tolerate multiple EXTERNSHEET records</action>
<action dev="POI-DEVELOPERS" type="fix">45682 - Fix for cloning of CFRecordsAggregate</action>
<action dev="POI-DEVELOPERS" type="add">Initial support for evaluating external add-in functions like YEARFRAC</action>
<action dev="POI-DEVELOPERS" type="fix">45672 - Fix for MissingRecordAwareHSSFListener to prevent multiple LastCellOfRowDummyRecords when shared formulas are present</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45698 - Fix LinkTable to tolerate multiple EXTERNSHEET records</action>
<action dev="POI-DEVELOPERS" type="fix">45682 - Fix for cloning of CFRecordsAggregate</action>
<action dev="POI-DEVELOPERS" type="add">Initial support for evaluating external add-in functions like YEARFRAC</action>
<action dev="POI-DEVELOPERS" type="fix">45672 - Fix for MissingRecordAwareHSSFListener to prevent multiple LastCellOfRowDummyRecords when shared formulas are present</action>

View File

@ -159,8 +159,7 @@ final class LinkTable {
if (_externalBookBlocks.length > 0) {
// If any ExternalBookBlock present, there is always 1 of ExternSheetRecord
Record next = rs.getNext();
_externSheetRecord = (ExternSheetRecord) next;
_externSheetRecord = readExtSheetRecord(rs);
} else {
_externSheetRecord = null;
}
@ -176,6 +175,28 @@ final class LinkTable {
_workbookRecordList.getRecords().addAll(inputList.subList(startIndex, startIndex + _recordCount));
}
private static ExternSheetRecord readExtSheetRecord(RecordStream rs) {
List temp = new ArrayList(2);
while(rs.peekNextClass() == ExternSheetRecord.class) {
temp.add(rs.getNext());
}
int nItems = temp.size();
if (nItems < 1) {
throw new RuntimeException("Expected an EXTERNSHEET record but got ("
+ rs.peekNextClass().getName() + ")");
}
if (nItems == 1) {
// this is the normal case. There should be just one ExternSheetRecord
return (ExternSheetRecord) temp.get(0);
}
// Some apps generate multiple ExternSheetRecords (see bug 45698).
// It seems like the best thing to do might be to combine these into one
ExternSheetRecord[] esrs = new ExternSheetRecord[nItems];
temp.toArray(esrs);
return ExternSheetRecord.combine(esrs);
}
public LinkTable(short numberOfSheets, WorkbookRecordList workbookRecordList) {
_workbookRecordList = workbookRecordList;
_definedNames = new ArrayList();

View File

@ -29,212 +29,212 @@ import org.apache.poi.util.LittleEndian;
* @author Libin Roman (Vista Portal LDT. Developer)
*/
public class ExternSheetRecord extends Record {
public final static short sid = 0x0017;
private List _list;
private final class RefSubRecord {
public static final int ENCODED_SIZE = 6;
public final static short sid = 0x0017;
private List _list;
private final class RefSubRecord {
public static final int ENCODED_SIZE = 6;
/** index to External Book Block (which starts with a EXTERNALBOOK record) */
private int _extBookIndex;
private int _firstSheetIndex; // may be -1 (0xFFFF)
private int _lastSheetIndex; // may be -1 (0xFFFF)
/** a Constructor for making new sub record
*/
public RefSubRecord(int extBookIndex, int firstSheetIndex, int lastSheetIndex) {
_extBookIndex = extBookIndex;
_firstSheetIndex = firstSheetIndex;
_lastSheetIndex = lastSheetIndex;
}
/**
* @param in the RecordInputstream to read the record from
*/
public RefSubRecord(RecordInputStream in) {
this(in.readShort(), in.readShort(), in.readShort());
}
public int getExtBookIndex(){
return _extBookIndex;
}
public int getFirstSheetIndex(){
return _firstSheetIndex;
}
public int getLastSheetIndex(){
return _lastSheetIndex;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("extBook=").append(_extBookIndex);
buffer.append(" firstSheet=").append(_firstSheetIndex);
buffer.append(" lastSheet=").append(_lastSheetIndex);
return buffer.toString();
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param offset to begin writing at
* @param data byte array containing instance data
* @return number of bytes written
*/
public void serialize(int offset, byte [] data) {
LittleEndian.putUShort(data, 0 + offset, _extBookIndex);
LittleEndian.putUShort(data, 2 + offset, _firstSheetIndex);
LittleEndian.putUShort(data, 4 + offset, _lastSheetIndex);
}
}
public ExternSheetRecord() {
_list = new ArrayList();
}
/**
* Constructs a Extern Sheet record and sets its fields appropriately.
* @param in the RecordInputstream to read the record from
*/
public ExternSheetRecord(RecordInputStream in) {
super(in);
}
/**
* called by constructor, should throw runtime exception in the event of a
* record passed with a differing ID.
*
* @param id alleged id for this record
*/
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT An ExternSheet RECORD");
}
}
/**
* called by the constructor, should set class level fields. Should throw
* runtime exception for bad/icomplete data.
*
* @param in the RecordInputstream to read the record from
*/
protected void fillFields(RecordInputStream in) {
_list = new ArrayList();
int nItems = in.readShort();
for (int i = 0 ; i < nItems ; ++i) {
RefSubRecord rec = new RefSubRecord(in);
_list.add( rec);
}
}
/** index to External Book Block (which starts with a EXTERNALBOOK record) */
private int _extBookIndex;
private int _firstSheetIndex; // may be -1 (0xFFFF)
private int _lastSheetIndex; // may be -1 (0xFFFF)
/** a Constructor for making new sub record
*/
public RefSubRecord(int extBookIndex, int firstSheetIndex, int lastSheetIndex) {
_extBookIndex = extBookIndex;
_firstSheetIndex = firstSheetIndex;
_lastSheetIndex = lastSheetIndex;
}
/**
* @param in the RecordInputstream to read the record from
*/
public RefSubRecord(RecordInputStream in) {
this(in.readShort(), in.readShort(), in.readShort());
}
public int getExtBookIndex(){
return _extBookIndex;
}
public int getFirstSheetIndex(){
return _firstSheetIndex;
}
public int getLastSheetIndex(){
return _lastSheetIndex;
}
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("extBook=").append(_extBookIndex);
buffer.append(" firstSheet=").append(_firstSheetIndex);
buffer.append(" lastSheet=").append(_lastSheetIndex);
return buffer.toString();
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param offset to begin writing at
* @param data byte array containing instance data
* @return number of bytes written
*/
public void serialize(int offset, byte [] data) {
LittleEndian.putUShort(data, 0 + offset, _extBookIndex);
LittleEndian.putUShort(data, 2 + offset, _firstSheetIndex);
LittleEndian.putUShort(data, 4 + offset, _lastSheetIndex);
}
}
public ExternSheetRecord() {
_list = new ArrayList();
}
/**
* Constructs a Extern Sheet record and sets its fields appropriately.
* @param in the RecordInputstream to read the record from
*/
public ExternSheetRecord(RecordInputStream in) {
super(in);
}
/**
* called by constructor, should throw runtime exception in the event of a
* record passed with a differing ID.
*
* @param id alleged id for this record
*/
protected void validateSid(short id) {
if (id != sid) {
throw new RecordFormatException("NOT An ExternSheet RECORD");
}
}
/**
* called by the constructor, should set class level fields. Should throw
* runtime exception for bad/icomplete data.
*
* @param in the RecordInputstream to read the record from
*/
protected void fillFields(RecordInputStream in) {
_list = new ArrayList();
int nItems = in.readShort();
for (int i = 0 ; i < nItems ; ++i) {
RefSubRecord rec = new RefSubRecord(in);
_list.add( rec);
}
}
/**
* @return number of REF structures
*/
public int getNumOfRefs() {
return _list.size();
}
/**
* adds REF struct (ExternSheetSubRecord)
* @param rec REF struct
*/
public void addREFRecord(RefSubRecord rec) {
_list.add(rec);
}
/** returns the number of REF Records, which is in model
* @return number of REF records
*/
public int getNumOfREFRecords() {
return _list.size();
}
public String toString() {
StringBuffer sb = new StringBuffer();
int nItems = _list.size();
sb.append("[EXTERNSHEET]\n");
sb.append(" numOfRefs = ").append(nItems).append("\n");
for (int i=0; i < nItems; i++) {
sb.append("refrec #").append(i).append(": ");
sb.append(getRef(i).toString());
sb.append('\n');
}
sb.append("[/EXTERNSHEET]\n");
return sb.toString();
}
private int getDataSize() {
return 2 + _list.size() * RefSubRecord.ENCODED_SIZE;
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param offset to begin writing at
* @param data byte array containing instance data
* @return number of bytes written
*/
public int serialize(int offset, byte [] data) {
int dataSize = getDataSize();
int nItems = _list.size();
/**
* @return number of REF structures
*/
public int getNumOfRefs() {
return _list.size();
}
/**
* adds REF struct (ExternSheetSubRecord)
* @param rec REF struct
*/
public void addREFRecord(RefSubRecord rec) {
_list.add(rec);
}
/** returns the number of REF Records, which is in model
* @return number of REF records
*/
public int getNumOfREFRecords() {
return _list.size();
}
public String toString() {
StringBuffer sb = new StringBuffer();
int nItems = _list.size();
sb.append("[EXTERNSHEET]\n");
sb.append(" numOfRefs = ").append(nItems).append("\n");
for (int i=0; i < nItems; i++) {
sb.append("refrec #").append(i).append(": ");
sb.append(getRef(i).toString());
sb.append('\n');
}
sb.append("[/EXTERNSHEET]\n");
return sb.toString();
}
private int getDataSize() {
return 2 + _list.size() * RefSubRecord.ENCODED_SIZE;
}
/**
* called by the class that is responsible for writing this sucker.
* Subclasses should implement this so that their data is passed back in a
* byte array.
*
* @param offset to begin writing at
* @param data byte array containing instance data
* @return number of bytes written
*/
public int serialize(int offset, byte [] data) {
int dataSize = getDataSize();
int nItems = _list.size();
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putUShort(data, 2 + offset, dataSize);
LittleEndian.putUShort(data, 4 + offset, nItems);
int pos = 6 ;
for (int i = 0; i < nItems; i++) {
getRef(i).serialize(offset + pos, data);
pos +=6;
}
return dataSize + 4;
}
LittleEndian.putUShort(data, 4 + offset, nItems);
int pos = 6 ;
for (int i = 0; i < nItems; i++) {
getRef(i).serialize(offset + pos, data);
pos +=6;
}
return dataSize + 4;
}
private RefSubRecord getRef(int i) {
return (RefSubRecord) _list.get(i);
}
public int getRecordSize() {
return 4 + getDataSize();
}
/**
* return the non static version of the id for this record.
*/
public short getSid() {
return sid;
}
public int getRecordSize() {
return 4 + getDataSize();
}
/**
* return the non static version of the id for this record.
*/
public short getSid() {
return sid;
}
public int getExtbookIndexFromRefIndex(int refIndex) {
return getRef(refIndex).getExtBookIndex();
return getRef(refIndex).getExtBookIndex();
}
/**
* @return -1 if not found
*/
public int findRefIndexFromExtBookIndex(int extBookIndex) {
int nItems = _list.size();
for (int i = 0; i < nItems; i++) {
if (getRef(i).getExtBookIndex() == extBookIndex) {
return i;
}
}
int nItems = _list.size();
for (int i = 0; i < nItems; i++) {
if (getRef(i).getExtBookIndex() == extBookIndex) {
return i;
}
}
return -1;
}
@ -251,13 +251,25 @@ public class ExternSheetRecord extends Record {
}
public int getRefIxForSheet(int sheetIndex) {
int nItems = _list.size();
for (int i = 0; i < nItems; i++) {
RefSubRecord ref = getRef(i);
int nItems = _list.size();
for (int i = 0; i < nItems; i++) {
RefSubRecord ref = getRef(i);
if (ref.getFirstSheetIndex() == sheetIndex && ref.getLastSheetIndex() == sheetIndex) {
return i;
}
}
return i;
}
}
return -1;
}
public static ExternSheetRecord combine(ExternSheetRecord[] esrs) {
ExternSheetRecord result = new ExternSheetRecord();
for (int i = 0; i < esrs.length; i++) {
ExternSheetRecord esr = esrs[i];
int nRefs = esr.getNumOfREFRecords();
for (int j=0; j<nRefs; j++) {
result.addREFRecord(esr.getRef(j));
}
}
return result;
}
}

View File

@ -41,4 +41,19 @@ public final class TestLinkTable extends TestCase {
assertEquals("ipcSummenproduktIntern($C5,N$2,$A$9,N$1)", formula);
}
public void testMultipleExternSheetRecords_bug45698() {
HSSFWorkbook wb;
try {
wb = HSSFTestDataSamples.openSampleWorkbook("ex45698-22488.xls");
} catch (RuntimeException e) {
if ("Extern sheet is part of LinkTable".equals(e.getMessage())) {
throw new AssertionFailedError("Identified bug 45698");
}
throw e;
}
// some other sanity checks
assertEquals(7, wb.getNumberOfSheets());
}
}