]> source.dussan.org Git - poi.git/commitdiff
fixed PageSettingsBlock to allow multiple HeaderFooterRecord records, see Bugzilla...
authorYegor Kozlov <yegor@apache.org>
Sat, 19 Dec 2009 11:37:45 +0000 (11:37 +0000)
committerYegor Kozlov <yegor@apache.org>
Sat, 19 Dec 2009 11:37:45 +0000 (11:37 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@892467 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/model/Sheet.java
src/java/org/apache/poi/hssf/record/HeaderFooterRecord.java [new file with mode: 0755]
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/record/UnknownRecord.java
src/java/org/apache/poi/hssf/record/UserSViewBegin.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/UserSViewEnd.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/aggregates/ChartSubstreamRecordAggregate.java
src/java/org/apache/poi/hssf/record/aggregates/CustomViewSettingsRecordAggregate.java
src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java
src/testcases/org/apache/poi/hssf/record/aggregates/TestPageSettingsBlock.java

index acfb9b51a670f43baa7e73467fdf5306a08b60ed..2a1ce7d6585181a423fb4ebec50ae1816337bfd4 100644 (file)
@@ -34,6 +34,9 @@
 
     <changes>
         <release version="3.7-SNAPSHOT" date="2010-??-??">
+           <action dev="POI-DEVELOPERS" type="add">added Ant target to install artifacts in local repository </action>
+           <action dev="POI-DEVELOPERS" type="fix">48026 - fixed PageSettingsBlock to allow multiple HeaderFooterRecord records </action>
+           <action dev="POI-DEVELOPERS" type="fix">48202 - fixed CellRangeUtil.mergeCellRanges to work for adjacent cell regions </action>
            <action dev="POI-DEVELOPERS" type="fix">48339 - fixed ExternalNameRecord to properly distinguish DDE data from OLE data items </action>
            <action dev="POI-DEVELOPERS" type="fix">47920 - allow editing workbooks embedded into PowerPoint files</action>
            <action dev="POI-DEVELOPERS" type="add">48343 - added implementation of SUBTOTAL function</action>
index df8d616d898d2d972e79ce508371c6788bf0e328..cc19ae1cd2af22b0bc44327979ce13468a2a435b 100644 (file)
@@ -221,6 +221,9 @@ public final class Sheet implements Model {
                     // one or more PSB records found after some intervening non-PSB records
                     _psBlock.addLateRecords(rs);
                 }
+                // YK: in some cases records can be moved to the preceding
+                // CustomViewSettingsRecordAggregate blocks
+                _psBlock.positionRecords(records);
                 continue;
             }
 
diff --git a/src/java/org/apache/poi/hssf/record/HeaderFooterRecord.java b/src/java/org/apache/poi/hssf/record/HeaderFooterRecord.java
new file mode 100755 (executable)
index 0000000..23d266f
--- /dev/null
@@ -0,0 +1,98 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianOutput;
+
+import java.util.Arrays;
+
+/**
+ * The HEADERFOOTER record stores information added in Office Excel 2007 for headers/footers.
+ *
+ * @author Yegor Kozlov
+ */
+public final class HeaderFooterRecord extends StandardRecord {
+
+    private static final byte[] BLANK_GUID = new byte[16];
+
+    public final static short sid = 0x089C;
+       private byte[] _rawData;
+
+    public HeaderFooterRecord(byte[] data) {
+        _rawData = data;
+    }
+
+       /**
+        * construct a HeaderFooterRecord record.  No fields are interpreted and the record will
+        * be serialized in its original form more or less
+        * @param in the RecordInputstream to read the record from
+        */
+       public HeaderFooterRecord(RecordInputStream in) {
+               _rawData = in.readRemainder();
+       }
+
+       /**
+        * spit the record out AS IS. no interpretation or identification
+        */
+       public void serialize(LittleEndianOutput out) {
+               out.write(_rawData);
+       }
+
+       protected int getDataSize() {
+               return _rawData.length;
+       }
+    
+    public short getSid()
+    {
+        return sid;
+    }
+
+    /**
+     * If this header belongs to a specific sheet view , the sheet view?s GUID will be saved here.
+     * <p>
+     * If it is zero, it means the current sheet. Otherwise, this field MUST match the guid field
+     * of the preceding {@link UserSViewBegin} record.
+     *
+     * @return the sheet view?s GUID
+     */
+    public byte[] getGuid(){
+        byte[] guid = new byte[16];
+        System.arraycopy(_rawData, 12, guid, 0, guid.length);
+        return guid;
+    }
+
+    /**
+     * @return whether this record belongs to the current sheet 
+     */
+    public boolean isCurrentSheet(){
+        return Arrays.equals(getGuid(), BLANK_GUID);
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("[").append("HEADERFOOTER").append("] (0x");
+        sb.append(Integer.toHexString(sid).toUpperCase() + ")\n");
+        sb.append("  rawData=").append(HexDump.toHex(_rawData)).append("\n");
+        sb.append("[/").append("HEADERFOOTER").append("]\n");
+        return sb.toString();
+    }
+    
+}
\ No newline at end of file
index 85f3a082e4f6d73b716b6d85d22cec39131cc532..86f947b92df5a2746bc7d36aa326f05ed24433f7 100644 (file)
@@ -126,6 +126,7 @@ public final class RecordFactory {
                GutsRecord.class,
                HCenterRecord.class,
                HeaderRecord.class,
+        HeaderFooterRecord.class,
                HideObjRecord.class,
                HorizontalPageBreakRecord.class,
                HyperlinkRecord.class,
@@ -179,6 +180,8 @@ public final class RecordFactory {
                TopMarginRecord.class,
                UncalcedRecord.class,
                UseSelFSRecord.class,
+        UserSViewBegin.class,
+        UserSViewEnd.class,
                VCenterRecord.class,
                VerticalPageBreakRecord.class,
                WindowOneRecord.class,
index 750cc5dab5053680f543f5367a90dffbeef56934..1351b7be59be0dc98fd6645f857109a5c88fb57f 100644 (file)
@@ -53,8 +53,6 @@ public final class UnknownRecord extends StandardRecord {
        public static final int BITMAP_00E9          = 0x00E9;
        public static final int PHONETICPR_00EF      = 0x00EF;
        public static final int LABELRANGES_015F     = 0x015F;
-       public static final int USERSVIEWBEGIN_01AA  = 0x01AA;
-       public static final int USERSVIEWEND_01AB    = 0x01AB;
        public static final int QUICKTIP_0800        = 0x0800;
        public static final int SHEETEXT_0862        = 0x0862; // OOO calls this SHEETLAYOUT
        public static final int SHEETPROTECTION_0867 = 0x0867;
@@ -160,8 +158,6 @@ public final class UnknownRecord extends StandardRecord {
                        case LABELRANGES_015F: return "LABELRANGES";
                        case 0x01BA: return "CODENAME";
                        case 0x01A9: return "USERBVIEW";
-                       case USERSVIEWBEGIN_01AA: return "USERSVIEWBEGIN";
-                       case USERSVIEWEND_01AB: return "USERSVIEWEND";
                        case 0x01AD: return "QSI";
 
                        case 0x01C0: return "EXCEL9FILE";
diff --git a/src/java/org/apache/poi/hssf/record/UserSViewBegin.java b/src/java/org/apache/poi/hssf/record/UserSViewBegin.java
new file mode 100644 (file)
index 0000000..ad59a58
--- /dev/null
@@ -0,0 +1,87 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianOutput;
+
+import java.util.Arrays;
+
+/**
+ * The UserSViewBegin record specifies settings for a custom view associated with the sheet.
+ * This record also marks the start of custom view records, which save custom view settings.
+ * Records between {@link UserSViewBegin} and {@link UserSViewEnd} contain settings for the custom view,
+ * not settings for the sheet itself.
+ *
+ * @author Yegor Kozlov
+ */
+public final class UserSViewBegin extends StandardRecord {
+
+    public final static short sid = 0x01AA;
+       private byte[] _rawData;
+
+    public UserSViewBegin(byte[] data) {
+        _rawData = data;
+    }
+
+       /**
+        * construct an UserSViewBegin record.  No fields are interpreted and the record will
+        * be serialized in its original form more or less
+        * @param in the RecordInputstream to read the record from
+        */
+       public UserSViewBegin(RecordInputStream in) {
+               _rawData = in.readRemainder();
+       }
+
+       /**
+        * spit the record out AS IS. no interpretation or identification
+        */
+       public void serialize(LittleEndianOutput out) {
+               out.write(_rawData);
+       }
+
+       protected int getDataSize() {
+               return _rawData.length;
+       }
+
+    public short getSid()
+    {
+        return sid;
+    }
+
+    /**
+     * @return Globally unique identifier for the custom view
+     */
+    public byte[] getGuid(){
+        byte[] guid = new byte[16];
+        System.arraycopy(_rawData, 0, guid, 0, guid.length);
+        return guid;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("[").append("USERSVIEWBEGIN").append("] (0x");
+        sb.append(Integer.toHexString(sid).toUpperCase() + ")\n");
+        sb.append("  rawData=").append(HexDump.toHex(_rawData)).append("\n");
+        sb.append("[/").append("USERSVIEWBEGIN").append("]\n");
+        return sb.toString();
+    }
+    
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/record/UserSViewEnd.java b/src/java/org/apache/poi/hssf/record/UserSViewEnd.java
new file mode 100644 (file)
index 0000000..271dd77
--- /dev/null
@@ -0,0 +1,75 @@
+/* ====================================================================
+   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 org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndianOutput;
+
+import java.util.Arrays;
+
+/**
+ * The UserSViewEnd record marks the end of the settings for a custom view associated with the sheet
+ *
+ * @author Yegor Kozlov
+ */
+public final class UserSViewEnd extends StandardRecord {
+
+    public final static short sid = 0x01AB;
+       private byte[] _rawData;
+
+    public UserSViewEnd(byte[] data) {
+        _rawData = data;
+    }
+
+       /**
+        * construct an UserSViewEnd record.  No fields are interpreted and the record will
+        * be serialized in its original form more or less
+        * @param in the RecordInputstream to read the record from
+        */
+       public UserSViewEnd(RecordInputStream in) {
+               _rawData = in.readRemainder();
+       }
+
+       /**
+        * spit the record out AS IS. no interpretation or identification
+        */
+       public void serialize(LittleEndianOutput out) {
+               out.write(_rawData);
+       }
+
+       protected int getDataSize() {
+               return _rawData.length;
+       }
+
+    public short getSid()
+    {
+        return sid;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("[").append("USERSVIEWEND").append("] (0x");
+        sb.append(Integer.toHexString(sid).toUpperCase() + ")\n");
+        sb.append("  rawData=").append(HexDump.toHex(_rawData)).append("\n");
+        sb.append("[/").append("USERSVIEWEND").append("]\n");
+        return sb.toString();
+    }
+
+}
\ No newline at end of file
index a891488b9fe7e73c0c5c08f68bb31327c03c5f02..dc7320993397dcf0f92db806984c5b34e5d89dd8 100644 (file)
@@ -21,11 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.poi.hssf.model.RecordStream;
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordBase;
-import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.*;
 
 /**
  * Manages the all the records associated with a chart sub-stream.<br/>
@@ -48,9 +44,9 @@ public final class ChartSubstreamRecordAggregate extends RecordAggregate {
                while (rs.peekNextClass() != EOFRecord.class) {
                        if (PageSettingsBlock.isComponentRecord(rs.peekNextSid())) {
                                if (_psBlock != null) {
-                                       if (rs.peekNextSid() == UnknownRecord.HEADER_FOOTER_089C) {
+                                       if (rs.peekNextSid() == HeaderFooterRecord.sid) {
                                                // test samples: 45538_classic_Footer.xls, 45538_classic_Header.xls
-                                               _psBlock.addLateHeaderFooter(rs.getNext());
+                                               _psBlock.addLateHeaderFooter((HeaderFooterRecord)rs.getNext());
                                                continue;
                                        }
                                        throw new IllegalStateException(
index de1a2fe2a5b6255adfa47596d1fe42b172a5a052..bc6a737e63b57faec3440a7de9c941b5581ccca2 100644 (file)
@@ -21,9 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.poi.hssf.model.RecordStream;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordBase;
-import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.*;
 
 /**
  * Manages the all the records associated with a 'Custom View Settings' sub-stream.<br/>
@@ -43,11 +41,11 @@ public final class CustomViewSettingsRecordAggregate extends RecordAggregate {
 
        public CustomViewSettingsRecordAggregate(RecordStream rs) {
                _begin = rs.getNext();
-               if (_begin.getSid() != UnknownRecord.USERSVIEWBEGIN_01AA) {
+               if (_begin.getSid() != UserSViewBegin.sid) {
                        throw new IllegalStateException("Bad begin record");
                }
                List<RecordBase> temp = new ArrayList<RecordBase>();
-               while (rs.peekNextSid() != UnknownRecord.USERSVIEWEND_01AB) {
+               while (rs.peekNextSid() != UserSViewEnd.sid) {
                        if (PageSettingsBlock.isComponentRecord(rs.peekNextSid())) {
                                if (_psBlock != null) {
                                        throw new IllegalStateException(
@@ -61,7 +59,7 @@ public final class CustomViewSettingsRecordAggregate extends RecordAggregate {
                }
                _recs = temp;
                _end = rs.getNext(); // no need to save EOF in field
-               if (_end.getSid() != UnknownRecord.USERSVIEWEND_01AB) {
+               if (_end.getSid() != UserSViewEnd.sid) {
                        throw new IllegalStateException("Bad custom view settings end record");
                }
        }
@@ -83,6 +81,10 @@ public final class CustomViewSettingsRecordAggregate extends RecordAggregate {
        }
 
        public static boolean isBeginRecord(int sid) {
-               return sid == UnknownRecord.USERSVIEWBEGIN_01AA;
+               return sid == UserSViewBegin.sid;
        }
+
+    public void append(RecordBase r){
+        _recs.add(r);    
+    }
 }
index 097eff1120741b93534f4c2f8bc5ebcff274994b..79ed333ce452fc7c2fb93838258cc78e49afd8e9 100644 (file)
@@ -20,26 +20,11 @@ package org.apache.poi.hssf.record.aggregates;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Arrays;
 
 import org.apache.poi.hssf.model.RecordStream;
 import org.apache.poi.hssf.model.Sheet;
-import org.apache.poi.hssf.record.BottomMarginRecord;
-import org.apache.poi.hssf.record.ContinueRecord;
-import org.apache.poi.hssf.record.FooterRecord;
-import org.apache.poi.hssf.record.HCenterRecord;
-import org.apache.poi.hssf.record.HeaderRecord;
-import org.apache.poi.hssf.record.HorizontalPageBreakRecord;
-import org.apache.poi.hssf.record.LeftMarginRecord;
-import org.apache.poi.hssf.record.Margin;
-import org.apache.poi.hssf.record.PageBreakRecord;
-import org.apache.poi.hssf.record.PrintSetupRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordFormatException;
-import org.apache.poi.hssf.record.RightMarginRecord;
-import org.apache.poi.hssf.record.TopMarginRecord;
-import org.apache.poi.hssf.record.UnknownRecord;
-import org.apache.poi.hssf.record.VCenterRecord;
-import org.apache.poi.hssf.record.VerticalPageBreakRecord;
+import org.apache.poi.hssf.record.*;
 
 /**
  * Groups the page settings records for a worksheet.<p/>
@@ -102,8 +87,14 @@ public final class PageSettingsBlock extends RecordAggregate {
        private final List<PLSAggregate> _plsRecords;
        private PrintSetupRecord _printSetup;
        private Record _bitmap;
-       private Record _headerFooter;
-       private Record _printSize;
+       private HeaderFooterRecord _headerFooter;
+    /**
+     * HeaderFooterRecord records belonging to preceding CustomViewSettingsRecordAggregates.
+     * The indicator of such records is a non-zero GUID,
+     *  see {@link  org.apache.poi.hssf.record.HeaderFooterRecord#getGuid()}
+     */
+    private List<HeaderFooterRecord> _sviewHeaderFooters = new ArrayList<HeaderFooterRecord>();
+    private Record _printSize;
 
        public PageSettingsBlock(RecordStream rs) {
                _plsRecords = new ArrayList<PLSAggregate>();
@@ -126,7 +117,7 @@ public final class PageSettingsBlock extends RecordAggregate {
                _hCenter = createHCenter();
                _vCenter = createVCenter();
                _printSetup = createPrintSetup();
-       }
+    }
 
        /**
         * @return <code>true</code> if the specified Record sid is one belonging to the
@@ -148,7 +139,7 @@ public final class PageSettingsBlock extends RecordAggregate {
                        case PrintSetupRecord.sid:
                        case UnknownRecord.BITMAP_00E9:
                        case UnknownRecord.PRINTSIZE_0033:
-                       case UnknownRecord.HEADER_FOOTER_089C: // extra header/footer settings supported by Excel 2007
+                       case HeaderFooterRecord.sid: // extra header/footer settings supported by Excel 2007
                                return true;
                }
                return false;
@@ -211,10 +202,14 @@ public final class PageSettingsBlock extends RecordAggregate {
                                checkNotPresent(_printSize);
                                _printSize = rs.getNext();
                                break;
-                       case UnknownRecord.HEADER_FOOTER_089C:
-                               checkNotPresent(_headerFooter);
-                               _headerFooter = rs.getNext();
-                               break;
+                       case HeaderFooterRecord.sid:
+                               //there can be multiple HeaderFooterRecord records belonging to different sheet views
+                               HeaderFooterRecord hf = (HeaderFooterRecord)rs.getNext();
+                if(hf.isCurrentSheet()) _headerFooter = hf;
+                else {
+                    _sviewHeaderFooters.add(hf);
+                }
+                break;
                        default:
                                // all other record types are not part of the PageSettingsBlock
                                return false;
@@ -596,11 +591,11 @@ public final class PageSettingsBlock extends RecordAggregate {
         * HEADERFOOTER is new in 2007.  Some apps seem to have scattered this record long after
         * the {@link PageSettingsBlock} where it belongs.
         */
-       public void addLateHeaderFooter(Record rec) {
+       public void addLateHeaderFooter(HeaderFooterRecord rec) {
                if (_headerFooter != null) {
                        throw new IllegalStateException("This page settings block already has a header/footer record");
                }
-               if (rec.getSid() != UnknownRecord.HEADER_FOOTER_089C) {
+               if (rec.getSid() != HeaderFooterRecord.sid) {
                        throw new RecordFormatException("Unexpected header-footer record sid: 0x" + Integer.toHexString(rec.getSid()));
                }
                _headerFooter = rec;
@@ -641,4 +636,40 @@ public final class PageSettingsBlock extends RecordAggregate {
                        }
                }
        }
+
+    /**
+     * Some apps can define multiple HeaderFooterRecord records for a sheet.
+     * When saving such a file Excel 2007 re-positiones them according to the followig rules:
+     *  - take a HeaderFooterRecord and read 16-byte GUID at offset 12. If it is zero,
+     *    it means the current sheet and the given HeaderFooterRecord belongs to this PageSettingsBlock
+     *  - If GUID is not zero then search in preceding CustomViewSettingsRecordAggregates.
+     *    Compare first 16 bytes of UserSViewBegin with the HeaderFooterRecord's GUID. If match,
+     *    then append the HeaderFooterRecord to this CustomViewSettingsRecordAggregates
+     *
+     * @param sheetRecords the list of sheet records read so far
+     */
+    public void positionRecords(List<RecordBase> sheetRecords) {
+        // loop through HeaderFooterRecord records having not-empty GUID and match them with
+        // CustomViewSettingsRecordAggregate blocks having UserSViewBegin with the same GUID
+        for (final Iterator<HeaderFooterRecord> it = _sviewHeaderFooters.iterator(); it.hasNext(); ) {
+            final HeaderFooterRecord hf = it.next();
+            for (RecordBase rb : sheetRecords) {
+                if (rb instanceof CustomViewSettingsRecordAggregate) {
+                    final CustomViewSettingsRecordAggregate cv = (CustomViewSettingsRecordAggregate) rb;
+                    cv.visitContainedRecords(new RecordVisitor() {
+                        public void visitRecord(Record r) {
+                            if (r.getSid() == UserSViewBegin.sid) {
+                                byte[] guid1 = ((UserSViewBegin) r).getGuid();
+                                byte[] guid2 = hf.getGuid();
+                                if (Arrays.equals(guid1, guid2)) {
+                                    cv.append(hf);
+                                    it.remove();
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        }
+    }
 }
index e3a01b476278df613c0962cd137134f581e19cfa..94448af7ad21ed3ebdf5a7e569101972022de427 100644 (file)
@@ -25,21 +25,7 @@ import junit.framework.TestCase;
 import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.model.RecordStream;
 import org.apache.poi.hssf.model.Sheet;
-import org.apache.poi.hssf.record.BOFRecord;
-import org.apache.poi.hssf.record.BottomMarginRecord;
-import org.apache.poi.hssf.record.ContinueRecord;
-import org.apache.poi.hssf.record.DimensionsRecord;
-import org.apache.poi.hssf.record.EOFRecord;
-import org.apache.poi.hssf.record.FooterRecord;
-import org.apache.poi.hssf.record.HCenterRecord;
-import org.apache.poi.hssf.record.HeaderRecord;
-import org.apache.poi.hssf.record.IndexRecord;
-import org.apache.poi.hssf.record.NumberRecord;
-import org.apache.poi.hssf.record.Record;
-import org.apache.poi.hssf.record.RecordFormatException;
-import org.apache.poi.hssf.record.UnknownRecord;
-import org.apache.poi.hssf.record.VCenterRecord;
-import org.apache.poi.hssf.record.WindowTwoRecord;
+import org.apache.poi.hssf.record.*;
 import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
@@ -86,14 +72,14 @@ public final class TestPageSettingsBlock extends TestCase {
                                BOFRecord.createSheetBOF(),
                                new HeaderRecord("&LSales Figures"),
                                new FooterRecord("&LJanuary"),
-                               ur(UnknownRecord.HEADER_FOOTER_089C, "9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00"),
+                               new HeaderFooterRecord(HexRead.readFromString("9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00")),
                                new DimensionsRecord(),
                                new WindowTwoRecord(),
-                               ur(UnknownRecord.USERSVIEWBEGIN_01AA, "ED 77 3B 86 BC 3F 37 4C A9 58 60 23 43 68 54 4B 01 00 00 00 64 00 00 00 40 00 00 00 02 00 00 00 3D 80 04 00 00 00 00 00 00 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 3F FF FF 01 00"),
+                               new UserSViewBegin(HexRead.readFromString("ED 77 3B 86 BC 3F 37 4C A9 58 60 23 43 68 54 4B 01 00 00 00 64 00 00 00 40 00 00 00 02 00 00 00 3D 80 04 00 00 00 00 00 00 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 3F FF FF 01 00")),
                                new HeaderRecord("&LSales Figures"),
                                new FooterRecord("&LJanuary"),
-                               ur(UnknownRecord.HEADER_FOOTER_089C, "9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00"),
-                               ur(UnknownRecord.USERSVIEWEND_01AB, "01, 00"),
+                               new HeaderFooterRecord(HexRead.readFromString("9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00")),
+                               new UserSViewEnd(HexRead.readFromString("01, 00")),
 
                                EOFRecord.instance,
                };
@@ -132,7 +118,7 @@ public final class TestPageSettingsBlock extends TestCase {
                                new FooterRecord("&LJanuary"),
                                new DimensionsRecord(),
                                new WindowTwoRecord(),
-                               ur(UnknownRecord.HEADER_FOOTER_089C, "9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00"),
+                               new HeaderFooterRecord(HexRead.readFromString("9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 C4 60 00 00 00 00 00 00 00 00")),
                                EOFRecord.instance,
                };
                RecordStream rs = new RecordStream(Arrays.asList(recs), 0);
@@ -150,7 +136,7 @@ public final class TestPageSettingsBlock extends TestCase {
                assertEquals(IndexRecord.class, outRecs[1].getClass());
                assertEquals(HeaderRecord.class, outRecs[2].getClass());
                assertEquals(FooterRecord.class, outRecs[3].getClass());
-               assertEquals(UnknownRecord.HEADER_FOOTER_089C, outRecs[4].getSid());
+               assertEquals(HeaderFooterRecord.class, outRecs[4].getClass());
                assertEquals(DimensionsRecord.class, outRecs[5].getClass());
                assertEquals(WindowTwoRecord.class, outRecs[6].getClass());
                assertEquals(EOFRecord.instance, outRecs[7]);
@@ -315,4 +301,81 @@ public final class TestPageSettingsBlock extends TestCase {
                // records were assembled in standard order, so this simple check is OK
                assertTrue(Arrays.equals(recs, outRecs));
        }
+
+    public void testDuplicateHeaderFooter_bug48026() {
+
+        Record[] recs = {
+                BOFRecord.createSheetBOF(),
+                new IndexRecord(),
+
+                //PageSettingsBlock
+                new HeaderRecord("&LDecember"),
+                new FooterRecord("&LJanuary"),
+                new DimensionsRecord(),
+
+                new WindowTwoRecord(),
+
+                //CustomViewSettingsRecordAggregate
+                new UserSViewBegin(HexRead.readFromString("53 CE BD CC DE 38 44 45 97 C1 5C 89 F9 37 32 1B 01 00 00 00 64 00 00 00 40 00 00 00 03 00 00 00 7D 00 00 20 00 00 34 00 00 00 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF")),
+                new SelectionRecord(0, 0),
+                new UserSViewEnd(HexRead.readFromString("01 00")),
+
+                // two HeaderFooterRecord records, the first one has zero GUID (16 bytes at offset 12) and belongs to the PSB,
+                // the other is matched with a CustomViewSettingsRecordAggregate having UserSViewBegin with the same GUID
+                new HeaderFooterRecord(HexRead.readFromString("9C 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 33 00 00 00 00 00 00 00 00")),
+                new HeaderFooterRecord(HexRead.readFromString("9C 08 00 00 00 00 00 00 00 00 00 00 53 CE BD CC DE 38 44 45 97 C1 5C 89 F9 37 32 1B 34 33 00 00 00 00 00 00 00 00")),
+
+                EOFRecord.instance,
+        };
+        RecordStream rs = new RecordStream(Arrays.asList(recs), 0);
+        Sheet sheet;
+        try {
+            sheet = Sheet.createSheet(rs);
+        } catch (RuntimeException e) {
+            if (e.getMessage().equals("Duplicate PageSettingsBlock record (sid=0x89c)")) {
+                throw new AssertionFailedError("Identified bug 48026");
+            }
+            throw e;
+        }
+
+        RecordCollector rv = new RecordCollector();
+        sheet.visitContainedRecords(rv, 0);
+        Record[] outRecs = rv.getRecords();
+
+        assertEquals(recs.length, outRecs.length);
+        //expected order of records:
+        Record[] expectedRecs = {
+                recs[0],  //BOFRecord
+                recs[1],  //IndexRecord
+
+                //PageSettingsBlock
+                recs[2],  //HeaderRecord
+                recs[3],  //FooterRecord
+                recs[9],  //HeaderFooterRecord
+                recs[4],  // DimensionsRecord
+                recs[5],  // WindowTwoRecord
+
+                //CustomViewSettingsRecordAggregate
+                recs[6],  // UserSViewBegin
+                recs[7],  // SelectionRecord
+                recs[10], // HeaderFooterRecord
+                recs[8],  // UserSViewEnd
+
+                recs[11],  //EOFRecord
+        };
+        for(int i=0; i < expectedRecs.length; i++){
+            assertEquals("Record mismatch at index " + i,  expectedRecs[i].getClass(), outRecs[i].getClass());
+        }
+        HeaderFooterRecord hd1 = (HeaderFooterRecord)expectedRecs[4];
+        //GUID is zero
+        assertTrue(Arrays.equals(new byte[16], hd1.getGuid()));
+        assertTrue(hd1.isCurrentSheet());
+
+        UserSViewBegin svb = (UserSViewBegin)expectedRecs[7];
+        HeaderFooterRecord hd2 = (HeaderFooterRecord)expectedRecs[9];
+        assertFalse(hd2.isCurrentSheet());
+        //GUIDs of HeaderFooterRecord and UserSViewBegin must be the same
+        assertTrue(Arrays.equals(svb.getGuid(), hd2.getGuid()));
+    }
+
 }