fixed PageSettingsBlock to allow multiple HeaderFooterRecord records, see Bugzilla 48026

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@892467 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2009-12-19 11:37:45 +00:00
parent f57d1d8a39
commit 13e963bc8c
11 changed files with 423 additions and 66 deletions

View 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>

View 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;
}

View File

@ -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();
}
}

View 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,

View 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";

View File

@ -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();
}
}

View File

@ -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();
}
}

View 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(

View 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);
}
}

View 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();
}
}
}
});
}
}
}
}
}

View 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()));
}
}