]> source.dussan.org Git - poi.git/commitdiff
Fix for bug 45698 - allow LinkTable to read EXTERNSHEET records
authorJosh Micich <josh@apache.org>
Thu, 28 Aug 2008 02:54:47 +0000 (02:54 +0000)
committerJosh Micich <josh@apache.org>
Thu, 28 Aug 2008 02:54:47 +0000 (02:54 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@689704 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/LinkTable.java
src/java/org/apache/poi/hssf/record/ExternSheetRecord.java
src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestLinkTable.java

index 1fe905a68658364746ee73266990e3561158dee8..87b3468994f522411cf852f5056e3b8774929f85 100644 (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>
index 6233176ed22d61adeb10a4ebe2b89d318ac253b0..127c12e8cbf94820d4a895de0216bb331929389c 100644 (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>
index 9d1707558d63792de794dadbbf2f3c0e17d05211..a64f134fed9ffab831eeebd1b0b10a402c55c334 100755 (executable)
@@ -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();
index 2b0744a91e3e7be67a728aaa24d12579fe7b60a5..c4f00581b2e13eb5be9373a2555a8da8ea42d3c7 100644 (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;
+       }
 }
diff --git a/src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls b/src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls
new file mode 100644 (file)
index 0000000..615e6c5
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/ex45698-22488.xls differ
index 7d1082e86156b73fb829da0df9b8a030eaed8aa9..d8b4e95212c46de5d95328ed0a4415cee8c712ad 100644 (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());
+       }
 }