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:
parent
12e76ef706
commit
7000eed3a9
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
BIN
src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls
Normal file
BIN
src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls
Normal file
Binary file not shown.
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user