]> source.dussan.org Git - poi.git/commitdiff
fixed RecordFormatException when reading LbsDataSubRecord, see bugzilla 47701
authorYegor Kozlov <yegor@apache.org>
Wed, 2 Dec 2009 10:56:01 +0000 (10:56 +0000)
committerYegor Kozlov <yegor@apache.org>
Wed, 2 Dec 2009 10:56:01 +0000 (10:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@886113 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/EndSubRecord.java
src/java/org/apache/poi/hssf/record/LbsDataSubRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/ObjRecord.java
src/java/org/apache/poi/hssf/record/SubRecord.java
src/testcases/org/apache/poi/hssf/record/TestLbsDataSubRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
test-data/spreadsheet/47701.xls [new file with mode: 0644]

index 26d63412425849e5e208786465462b7a3422bc50..d9c53a0156ce9465a7fc21fa822525a168ee9762 100644 (file)
@@ -34,6 +34,7 @@
 
     <changes>
         <release version="3.6-beta1" date="2009-??-??">
+          <action dev="POI-DEVELOPERS" type="fix">47701 - fixed RecordFormatException when reading list subrecords (LbsDataSubRecord)</action> 
           <action dev="POI-DEVELOPERS" type="add"> memory usage optimization in XSSF - avoid creating parentless xml beans</action> 
           <action dev="POI-DEVELOPERS" type="fix">47188 - avoid corruption of workbook when adding cell comments </action> 
           <action dev="POI-DEVELOPERS" type="fix">48106 - improved work with cell comments in XSSF</action> 
index 518dc2524bb31f1d3b5672ce0f6365f795d7eb26..e31692b021a21ec737ead7464a560b19c4164532 100644 (file)
@@ -46,6 +46,11 @@ public final class EndSubRecord extends SubRecord {
         }
     }
 
+    @Override
+    public boolean isTerminating(){
+        return true;
+    }
+
     public String toString()
     {
         StringBuffer buffer = new StringBuffer();
diff --git a/src/java/org/apache/poi/hssf/record/LbsDataSubRecord.java b/src/java/org/apache/poi/hssf/record/LbsDataSubRecord.java
new file mode 100755 (executable)
index 0000000..7121765
--- /dev/null
@@ -0,0 +1,340 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+package org.apache.poi.hssf.record;\r
+\r
+import org.apache.poi.hssf.record.formula.*;\r
+import org.apache.poi.util.*;\r
+\r
+/**\r
+ * This structure specifies the properties of a list or drop-down list embedded object in a sheet.\r
+ */\r
+public class LbsDataSubRecord extends SubRecord {\r
+\r
+    public static final int sid = 0x0013;\r
+\r
+    /**\r
+     * From [MS-XLS].pdf 2.5.147 FtLbsData:\r
+     *\r
+     * An unsigned integer that indirectly specifies whether\r
+     * some of the data in this structure appear in a subsequent Continue record.\r
+     * If _cbFContinued is 0x00, all of the fields in this structure except sid and _cbFContinued\r
+     *  MUST NOT exist. If this entire structure is contained within the same record,\r
+     * then _cbFContinued MUST be greater than or equal to the size, in bytes,\r
+     * of this structure, not including the four bytes for the ft and _cbFContinued fields\r
+     */\r
+    private int _cbFContinued;\r
+\r
+    /**\r
+     * a formula that specifies the range of cell values that are the items in this list.\r
+     */\r
+    private int _unknownPreFormulaInt;\r
+    private Ptg _linkPtg;\r
+    private Byte _unknownPostFormulaByte;\r
+\r
+    /**\r
+     * An unsigned integer that specifies the number of items in the list.\r
+     */\r
+    private int _cLines;\r
+\r
+    /**\r
+     * An unsigned integer that specifies the one-based index of the first selected item in this list.\r
+     * A value of 0x00 specifies there is no currently selected item.\r
+     */\r
+    private int _iSel;\r
+\r
+    /**\r
+     *  flags that tell what data follows\r
+     */\r
+    private int _flags;\r
+\r
+    /**\r
+     * An ObjId that specifies the edit box associated with this list.\r
+     * A value of 0x00 specifies that there is no edit box associated with this list.\r
+     */\r
+    private int _idEdit;\r
+\r
+    /**\r
+     * An optional LbsDropData that specifies properties for this dropdown control.\r
+     * This field MUST exist if and only if the containing Obj?s cmo.ot is equal to 0x14.\r
+     */\r
+    private LbsDropData _dropData;\r
+\r
+    /**\r
+     * An optional array of strings where each string specifies an item in the list.\r
+     * The number of elements in this array, if it exists, MUST be {@link #_cLines}\r
+     */\r
+    private String[] _rgLines;\r
+\r
+    /**\r
+     * An optional array of booleans that specifies\r
+     * which items in the list are part of a multiple selection\r
+     */\r
+    private boolean[] _bsels;\r
+\r
+    /**\r
+     * @param in the stream to read data from\r
+     * @param cbFContinued the seconf short in the record header\r
+     * @param cmoOt the containing Obj's {@link CommonObjectDataSubRecord#field_1_objectType}\r
+     */\r
+    public LbsDataSubRecord(LittleEndianInput in, int cbFContinued, int cmoOt) {\r
+        _cbFContinued = cbFContinued;\r
+\r
+        int encodedTokenLen = in.readUShort();\r
+        if (encodedTokenLen > 0) {\r
+            int formulaSize = in.readUShort();\r
+            _unknownPreFormulaInt = in.readInt();\r
+\r
+            Ptg[] ptgs = Ptg.readTokens(formulaSize, in);\r
+            if (ptgs.length != 1) {\r
+                throw new RecordFormatException("Read " + ptgs.length\r
+                        + " tokens but expected exactly 1");\r
+            }\r
+            _linkPtg = ptgs[0];\r
+            switch (encodedTokenLen - formulaSize - 6) {\r
+                case 1:\r
+                    _unknownPostFormulaByte = in.readByte();\r
+                    break;\r
+                case 0:\r
+                    _unknownPostFormulaByte = null;\r
+                    break;\r
+                default:\r
+                    throw new RecordFormatException("Unexpected leftover bytes");\r
+            }\r
+        }\r
+\r
+        _cLines = in.readUShort();\r
+        _iSel = in.readUShort();\r
+        _flags = in.readUShort();\r
+        _idEdit = in.readUShort();\r
+\r
+        // From [MS-XLS].pdf 2.5.147 FtLbsData:\r
+        // This field MUST exist if and only if the containing Obj?s cmo.ot is equal to 0x14.\r
+        if(cmoOt == 0x14) {\r
+            _dropData = new LbsDropData(in);\r
+        }\r
+\r
+        // From [MS-XLS].pdf 2.5.147 FtLbsData:\r
+        // This array MUST exist if and only if the fValidPlex flag (0x2) is set\r
+        if((_flags & 0x2) != 0) {\r
+            _rgLines = new String[_cLines];\r
+            for(int i=0; i < _cLines; i++) {\r
+                _rgLines[i] = StringUtil.readUnicodeString(in);\r
+            }\r
+        }\r
+\r
+        // bits 5-6 in the _flags specify the type\r
+        // of selection behavior this list control is expected to support\r
+\r
+        // From [MS-XLS].pdf 2.5.147 FtLbsData:\r
+        // This array MUST exist if and only if the wListType field is not equal to 0.\r
+        if(((_flags >> 4) & 0x2) != 0) {\r
+            _bsels = new boolean[_cLines];\r
+            for(int i=0; i < _cLines; i++) {\r
+                _bsels[i] = in.readByte() == 1;\r
+            }\r
+        }\r
+\r
+    }\r
+\r
+    /**\r
+     * @return true as LbsDataSubRecord is always the last sub-record\r
+     */\r
+    @Override\r
+    public boolean isTerminating(){\r
+        return true;\r
+    }\r
+\r
+    @Override\r
+    protected int getDataSize() {\r
+        int result = 2; // 2 initial shorts\r
+\r
+        // optional link formula\r
+        if (_linkPtg != null) {\r
+            result += 2; // encoded Ptg size\r
+            result += 4; // unknown int\r
+            result += _linkPtg.getSize();\r
+            if (_unknownPostFormulaByte != null) {\r
+                result += 1;\r
+            }\r
+        }\r
+\r
+        result += 4 * 2; // 4 shorts\r
+        if(_dropData != null) {\r
+            result += _dropData.getDataSize();\r
+        }\r
+        if(_rgLines != null) {\r
+            for(String str : _rgLines){\r
+                result += StringUtil.getEncodedSize(str);\r
+            }\r
+        }\r
+        if(_bsels != null) {\r
+            result += _bsels.length;\r
+        }\r
+        return result;\r
+    }\r
+\r
+    @Override\r
+    public void serialize(LittleEndianOutput out) {\r
+        out.writeShort(sid);\r
+        out.writeShort(_cbFContinued);\r
+\r
+        if (_linkPtg == null) {\r
+            out.writeShort(0);\r
+        } else {\r
+            int formulaSize = _linkPtg.getSize();\r
+            int linkSize = formulaSize + 6;\r
+            if (_unknownPostFormulaByte != null) {\r
+                linkSize++;\r
+            }\r
+            out.writeShort(linkSize);\r
+            out.writeShort(formulaSize);\r
+            out.writeInt(_unknownPreFormulaInt);\r
+            _linkPtg.write(out);\r
+            if (_unknownPostFormulaByte != null) {\r
+                out.writeByte(_unknownPostFormulaByte.intValue());\r
+            }\r
+        }\r
+\r
+        out.writeShort(_cLines);\r
+        out.writeShort(_iSel);\r
+        out.writeShort(_flags);\r
+        out.writeShort(_idEdit);\r
+\r
+        if(_dropData != null) {\r
+            _dropData.serialize(out);\r
+        }\r
+\r
+        if(_rgLines != null) {\r
+            for(String str : _rgLines){\r
+                StringUtil.writeUnicodeString(out, str);\r
+            }\r
+        }\r
+\r
+        if(_bsels != null) {\r
+            for(boolean val : _bsels){\r
+                out.writeByte(val ? 1 : 0);\r
+            }\r
+        }\r
+    }\r
+\r
+    @Override\r
+    public Object clone() {\r
+        return this;\r
+    }\r
+\r
+    @Override\r
+    public String toString() {\r
+        StringBuffer sb = new StringBuffer(256);\r
+\r
+        sb.append("[ftLbsData]\n");\r
+        sb.append("    .unknownShort1 =").append(HexDump.shortToHex(_cbFContinued)).append("\n");\r
+        sb.append("    .formula        = ").append('\n');\r
+        sb.append(_linkPtg.toString()).append(_linkPtg.getRVAType()).append('\n');\r
+        sb.append("    .nEntryCount   =").append(HexDump.shortToHex(_cLines)).append("\n");\r
+        sb.append("    .selEntryIx    =").append(HexDump.shortToHex(_iSel)).append("\n");\r
+        sb.append("    .style         =").append(HexDump.shortToHex(_flags)).append("\n");\r
+        sb.append("    .unknownShort10=").append(HexDump.shortToHex(_idEdit)).append("\n");\r
+        if(_dropData != null) sb.append('\n').append(_dropData.toString());\r
+        sb.append("[/ftLbsData]\n");\r
+        return sb.toString();\r
+    }\r
+\r
+    /**\r
+     *\r
+     * @return the formula that specifies the range of cell values that are the items in this list.\r
+     */\r
+    public Ptg getFormula(){\r
+        return _linkPtg;\r
+    }\r
+\r
+    /**\r
+     * @return the number of items in the list\r
+     */\r
+    public int getNumberOfItems(){\r
+        return _cLines;\r
+    }\r
+\r
+    /**\r
+     * This structure specifies properties of the dropdown list control\r
+     */\r
+    public static class LbsDropData {\r
+        /**\r
+         *  An unsigned integer that specifies the style of this dropdown. \r
+         */\r
+        private int _wStyle;\r
+\r
+        /**\r
+         * An unsigned integer that specifies the number of lines to be displayed in the dropdown.\r
+         */\r
+        private int _cLine;\r
+\r
+        /**\r
+         * An unsigned integer that specifies the smallest width in pixels allowed for the dropdown window\r
+         */\r
+        private int _dxMin;\r
+\r
+        /**\r
+         * a string that specifies the current string value in the dropdown\r
+         */\r
+        private String _str;\r
+\r
+        /**\r
+         * Optional, undefined and MUST be ignored.\r
+         * This field MUST exist if and only if the size of str in bytes is an odd number\r
+         */\r
+        private Byte _unused;\r
+\r
+        public LbsDropData(LittleEndianInput in){\r
+            _wStyle = in.readUShort();\r
+            _cLine = in.readUShort();\r
+            _dxMin = in.readUShort();\r
+            _str = StringUtil.readUnicodeString(in);\r
+            if(StringUtil.getEncodedSize(_str) % 2 != 0){\r
+                _unused = in.readByte();\r
+            }\r
+        }\r
+\r
+        public void serialize(LittleEndianOutput out) {\r
+            out.writeShort(_wStyle);\r
+            out.writeShort(_cLine);\r
+            out.writeShort(_dxMin);\r
+            StringUtil.writeUnicodeString(out, _str);\r
+            if(_unused != null) out.writeByte(_unused);\r
+        }\r
+\r
+        public int getDataSize() {\r
+            int size = 6;\r
+            size += StringUtil.getEncodedSize(_str);\r
+            size += _unused;\r
+            return size;\r
+        }\r
+\r
+        @Override\r
+        public String toString(){\r
+            StringBuffer sb = new StringBuffer();\r
+            sb.append("[LbsDropData]\n");\r
+            sb.append("  ._wStyle:  ").append(_wStyle).append('\n');\r
+            sb.append("  ._cLine:  ").append(_cLine).append('\n');\r
+            sb.append("  ._dxMin:  ").append(_dxMin).append('\n');\r
+            sb.append("  ._str:  ").append(_str).append('\n');\r
+            if(_unused != null) sb.append("  ._unused:  ").append(_unused).append('\n');\r
+            sb.append("[/LbsDropData]\n");\r
+\r
+            return sb.toString();\r
+        }\r
+    }\r
+}\r
index 32231d9ae5b4b4ccd6aff831c6d97e111b70be6f..cf696bf28226b2ac6c06d2f33cb24a4fb20f9c4b 100644 (file)
@@ -78,20 +78,24 @@ public final class ObjRecord extends Record {
                        subrecords = null;
                        return;
                }
-               if (subRecordData.length % 2 != 0) {
+
+        //YK: files produced by OO violate the condition below
+        /*
+        if (subRecordData.length % 2 != 0) {
                        String msg = "Unexpected length of subRecordData : " + HexDump.toHex(subRecordData);
                        throw new RecordFormatException(msg);
                }
-
-//             System.out.println(HexDump.toHex(subRecordData));
+        */
 
                subrecords = new ArrayList<SubRecord>();
                ByteArrayInputStream bais = new ByteArrayInputStream(subRecordData);
                LittleEndianInputStream subRecStream = new LittleEndianInputStream(bais);
-               while (true) {
-                       SubRecord subRecord = SubRecord.createSubRecord(subRecStream);
+               CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord)SubRecord.createSubRecord(subRecStream, 0);
+        subrecords.add(cmo);
+        while (true) {
+                       SubRecord subRecord = SubRecord.createSubRecord(subRecStream, cmo.getObjectType());
                        subrecords.add(subRecord);
-                       if (subRecord instanceof EndSubRecord) {
+                       if (subRecord.isTerminating()) {
                                break;
                        }
                }
@@ -107,7 +111,8 @@ public final class ObjRecord extends Record {
                                }
                                _isPaddedToQuadByteMultiple = false;
                        }
-               } else {
+
+        } else {
                        _isPaddedToQuadByteMultiple = false;
                }
                _uninterpretedData = null;
@@ -123,11 +128,7 @@ public final class ObjRecord extends Record {
         * to the its proper size.  POI does the same.
         */
        private static boolean canPaddingBeDiscarded(byte[] data, int nRemainingBytes) {
-               if (data.length < 0x1FEE) {
-                       // this looks like a different problem
-                       return false;
-               }
-               // make sure none of the padding looks important
+        // make sure none of the padding looks important
                for(int i=data.length-nRemainingBytes; i<data.length; i++) {
                        if (data[i] != 0x00) {
                                return false;
index 108a847dbde07234a0627c80c2aa8b33a68aff79..59b01cf35dd75c4dbf594a797ec140b82eebf76b 100644 (file)
 
 package org.apache.poi.hssf.record;
 
-import java.io.ByteArrayOutputStream;
-
-import org.apache.poi.hssf.record.formula.Area3DPtg;
-import org.apache.poi.hssf.record.formula.AreaPtg;
-import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.hssf.record.formula.Ref3DPtg;
-import org.apache.poi.hssf.record.formula.RefPtg;
 import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndianByteArrayInputStream;
 import org.apache.poi.util.LittleEndianInput;
 import org.apache.poi.util.LittleEndianOutput;
 import org.apache.poi.util.LittleEndianOutputStream;
 
+import java.io.ByteArrayOutputStream;
+
 /**
  * Subrecords are part of the OBJ class.
  */
@@ -38,7 +32,15 @@ public abstract class SubRecord {
                // no fields to initialise
        }
 
-       public static SubRecord createSubRecord(LittleEndianInput in) {
+    /**
+     * read a sub-record from the supplied stream
+     *
+     * @param in    the stream to read from
+     * @param cmoOt the objectType field of the containing CommonObjectDataSubRecord,
+     *   we need it to propagate to next sub-records as it defines what data follows
+     * @return the created sub-record
+     */
+    public static SubRecord createSubRecord(LittleEndianInput in, int cmoOt) {
                int sid = in.readUShort();
                int secondUShort = in.readUShort(); // Often (but not always) the datasize for the sub-record
 
@@ -54,7 +56,7 @@ public abstract class SubRecord {
                        case NoteStructureSubRecord.sid:
                                return new NoteStructureSubRecord(in, secondUShort);
                        case LbsDataSubRecord.sid:
-                               return new LbsDataSubRecord(in, secondUShort);
+                               return new LbsDataSubRecord(in, secondUShort, cmoOt);
                }
                return new UnknownSubRecord(in, sid, secondUShort);
        }
@@ -78,8 +80,19 @@ public abstract class SubRecord {
        public abstract void serialize(LittleEndianOutput out);
        public abstract Object clone();
 
+    /**
+     * Wether this record terminates the sub-record stream.
+     * There are two cases when this method must be overridden and return <code>true</code>
+     *  - EndSubRecord (sid = 0x00)
+     *  - LbsDataSubRecord (sid = 0x12)
+     *
+     * @return whether this record is the last in the sub-record stream
+     */
+    public boolean isTerminating(){
+        return false;
+    }
 
-       private static final class UnknownSubRecord extends SubRecord {
+    private static final class UnknownSubRecord extends SubRecord {
 
                private final int _sid;
                private final byte[] _data;
@@ -111,140 +124,4 @@ public abstract class SubRecord {
                        return sb.toString();
                }
        }
-
-       // TODO make into a top level class
-       // perhaps all SubRecord sublcasses could go to their own package
-       private static final class LbsDataSubRecord extends SubRecord {
-
-               public static final int sid = 0x0013;
-
-               private int _unknownShort1;
-               private int _unknownInt4;
-               private Ptg _linkPtg;
-               private Byte _unknownByte6;
-               private int _nEntryCount;
-               private int _selectedEntryIndex;
-               private int _style;
-               private int _unknownShort10;
-               private int _comboStyle;
-               private int _lineCount;
-               private int _unknownShort13;
-
-               public LbsDataSubRecord(LittleEndianInput in, int unknownShort1) {
-                       _unknownShort1 = unknownShort1;
-                       int linkSize = in.readUShort();
-                       if (linkSize > 0) {
-                               int formulaSize = in.readUShort();
-                               _unknownInt4 = in.readInt();
-
-
-                               byte[] buf = new byte[formulaSize];
-                               in.readFully(buf);
-                               _linkPtg = readRefPtg(buf);
-                               switch (linkSize - formulaSize - 6) {
-                                       case 1:
-                                               _unknownByte6 = Byte.valueOf(in.readByte());
-                                               break;
-                                       case 0:
-                                               _unknownByte6 = null;
-                                               break;
-                                       default:
-                                               throw new RecordFormatException("Unexpected leftover bytes");
-                               }
-
-                       } else {
-                               _unknownInt4 = 0;
-                               _linkPtg = null;
-                               _unknownByte6 = null;
-                       }
-                       _nEntryCount = in.readUShort();
-                       _selectedEntryIndex = in.readUShort();
-                       _style = in.readUShort();
-                       _unknownShort10 = in.readUShort();
-                       _comboStyle = in.readUShort();
-                       _lineCount = in.readUShort();
-                       _unknownShort13 = in.readUShort();
-
-               }
-               protected int getDataSize() {
-                       int result = 2; // 2 initial shorts
-
-                       // optional link formula
-                       if (_linkPtg != null) {
-                               result += 2; // encoded Ptg size
-                               result += 4; // unknown int
-                               result += _linkPtg.getSize();
-                               if (_unknownByte6 != null) {
-                                       result += 1;
-                               }
-                       }
-                       result += 7 * 2; // 7 shorts
-                       return result;
-               }
-               public void serialize(LittleEndianOutput out) {
-                       out.writeShort(sid);
-                       out.writeShort(_unknownShort1); // note - this is *not* the size
-                       if (_linkPtg == null) {
-                               out.writeShort(0);
-                       } else {
-                               int formulaSize = _linkPtg.getSize();
-                               int linkSize = formulaSize + 6;
-                               if (_unknownByte6 != null) {
-                                       linkSize++;
-                               }
-                               out.writeShort(linkSize);
-                               out.writeShort(formulaSize);
-                               out.writeInt(_unknownInt4);
-                               _linkPtg.write(out);
-                               if (_unknownByte6 != null) {
-                                       out.writeByte(_unknownByte6.intValue());
-                               }
-                       }
-                       out.writeShort(_nEntryCount);
-                       out.writeShort(_selectedEntryIndex);
-                       out.writeShort(_style);
-                       out.writeShort(_unknownShort10);
-                       out.writeShort(_comboStyle);
-                       out.writeShort(_lineCount);
-                       out.writeShort(_unknownShort13);
-               }
-               private static Ptg readRefPtg(byte[] formulaRawBytes) {
-                       LittleEndianInput in = new LittleEndianByteArrayInputStream(formulaRawBytes);
-                       byte ptgSid = in.readByte();
-                       switch(ptgSid) {
-                               case AreaPtg.sid:   return new AreaPtg(in);
-                               case Area3DPtg.sid: return new Area3DPtg(in);
-                               case RefPtg.sid:    return new RefPtg(in);
-                               case Ref3DPtg.sid:  return new Ref3DPtg(in);
-                       }
-                       return null;
-               }
-               public Object clone() {
-                       return this;
-               }
-               public String toString() {
-                       StringBuffer sb = new StringBuffer(256);
-
-                       sb.append("[ftLbsData]\n");
-                       sb.append("    .unknownShort1 =").append(HexDump.shortToHex(_unknownShort1)).append("\n");
-                       if (_linkPtg == null) {
-                               sb.append("  <no link formula>\n");
-                       } else {
-                               sb.append("    .unknownInt4   =").append(HexDump.intToHex(_unknownInt4)).append("\n");
-                               sb.append("    .linkPtg       =").append(_linkPtg.toFormulaString()).append(" (").append(_linkPtg.getRVAType()).append(")").append("\n");
-                               if (_unknownByte6 != null) {
-                                       sb.append("    .unknownByte6  =").append(HexDump.byteToHex(_unknownByte6.byteValue())).append("\n");
-                               }
-                       }
-                       sb.append("    .nEntryCount   =").append(HexDump.shortToHex(_nEntryCount)).append("\n");
-                       sb.append("    .selEntryIx    =").append(HexDump.shortToHex(_selectedEntryIndex)).append("\n");
-                       sb.append("    .style         =").append(HexDump.shortToHex(_style)).append("\n");
-                       sb.append("    .unknownShort10=").append(HexDump.shortToHex(_unknownShort10)).append("\n");
-                       sb.append("    .comboStyle    =").append(HexDump.shortToHex(_comboStyle)).append("\n");
-                       sb.append("    .lineCount     =").append(HexDump.shortToHex(_lineCount)).append("\n");
-                       sb.append("    .unknownShort13=").append(HexDump.shortToHex(_unknownShort13)).append("\n");
-                       sb.append("[/ftLbsData]\n");
-                       return sb.toString();
-               }
-       }
 }
diff --git a/src/testcases/org/apache/poi/hssf/record/TestLbsDataSubRecord.java b/src/testcases/org/apache/poi/hssf/record/TestLbsDataSubRecord.java
new file mode 100644 (file)
index 0000000..e0b4eb1
--- /dev/null
@@ -0,0 +1,170 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hssf.record;
+
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import org.apache.poi.util.HexRead;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.LittleEndianOutputStream;
+import org.apache.poi.hssf.record.formula.Ptg;
+import org.apache.poi.hssf.record.formula.AreaPtg;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+/**
+ * Tests the serialization and deserialization of the LbsDataSubRecord class works correctly.
+ *
+ * @author Yegor Kozlov
+ */
+public final class TestLbsDataSubRecord extends TestCase {
+
+    /**
+     * test read-write round trip
+     * test data was taken from 47701.xls
+     */
+    public void test_47701(){
+        byte[] data = HexRead.readFromString(
+                        "15, 00, 12, 00, 12, 00, 02, 00, 11, 20, " +
+                        "00, 00, 00, 00, 80, 3D, 03, 05, 00, 00, " +
+                        "00, 00, 0C, 00, 14, 00, 00, 00, 00, 00, " +
+                        "00, 00, 00, 00, 00, 00, 01, 00, 0A, 00, " +
+                        "00, 00, 10, 00, 01, 00, 13, 00, EE, 1F, " +
+                        "10, 00, 09, 00, 40, 9F, 74, 01, 25, 09, " +
+                        "00, 0C, 00, 07, 00, 07, 00, 07, 04, 00, " +
+                        "00, 00, 08, 00, 00, 00");
+        RecordInputStream in = TestcaseRecordInputStream.create(ObjRecord.sid, data);
+        // check read OK
+        ObjRecord record = new ObjRecord(in);
+        assertEquals(3, record.getSubRecords().size());
+        SubRecord sr = record.getSubRecords().get(2);
+        assertTrue(sr instanceof LbsDataSubRecord);
+        LbsDataSubRecord lbs = (LbsDataSubRecord)sr;
+        assertEquals(4, lbs.getNumberOfItems());
+
+        assertTrue(lbs.getFormula() instanceof AreaPtg);
+        AreaPtg ptg = (AreaPtg)lbs.getFormula();
+        CellRangeAddress range = new CellRangeAddress(
+                ptg.getFirstRow(), ptg.getLastRow(), ptg.getFirstColumn(), ptg.getLastColumn());
+        assertEquals("H10:H13", range.formatAsString());
+
+        // check that it re-serializes to the same data
+        byte[] ser = record.serialize();
+        TestcaseRecordInputStream.confirmRecordEncoding(ObjRecord.sid, data, ser);
+    }
+
+    /**
+     * test data was taken from the file attached to Bugzilla 45778
+     */
+    public void test_45778(){
+        byte[] data = HexRead.readFromString(
+                                "15, 00, 12, 00, 14, 00, 01, 00, 01, 00, " +
+                                "01, 21, 00, 00, 3C, 13, F4, 03, 00, 00, " +
+                                "00, 00, 0C, 00, 14, 00, 00, 00, 00, 00, " +
+                                "00, 00, 00, 00, 00, 00, 01, 00, 08, 00, 00, " +
+                                "00, 10, 00, 00, 00, " +
+                                 "13, 00, EE, 1F, " +
+                                 "00, 00, " +
+                                 "08, 00, " +  //number of items
+                                 "08, 00, " + //selected item
+                                 "01, 03, " + //flags
+                                 "00, 00, " + //objId
+                                 //LbsDropData
+                                 "0A, 00, " + //flags
+                                 "14, 00, " + //the number of lines to be displayed in the dropdown
+                                 "6C, 00, " + //the smallest width in pixels allowed for the dropdown window
+                                 "00, 00, " +  //num chars
+                                 "00, 00");
+        RecordInputStream in = TestcaseRecordInputStream.create(ObjRecord.sid, data);
+        // check read OK
+        ObjRecord record = new ObjRecord(in);
+
+        SubRecord sr = record.getSubRecords().get(2);
+        assertTrue(sr instanceof LbsDataSubRecord);
+        LbsDataSubRecord lbs = (LbsDataSubRecord)sr;
+        assertEquals(8, lbs.getNumberOfItems());
+        assertNull(lbs.getFormula());
+
+        // check that it re-serializes to the same data
+        byte[] ser = record.serialize();
+        TestcaseRecordInputStream.confirmRecordEncoding(ObjRecord.sid, data, ser);
+
+    }
+
+    /**
+     * Test data produced by OpenOffice 3.1 by opening  and saving 47701.xls
+     * There are 5 padding bytes that are removed by POI
+     */
+    public void test_remove_padding(){
+        byte[] data = HexRead.readFromString(
+                        "5D, 00, 4C, 00, " +
+                        "15, 00, 12, 00, 12, 00, 01, 00, 11, 00, " +
+                        "00, 00, 00, 00, 00, 00, 00, 00, 00, 00, " +
+                        "00, 00, 0C, 00, 14, 00, 00, 00, 00, 00, " +
+                        "00, 00, 00, 00, 00, 00, 01, 00, 09, 00, " +
+                        "00, 00, 0F, 00, 01, 00, " +
+                        "13, 00, 1B, 00, " +
+                        "10, 00, " + //next 16 bytes is a ptg aray
+                        "09, 00, 00, 00, 00, 00, 25, 09, 00, 0C, 00, 07, 00, 07, 00, 00, " +
+                        "01, 00, " + //num lines
+                        "00, 00, " + //selected
+                         "08, 00, " +
+                         "00, 00, " +
+                         "00, 00, 00, 00, 00"); //padding bytes
+
+        RecordInputStream in = TestcaseRecordInputStream.create(data);
+        // check read OK
+        ObjRecord record = new ObjRecord(in);
+        // check that it re-serializes to the same data
+        byte[] ser = record.serialize();
+
+        assertEquals(data.length-5, ser.length);
+        for(int i=0; i < ser.length; i++) assertEquals(data[i],ser[i]);
+
+        //check we can read the trimmed record
+        RecordInputStream in2 = TestcaseRecordInputStream.create(ser);
+        ObjRecord record2 = new ObjRecord(in2);
+        byte[] ser2 = record2.serialize();
+        assertTrue(Arrays.equals(ser, ser2));
+    }
+
+    public void test_LbsDropData(){
+        byte[] data = HexRead.readFromString(
+                                 //LbsDropData
+                                 "0A, 00, " + //flags
+                                 "14, 00, " + //the number of lines to be displayed in the dropdown
+                                 "6C, 00, " + //the smallest width in pixels allowed for the dropdown window
+                                 "00, 00, " +  //num chars
+                                 "00, " +      //compression flag
+                                 "00");        //padding byte
+
+        LittleEndianInputStream in = new LittleEndianInputStream(new ByteArrayInputStream(data));
+
+        LbsDataSubRecord.LbsDropData lbs = new LbsDataSubRecord.LbsDropData(in);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        lbs.serialize(new LittleEndianOutputStream(baos));
+
+        assertTrue(Arrays.equals(data, baos.toByteArray()));
+    }
+}
\ No newline at end of file
index 5e15be84056f863e22ee8d977faf5d67c0888f69..5a75e91484d95e77f4e7a9ff169f2428ac35c27f 100644 (file)
@@ -1522,4 +1522,12 @@ public final class TestBugs extends BaseTestBugzillaIssues {
         HSSFCell cell2 = s.getRow(0).getCell(1);
         assertEquals(1.0, cell2.getNumericCellValue());
     }
+
+    /**
+     * POI 3.5 beta 7 can not read excel file contain list box (Form Control)  
+     */
+    public void test47701() {
+        openSample("47701.xls");
+    }
+    
 }
diff --git a/test-data/spreadsheet/47701.xls b/test-data/spreadsheet/47701.xls
new file mode 100644 (file)
index 0000000..f6cf8a2
Binary files /dev/null and b/test-data/spreadsheet/47701.xls differ