diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 948b5befc..78f7187ea 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -50,7 +50,6 @@ import org.apache.poi.hssf.record.PrintHeadersRecord; import org.apache.poi.hssf.record.ProtectRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RecordBase; -import org.apache.poi.hssf.record.RecordFormatException; import org.apache.poi.hssf.record.RefModeRecord; import org.apache.poi.hssf.record.RowRecord; import org.apache.poi.hssf.record.SCLRecord; @@ -61,6 +60,7 @@ 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; import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; import org.apache.poi.hssf.record.aggregates.DataValidityTable; @@ -71,8 +71,8 @@ import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; import org.apache.poi.hssf.record.aggregates.RecordAggregate.PositionTrackingVisitor; import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.hssf.record.formula.FormulaShifter; -import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.hssf.util.PaneInformation; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -165,19 +165,16 @@ public final class Sheet implements Model { RowRecordsAggregate rra = null; records = new ArrayList(128); - // TODO - take chart streams off into separate java objects - int bofEofNestingLevel = 1; // nesting level can only get to 2 (when charts are present) int dimsloc = -1; - if (rs.peekNextSid() == BOFRecord.sid) { - BOFRecord bof = (BOFRecord) rs.getNext(); - if (bof.getType() != BOFRecord.TYPE_WORKSHEET) { - // TODO - fix junit tests throw new RuntimeException("Bad BOF record type"); - } - records.add(bof); - } else { + if (rs.peekNextSid() != BOFRecord.sid) { throw new RuntimeException("BOF record expected"); } + BOFRecord bof = (BOFRecord) rs.getNext(); + if (bof.getType() != BOFRecord.TYPE_WORKSHEET) { + // TODO - fix junit tests throw new RuntimeException("Bad BOF record type"); + } + records.add(bof); while (rs.hasNext()) { int recSid = rs.peekNextSid(); @@ -198,7 +195,7 @@ public final class Sheet implements Model { continue; } - if (RecordOrderer.isRowBlockRecord(recSid) && bofEofNestingLevel == 1 ) { + if (RecordOrderer.isRowBlockRecord(recSid)) { //only add the aggregate once if (rra != null) { throw new RuntimeException("row/cell records found in the wrong place"); @@ -218,13 +215,7 @@ public final class Sheet implements Model { records.add(psb); continue; } - if (bofEofNestingLevel == 2) { - psb = new PageSettingsBlock(rs); - // It's normal for a chart to have its own PageSettingsBlock - // Fall through and add psb here, because chart records - // are stored loose among the sheet records. - // this latest psb does not clash with _psBlock - } else if (windowTwo != null) { + if (windowTwo != null) { // probably 'Custom View Settings' sub-stream which is found between // USERSVIEWBEGIN(01AA) and USERSVIEWEND(01AB) // TODO - create UsersViewAggregate to hold these sub-streams, and simplify this code a bit @@ -262,6 +253,17 @@ public final class Sheet implements Model { _mergedCellsTable.read(rs); continue; } + + if (recSid == BOFRecord.sid) { + ChartSubstreamRecordAggregate chartAgg = new ChartSubstreamRecordAggregate(rs); + if (false) { + // TODO - would like to keep the chart aggregate packed, but one unit test needs attention + records.add(chartAgg); + } else { + spillAggregate(chartAgg, records); + } + continue; + } Record rec = rs.getNext(); if ( recSid == IndexRecord.sid ) { @@ -277,28 +279,12 @@ public final class Sheet implements Model { continue; } - if (recSid == BOFRecord.sid) - { - bofEofNestingLevel++; - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "Hit BOF record. Nesting increased to " + bofEofNestingLevel); - BOFRecord bof = (BOFRecord)rec; - // TODO - extract chart sub-stream into record aggregate - if (bof.getType() != BOFRecord.TYPE_CHART) { - throw new RecordFormatException("Bad BOF record type: " + bof.getType()); - } + if (recSid == EOFRecord.sid) { + records.add(rec); + break; } - else if (recSid == EOFRecord.sid) - { - --bofEofNestingLevel; - if (log.check( POILogger.DEBUG )) - log.log(POILogger.DEBUG, "Hit EOF record. Nesting decreased to " + bofEofNestingLevel); - if (bofEofNestingLevel == 0) { - records.add(rec); - break; - } - } - else if (recSid == DimensionsRecord.sid) + + if (recSid == DimensionsRecord.sid) { // Make a columns aggregate if one hasn't ready been created. if (_columnInfos == null) @@ -386,6 +372,12 @@ public final class Sheet implements Model { if (log.check( POILogger.DEBUG )) log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited"); } + private static void spillAggregate(RecordAggregate ra, final List recs) { + ra.visitContainedRecords(new RecordVisitor() { + public void visitRecord(Record r) { + recs.add(r); + }}); + } /** * Hack to recover from the situation where the page settings block has been split by * an intervening {@link WSBoolRecord} diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index e96de6ec7..1c202f847 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -43,6 +43,7 @@ import org.apache.poi.hssf.model.CommentShape; import org.apache.poi.hssf.model.ConvertAnchor; import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.record.aggregates.RecordAggregate; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFPatriarch; import org.apache.poi.hssf.usermodel.HSSFShape; @@ -884,7 +885,11 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { private static short sid( List records, int loc ) { - return ( (Record) records.get( loc ) ).getSid(); + Object obj = records.get( loc ); + if (obj instanceof RecordAggregate) { + return -1; + } + return ( (Record) obj ).getSid(); } diff --git a/src/java/org/apache/poi/hssf/record/UnknownRecord.java b/src/java/org/apache/poi/hssf/record/UnknownRecord.java index 0a7209332..5b6faf315 100644 --- a/src/java/org/apache/poi/hssf/record/UnknownRecord.java +++ b/src/java/org/apache/poi/hssf/record/UnknownRecord.java @@ -17,6 +17,7 @@ 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; @@ -35,6 +36,12 @@ public final class UnknownRecord extends StandardRecord { /* * Some Record IDs used by POI as 'milestones' in the record stream */ + /** + * seems to be part of the {@link PageSettingsBlock}. Not interpreted by POI. + * The name 'PRINTSIZE' was taken from OOO source.
+ * The few POI test samples with this record have data { 0x03, 0x00 }. + */ + public static final int PRINTSIZE_0033 = 0x0033; public static final int PLS_004D = 0x004D; public static final int SHEETPR_0081 = 0x0081; public static final int STANDARDWIDTH_0099 = 0x0099; @@ -123,6 +130,7 @@ public final class UnknownRecord extends StandardRecord { // Make sure you delete the corresponding entry from // this method any time a new Record subclass is created. switch (sid) { + case PRINTSIZE_0033: return "PRINTSIZE"; case PLS_004D: return "PLS"; case 0x0050: return "DCON"; // Data Consolidation Information case 0x007F: return "IMDATA"; diff --git a/src/java/org/apache/poi/hssf/record/aggregates/ChartSubstreamRecordAggregate.java b/src/java/org/apache/poi/hssf/record/aggregates/ChartSubstreamRecordAggregate.java new file mode 100644 index 000000000..bb135174d --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/aggregates/ChartSubstreamRecordAggregate.java @@ -0,0 +1,81 @@ +/* ==================================================================== + 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.aggregates; + +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; + +/** + * Manages the all the records associated with a chart sub-stream.
+ * Includes the initial {@link BOFRecord} and final {@link EOFRecord}. + * + * @author Josh Micich + */ +public final class ChartSubstreamRecordAggregate extends RecordAggregate { + + private final BOFRecord _bofRec; + /** + * All the records between BOF and EOF + */ + private final List _recs; + private PageSettingsBlock _psBlock; + + public ChartSubstreamRecordAggregate(RecordStream rs) { + _bofRec = (BOFRecord) rs.getNext(); + List temp = new ArrayList(); + while (rs.peekNextClass() != EOFRecord.class) { + if (PageSettingsBlock.isComponentRecord(rs.peekNextSid())) { + if (_psBlock != null) { + throw new IllegalStateException( + "Found more than one PageSettingsBlock in chart sub-stream"); + } + _psBlock = new PageSettingsBlock(rs); + temp.add(_psBlock); + continue; + } + temp.add(rs.getNext()); + } + _recs = temp; + Record eof = rs.getNext(); // no need to save EOF in field + if (!(eof instanceof EOFRecord)) { + throw new IllegalStateException("Bad chart EOF"); + } + } + + public void visitContainedRecords(RecordVisitor rv) { + if (_recs.isEmpty()) { + return; + } + rv.visitRecord(_bofRec); + for (int i = 0; i < _recs.size(); i++) { + RecordBase rb = _recs.get(i); + if (rb instanceof RecordAggregate) { + ((RecordAggregate) rb).visitContainedRecords(rv); + } else { + rv.visitRecord((Record) rb); + } + } + rv.visitRecord(EOFRecord.instance); + } +} diff --git a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java index 1731a1c3a..f906c7a99 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java @@ -70,9 +70,10 @@ public final class PageSettingsBlock extends RecordAggregate { * include any trailing {@link ContinueRecord}s. */ private List _plsContinues; - private PrintSetupRecord printSetup; + private PrintSetupRecord _printSetup; private Record _bitmap; private Record _headerFooter; + private Record _printSize; public PageSettingsBlock(RecordStream rs) { while(true) { @@ -92,7 +93,7 @@ public final class PageSettingsBlock extends RecordAggregate { _footer = new FooterRecord(""); _hCenter = createHCenter(); _vCenter = createVCenter(); - printSetup = createPrintSetup(); + _printSetup = createPrintSetup(); } /** @@ -114,6 +115,7 @@ public final class PageSettingsBlock extends RecordAggregate { case UnknownRecord.PLS_004D: case PrintSetupRecord.sid: case UnknownRecord.BITMAP_00E9: + case UnknownRecord.PRINTSIZE_0033: case UnknownRecord.HEADER_FOOTER_089C: // extra header/footer settings supported by Excel 2007 return true; } @@ -162,11 +164,14 @@ public final class PageSettingsBlock extends RecordAggregate { } break; case PrintSetupRecord.sid: - printSetup = (PrintSetupRecord)rs.getNext(); + _printSetup = (PrintSetupRecord)rs.getNext(); break; case UnknownRecord.BITMAP_00E9: _bitmap = rs.getNext(); break; + case UnknownRecord.PRINTSIZE_0033: + _printSize = rs.getNext(); + break; case UnknownRecord.HEADER_FOOTER_089C: _headerFooter = rs.getNext(); break; @@ -228,8 +233,9 @@ public final class PageSettingsBlock extends RecordAggregate { visitIfPresent(cr, rv); } } - visitIfPresent(printSetup, rv); + visitIfPresent(_printSetup, rv); visitIfPresent(_bitmap, rv); + visitIfPresent(_printSize, rv); visitIfPresent(_headerFooter, rv); } private static void visitIfPresent(Record r, RecordVisitor rv) { @@ -333,7 +339,7 @@ public final class PageSettingsBlock extends RecordAggregate { */ public PrintSetupRecord getPrintSetup () { - return printSetup; + return _printSetup; } /** @@ -342,7 +348,7 @@ public final class PageSettingsBlock extends RecordAggregate { */ public void setPrintSetup (PrintSetupRecord newPrintSetup) { - printSetup = newPrintSetup; + _printSetup = newPrintSetup; }