Bugzilla 47199 - Fixed PageSettingsBlock/Sheet to tolerate margin records after other non-PSB records

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@780774 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-06-01 18:41:10 +00:00
parent 96e8585cff
commit bffe0432f6
4 changed files with 148 additions and 51 deletions

View File

@ -35,6 +35,7 @@
<release version="3.5-beta7" date="2009-??-??">
</release>
<release version="3.5-beta6" date="2009-06-11">
<action dev="POI-DEVELOPERS" type="fix">47199 - Fixed PageSettingsBlock/Sheet to tolerate margin records after other non-PSB records</action>
<action dev="POI-DEVELOPERS" type="fix">47069 - Fixed HSSFSheet#getFirstRowNum and HSSFSheet#getLastRowNum to return correct values after removal of all rows</action>
<action dev="POI-DEVELOPERS" type="fix">47278 - Fixed XSSFCell to avoid generating xsi:nil entries in shared string table</action>
<action dev="POI-DEVELOPERS" type="fix">47206 - Fixed XSSFCell to properly read inline strings</action>

View File

@ -57,7 +57,6 @@ import org.apache.poi.hssf.record.SaveRecalcRecord;
import org.apache.poi.hssf.record.ScenarioProtectRecord;
import org.apache.poi.hssf.record.SelectionRecord;
import org.apache.poi.hssf.record.UncalcedRecord;
import org.apache.poi.hssf.record.UnknownRecord;
import org.apache.poi.hssf.record.WSBoolRecord;
import org.apache.poi.hssf.record.WindowTwoRecord;
import org.apache.poi.hssf.record.aggregates.ChartSubstreamRecordAggregate;
@ -217,28 +216,13 @@ public final class Sheet implements Model {
if (PageSettingsBlock.isComponentRecord(recSid)) {
if (_psBlock == null) {
// typical case - just one PSB (so far)
// first PSB record encountered - read all of them:
_psBlock = new PageSettingsBlock(rs);
records.add(_psBlock);
continue;
} else {
// one or more PSB records found after some intervening non-PSB records
_psBlock.addLateRecords(rs);
}
if (recSid == UnknownRecord.HEADER_FOOTER_089C) {
// test samples: SharedFormulaTest.xls, ex44921-21902.xls, ex42570-20305.xls
_psBlock.addLateHeaderFooter(rs.getNext());
continue;
}
// Some apps write PLS, WSBOOL, <psb> but PLS is part of <psb>
// This happens in the test sample file "NoGutsRecords.xls" and "WORKBOOK_in_capitals.xls"
// In this case the first PSB is two records back
int prevPsbIx = records.size()-2;
if (_psBlock != records.get(prevPsbIx) || !(records.get(prevPsbIx+1) instanceof WSBoolRecord)) {
// not quite the expected situation
throw new RuntimeException("two Page Settings Blocks found in the same sheet");
}
records.remove(prevPsbIx); // WSBOOL will drop down one position.
PageSettingsBlock latePsb = new PageSettingsBlock(rs);
_psBlock = mergePSBs(_psBlock, latePsb);
records.add(_psBlock);
continue;
}
@ -372,34 +356,7 @@ public final class Sheet implements Model {
recs.add(r);
}});
}
/**
* Hack to recover from the situation where the page settings block has been split by
* an intervening {@link WSBoolRecord}
*/
private static PageSettingsBlock mergePSBs(PageSettingsBlock a, PageSettingsBlock b) {
List<Record> temp = new ArrayList<Record>();
RecordTransferrer rt = new RecordTransferrer(temp);
a.visitContainedRecords(rt);
b.visitContainedRecords(rt);
RecordStream rs = new RecordStream(temp, 0);
PageSettingsBlock result = new PageSettingsBlock(rs);
if (rs.hasNext()) {
throw new RuntimeException("PageSettingsBlocks did not merge properly");
}
return result;
}
private static final class RecordTransferrer implements RecordVisitor {
private final List<Record> _destList;
public RecordTransferrer(List<Record> destList) {
_destList = destList;
}
public void visitRecord(Record r) {
_destList.add(r);
}
}
private static final class RecordCloner implements RecordVisitor {
private final List<RecordBase> _destList;

View File

@ -125,36 +125,47 @@ public final class PageSettingsBlock extends RecordAggregate {
private boolean readARecord(RecordStream rs) {
switch (rs.peekNextSid()) {
case HorizontalPageBreakRecord.sid:
checkNotPresent(_rowBreaksRecord);
_rowBreaksRecord = (PageBreakRecord) rs.getNext();
break;
case VerticalPageBreakRecord.sid:
checkNotPresent(_columnBreaksRecord);
_columnBreaksRecord = (PageBreakRecord) rs.getNext();
break;
case HeaderRecord.sid:
checkNotPresent(_header);
_header = (HeaderRecord) rs.getNext();
break;
case FooterRecord.sid:
checkNotPresent(_footer);
_footer = (FooterRecord) rs.getNext();
break;
case HCenterRecord.sid:
checkNotPresent(_hCenter);
_hCenter = (HCenterRecord) rs.getNext();
break;
case VCenterRecord.sid:
checkNotPresent(_vCenter);
_vCenter = (VCenterRecord) rs.getNext();
break;
case LeftMarginRecord.sid:
checkNotPresent(_leftMargin);
_leftMargin = (LeftMarginRecord) rs.getNext();
break;
case RightMarginRecord.sid:
checkNotPresent(_rightMargin);
_rightMargin = (RightMarginRecord) rs.getNext();
break;
case TopMarginRecord.sid:
checkNotPresent(_topMargin);
_topMargin = (TopMarginRecord) rs.getNext();
break;
case BottomMarginRecord.sid:
checkNotPresent(_bottomMargin);
_bottomMargin = (BottomMarginRecord) rs.getNext();
break;
case UnknownRecord.PLS_004D:
checkNotPresent(_pls);
_pls = rs.getNext();
while (rs.peekNextSid()==ContinueRecord.sid) {
if (_plsContinues==null) {
@ -164,15 +175,19 @@ public final class PageSettingsBlock extends RecordAggregate {
}
break;
case PrintSetupRecord.sid:
checkNotPresent(_printSetup);
_printSetup = (PrintSetupRecord)rs.getNext();
break;
case UnknownRecord.BITMAP_00E9:
checkNotPresent(_bitmap);
_bitmap = rs.getNext();
break;
case UnknownRecord.PRINTSIZE_0033:
checkNotPresent(_printSize);
_printSize = rs.getNext();
break;
case UnknownRecord.HEADER_FOOTER_089C:
checkNotPresent(_headerFooter);
_headerFooter = rs.getNext();
break;
default:
@ -182,6 +197,13 @@ public final class PageSettingsBlock extends RecordAggregate {
return true;
}
private void checkNotPresent(Record rec) {
if (rec != null) {
throw new RecordFormatException("Duplicate PageSettingsBlock record (sid=0x"
+ Integer.toHexString(rec.getSid()) + ")");
}
}
private PageBreakRecord getRowBreaksRecord() {
if (_rowBreaksRecord == null) {
_rowBreaksRecord = new HorizontalPageBreakRecord();
@ -551,4 +573,40 @@ public final class PageSettingsBlock extends RecordAggregate {
}
_headerFooter = rec;
}
/**
* This method reads PageSettingsBlock records from the supplied RecordStream until the first
* non-PageSettingsBlock record is encountered. As each record is read, it is incorporated
* into this PageSettingsBlock.
* <p/>
* The latest Excel version seems to write the PageSettingsBlock uninterrupted. However there
* are several examples (that Excel reads OK) where these records are not written together:
* <ul>
* <li><b>HEADER_FOOTER(0x089C) after WINDOW2</b> - This record is new in 2007. Some apps
* seem to have scattered this record long after the PageSettingsBlock where it belongs
* test samples: SharedFormulaTest.xls, ex44921-21902.xls, ex42570-20305.xls</li>
* <li><b>PLS, WSBOOL, PageSettingsBlock</b> - WSBOOL is not a PSB record.
* This happens in the test sample file "NoGutsRecords.xls" and "WORKBOOK_in_capitals.xls"</li>
* <li><b>Margins after DIMENSION</b> - All of PSB should be before DIMENSION. (Bug-47199)</li>
* </ul>
* These were probably written by other applications (or earlier versions of Excel). It was
* decided to not write specific code for detecting each of these cases. POI now tolerates
* PageSettingsBlock records scattered all over the sheet record stream, and in any order, but
* does not allow duplicates of any of those records.
*
* <p/>
* <b>Note</b> - when POI writes out this PageSettingsBlock, the records will always be written
* in one consolidated block (in the standard ordering) regardless of how scattered the records
* were when they were originally read.
*
* @throws RecordFormatException if any PSB record encountered has the same type (sid) as
* a record that is already part of this PageSettingsBlock
*/
public void addLateRecords(RecordStream rs) {
while(true) {
if (!readARecord(rs)) {
break;
}
}
}
}

View File

@ -26,6 +26,7 @@ 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.DimensionsRecord;
import org.apache.poi.hssf.record.EOFRecord;
import org.apache.poi.hssf.record.FooterRecord;
@ -33,6 +34,7 @@ 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.WindowTwoRecord;
import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
@ -135,7 +137,7 @@ public final class TestPageSettingsBlock extends TestCase {
Sheet sheet = Sheet.createSheet(rs);
RecordCollector rv = new RecordCollector();
sheet.visitContainedRecords(rv, rowIx);
sheet.visitContainedRecords(rv, 0);
Record[] outRecs = rv.getRecords();
if (outRecs[4] == EOFRecord.instance) {
throw new AssertionFailedError("Identified bug 46953 - EOF incorrectly appended to PSB");
@ -151,6 +153,85 @@ public final class TestPageSettingsBlock extends TestCase {
assertEquals(WindowTwoRecord.class, outRecs[6].getClass());
assertEquals(EOFRecord.instance, outRecs[7]);
}
/**
* Bug 47199 was due to the margin records being located well after the initial PSB records.
* The example file supplied (attachment 23710) had three non-PSB record types
* between the PRINTSETUP record and first MARGIN record:
* <ul>
* <li>PRINTSETUP(0x00A1)</li>
* <li>DEFAULTCOLWIDTH(0x0055)</li>
* <li>COLINFO(0x007D)</li>
* <li>DIMENSIONS(0x0200)</li>
* <li>BottomMargin(0x0029)</li>
* </ul>
*/
public void testLateMargins_bug47199() {
Record[] recs = {
BOFRecord.createSheetBOF(),
new HeaderRecord("&LSales Figures"),
new FooterRecord("&LJanuary"),
new DimensionsRecord(),
createBottomMargin(0.787F),
new WindowTwoRecord(),
EOFRecord.instance,
};
RecordStream rs = new RecordStream(Arrays.asList(recs), 0);
Sheet sheet;
try {
sheet = Sheet.createSheet(rs);
} catch (RuntimeException e) {
if (e.getMessage().equals("two Page Settings Blocks found in the same sheet")) {
throw new AssertionFailedError("Identified bug 47199a - failed to process late margings records");
}
throw e;
}
RecordCollector rv = new RecordCollector();
sheet.visitContainedRecords(rv, 0);
Record[] outRecs = rv.getRecords();
assertEquals(recs.length+1, outRecs.length); // +1 for index record
assertEquals(BOFRecord.class, outRecs[0].getClass());
assertEquals(IndexRecord.class, outRecs[1].getClass());
assertEquals(HeaderRecord.class, outRecs[2].getClass());
assertEquals(FooterRecord.class, outRecs[3].getClass());
assertEquals(DimensionsRecord.class, outRecs[5].getClass());
assertEquals(WindowTwoRecord.class, outRecs[6].getClass());
assertEquals(EOFRecord.instance, outRecs[7]);
}
private Record createBottomMargin(float value) {
BottomMarginRecord result = new BottomMarginRecord();
result.setMargin(value);
return result;
}
/**
* The PageSettingsBlock should not allow multiple copies of the same record. This extra assertion
* was added while fixing bug 47199. All existing POI test samples comply with this requirement.
*/
public void testDuplicatePSBRecord_bug47199() {
// Hypothetical setup of PSB records which should cause POI to crash
Record[] recs = {
new HeaderRecord("&LSales Figures"),
new HeaderRecord("&LInventory"),
};
RecordStream rs = new RecordStream(Arrays.asList(recs), 0);
try {
new PageSettingsBlock(rs);
throw new AssertionFailedError("Identified bug 47199b - duplicate PSB records should not be allowed");
} catch (RecordFormatException e) {
if (e.getMessage().equals("Duplicate PageSettingsBlock record (sid=0x14)")) {
// expected during successful test
} else {
throw new AssertionFailedError("Expected RecordFormatException due to duplicate PSB record");
}
}
}
private static UnknownRecord ur(int sid, String hexData) {
return new UnknownRecord(sid, HexRead.readFromString(hexData));