diff options
author | Yegor Kozlov <yegor@apache.org> | 2012-07-22 10:50:01 +0000 |
---|---|---|
committer | Yegor Kozlov <yegor@apache.org> | 2012-07-22 10:50:01 +0000 |
commit | 22bdfbbbd2b4225e2463e8eea768f3c101bd4c45 (patch) | |
tree | cc911dfd250491634e913cc1f676b9a85c2572ca /src | |
parent | 1de04ad5f80caf921f35a6fed21f4f954937580d (diff) | |
download | poi-22bdfbbbd2b4225e2463e8eea768f3c101bd4c45.tar.gz poi-22bdfbbbd2b4225e2463e8eea768f3c101bd4c45.zip |
Bugzilla 53204: Improve performance when opening certain Excel files (PageSettingsBlock)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1364254 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src')
-rw-r--r-- | src/documentation/content/xdocs/status.xml | 1 | ||||
-rw-r--r-- | src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java | 1217 |
2 files changed, 613 insertions, 605 deletions
diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 4d84732c4d..4e5d4384dc 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ <changes> <release version="3.9-beta1" date="2012-??-??"> + <action dev="poi-developers" type="add">53204 - Improved performanceof PageSettingsBlock in HSSF </action> <action dev="poi-developers" type="add">53500 - Getter for repeating rows and columns</action> <action dev="poi-developers" type="fix">53369 - Fixed tests failing on JDK 1.7</action> <action dev="poi-developers" type="fix">53360 - Fixed SXSSF to correctly write text before escaped Unicode control character</action> 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 676a8f9831..30d3fc6295 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java @@ -18,13 +18,15 @@ package org.apache.poi.hssf.record.aggregates; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Arrays; +import java.util.Map; import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.record.*; +import org.apache.poi.util.HexDump; /** * Groups the page settings records for a worksheet.<p/> @@ -35,59 +37,59 @@ import org.apache.poi.hssf.record.*; */ public final class PageSettingsBlock extends RecordAggregate { - /** - * PLS is potentially a <i>continued</i> record, but is currently uninterpreted by POI - */ - private static final class PLSAggregate extends RecordAggregate { - private static final ContinueRecord[] EMPTY_CONTINUE_RECORD_ARRAY = { }; - private final Record _pls; - /** - * holds any continue records found after the PLS record.<br/> - * This would not be required if PLS was properly interpreted. - * Currently, PLS is an {@link UnknownRecord} and does not automatically - * include any trailing {@link ContinueRecord}s. - */ - private ContinueRecord[] _plsContinues; - - public PLSAggregate(RecordStream rs) { - _pls = rs.getNext(); - if (rs.peekNextSid()==ContinueRecord.sid) { - List<ContinueRecord> temp = new ArrayList<ContinueRecord>(); - while (rs.peekNextSid()==ContinueRecord.sid) { - temp.add((ContinueRecord)rs.getNext()); - } - _plsContinues = new ContinueRecord[temp.size()]; - temp.toArray(_plsContinues); - } else { - _plsContinues = EMPTY_CONTINUE_RECORD_ARRAY; - } - } - - @Override - public void visitContainedRecords(RecordVisitor rv) { - rv.visitRecord(_pls); - for (int i = 0; i < _plsContinues.length; i++) { - rv.visitRecord(_plsContinues[i]); - } - } - } - - // Every one of these component records is optional - // (The whole PageSettingsBlock may not be present) - private PageBreakRecord _rowBreaksRecord; - private PageBreakRecord _columnBreaksRecord; - private HeaderRecord _header; - private FooterRecord _footer; - private HCenterRecord _hCenter; - private VCenterRecord _vCenter; - private LeftMarginRecord _leftMargin; - private RightMarginRecord _rightMargin; - private TopMarginRecord _topMargin; - private BottomMarginRecord _bottomMargin; - private final List<PLSAggregate> _plsRecords; - private PrintSetupRecord _printSetup; - private Record _bitmap; - private HeaderFooterRecord _headerFooter; + /** + * PLS is potentially a <i>continued</i> record, but is currently uninterpreted by POI + */ + private static final class PLSAggregate extends RecordAggregate { + private static final ContinueRecord[] EMPTY_CONTINUE_RECORD_ARRAY = { }; + private final Record _pls; + /** + * holds any continue records found after the PLS record.<br/> + * This would not be required if PLS was properly interpreted. + * Currently, PLS is an {@link UnknownRecord} and does not automatically + * include any trailing {@link ContinueRecord}s. + */ + private ContinueRecord[] _plsContinues; + + public PLSAggregate(RecordStream rs) { + _pls = rs.getNext(); + if (rs.peekNextSid()==ContinueRecord.sid) { + List<ContinueRecord> temp = new ArrayList<ContinueRecord>(); + while (rs.peekNextSid()==ContinueRecord.sid) { + temp.add((ContinueRecord)rs.getNext()); + } + _plsContinues = new ContinueRecord[temp.size()]; + temp.toArray(_plsContinues); + } else { + _plsContinues = EMPTY_CONTINUE_RECORD_ARRAY; + } + } + + @Override + public void visitContainedRecords(RecordVisitor rv) { + rv.visitRecord(_pls); + for (int i = 0; i < _plsContinues.length; i++) { + rv.visitRecord(_plsContinues[i]); + } + } + } + + // Every one of these component records is optional + // (The whole PageSettingsBlock may not be present) + private PageBreakRecord _rowBreaksRecord; + private PageBreakRecord _columnBreaksRecord; + private HeaderRecord _header; + private FooterRecord _footer; + private HCenterRecord _hCenter; + private VCenterRecord _vCenter; + private LeftMarginRecord _leftMargin; + private RightMarginRecord _rightMargin; + private TopMarginRecord _topMargin; + private BottomMarginRecord _bottomMargin; + private final List<PLSAggregate> _plsRecords; + private PrintSetupRecord _printSetup; + private Record _bitmap; + private HeaderFooterRecord _headerFooter; /** * HeaderFooterRecord records belonging to preceding CustomViewSettingsRecordAggregates. * The indicator of such records is a non-zero GUID, @@ -96,546 +98,546 @@ public final class PageSettingsBlock extends RecordAggregate { private List<HeaderFooterRecord> _sviewHeaderFooters = new ArrayList<HeaderFooterRecord>(); private Record _printSize; - public PageSettingsBlock(RecordStream rs) { - _plsRecords = new ArrayList<PLSAggregate>(); - while(true) { - if (!readARecord(rs)) { - break; - } - } - } - - /** - * Creates a PageSettingsBlock with default settings - */ - public PageSettingsBlock() { - _plsRecords = new ArrayList<PLSAggregate>(); - _rowBreaksRecord = new HorizontalPageBreakRecord(); - _columnBreaksRecord = new VerticalPageBreakRecord(); - _header = new HeaderRecord(""); - _footer = new FooterRecord(""); - _hCenter = createHCenter(); - _vCenter = createVCenter(); - _printSetup = createPrintSetup(); - } - - /** - * @return <code>true</code> if the specified Record sid is one belonging to the - * 'Page Settings Block'. - */ - public static boolean isComponentRecord(int sid) { - switch (sid) { - case HorizontalPageBreakRecord.sid: - case VerticalPageBreakRecord.sid: - case HeaderRecord.sid: - case FooterRecord.sid: - case HCenterRecord.sid: - case VCenterRecord.sid: - case LeftMarginRecord.sid: - case RightMarginRecord.sid: - case TopMarginRecord.sid: - case BottomMarginRecord.sid: - case UnknownRecord.PLS_004D: - case PrintSetupRecord.sid: - case UnknownRecord.BITMAP_00E9: - case UnknownRecord.PRINTSIZE_0033: - case HeaderFooterRecord.sid: // extra header/footer settings supported by Excel 2007 - return true; - } - return false; - } - - 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: - _plsRecords.add(new PLSAggregate(rs)); - 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 HeaderFooterRecord.sid: - //there can be multiple HeaderFooterRecord records belonging to different sheet views - HeaderFooterRecord hf = (HeaderFooterRecord)rs.getNext(); + public PageSettingsBlock(RecordStream rs) { + _plsRecords = new ArrayList<PLSAggregate>(); + while(true) { + if (!readARecord(rs)) { + break; + } + } + } + + /** + * Creates a PageSettingsBlock with default settings + */ + public PageSettingsBlock() { + _plsRecords = new ArrayList<PLSAggregate>(); + _rowBreaksRecord = new HorizontalPageBreakRecord(); + _columnBreaksRecord = new VerticalPageBreakRecord(); + _header = new HeaderRecord(""); + _footer = new FooterRecord(""); + _hCenter = createHCenter(); + _vCenter = createVCenter(); + _printSetup = createPrintSetup(); + } + + /** + * @return <code>true</code> if the specified Record sid is one belonging to the + * 'Page Settings Block'. + */ + public static boolean isComponentRecord(int sid) { + switch (sid) { + case HorizontalPageBreakRecord.sid: + case VerticalPageBreakRecord.sid: + case HeaderRecord.sid: + case FooterRecord.sid: + case HCenterRecord.sid: + case VCenterRecord.sid: + case LeftMarginRecord.sid: + case RightMarginRecord.sid: + case TopMarginRecord.sid: + case BottomMarginRecord.sid: + case UnknownRecord.PLS_004D: + case PrintSetupRecord.sid: + case UnknownRecord.BITMAP_00E9: + case UnknownRecord.PRINTSIZE_0033: + case HeaderFooterRecord.sid: // extra header/footer settings supported by Excel 2007 + return true; + } + return false; + } + + 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: + _plsRecords.add(new PLSAggregate(rs)); + 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 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; - } - 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(); - } - return _rowBreaksRecord; - } - - private PageBreakRecord getColumnBreaksRecord() { - if (_columnBreaksRecord == null) { - _columnBreaksRecord = new VerticalPageBreakRecord(); - } - return _columnBreaksRecord; - } - - - /** - * Sets a page break at the indicated column - * - */ - public void setColumnBreak(short column, short fromRow, short toRow) { - getColumnBreaksRecord().addBreak(column, fromRow, toRow); - } - - /** - * Removes a page break at the indicated column - * - */ - public void removeColumnBreak(int column) { - getColumnBreaksRecord().removeBreak(column); - } - - - - - public void visitContainedRecords(RecordVisitor rv) { - // Replicates record order from Excel 2007, though this is not critical - - visitIfPresent(_rowBreaksRecord, rv); - visitIfPresent(_columnBreaksRecord, rv); - // Write out empty header / footer records if these are missing - if (_header == null) { - rv.visitRecord(new HeaderRecord("")); - } else { - rv.visitRecord(_header); - } - if (_footer == null) { - rv.visitRecord(new FooterRecord("")); - } else { - rv.visitRecord(_footer); - } - visitIfPresent(_hCenter, rv); - visitIfPresent(_vCenter, rv); - visitIfPresent(_leftMargin, rv); - visitIfPresent(_rightMargin, rv); - visitIfPresent(_topMargin, rv); - visitIfPresent(_bottomMargin, rv); - for (RecordAggregate pls : _plsRecords) { - pls.visitContainedRecords(rv); - } - visitIfPresent(_printSetup, rv); - visitIfPresent(_bitmap, rv); - visitIfPresent(_printSize, rv); - visitIfPresent(_headerFooter, rv); - } - private static void visitIfPresent(Record r, RecordVisitor rv) { - if (r != null) { - rv.visitRecord(r); - } - } - private static void visitIfPresent(PageBreakRecord r, RecordVisitor rv) { - if (r != null) { - if (r.isEmpty()) { - // its OK to not serialize empty page break records - return; - } - rv.visitRecord(r); - } - } - - /** - * creates the HCenter Record and sets it to false (don't horizontally center) - */ - private static HCenterRecord createHCenter() { - HCenterRecord retval = new HCenterRecord(); - - retval.setHCenter(false); - return retval; - } - - /** - * creates the VCenter Record and sets it to false (don't horizontally center) - */ - private static VCenterRecord createVCenter() { - VCenterRecord retval = new VCenterRecord(); - - retval.setVCenter(false); - return retval; - } - - /** - * creates the PrintSetup Record and sets it to defaults and marks it invalid - * @see org.apache.poi.hssf.record.PrintSetupRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a PrintSetupRecord - */ - private static PrintSetupRecord createPrintSetup() { - PrintSetupRecord retval = new PrintSetupRecord(); - - retval.setPaperSize(( short ) 1); - retval.setScale(( short ) 100); - retval.setPageStart(( short ) 1); - retval.setFitWidth(( short ) 1); - retval.setFitHeight(( short ) 1); - retval.setOptions(( short ) 2); - retval.setHResolution(( short ) 300); - retval.setVResolution(( short ) 300); - retval.setHeaderMargin( 0.5); - retval.setFooterMargin( 0.5); - retval.setCopies(( short ) 1); - return retval; - } - - - /** - * Returns the HeaderRecord. - * @return HeaderRecord for the sheet. - */ - public HeaderRecord getHeader () - { - return _header; - } - - /** - * Sets the HeaderRecord. - * @param newHeader The new HeaderRecord for the sheet. - */ - public void setHeader (HeaderRecord newHeader) - { - _header = newHeader; - } - - /** - * Returns the FooterRecord. - * @return FooterRecord for the sheet. - */ - public FooterRecord getFooter () - { - return _footer; - } - - /** - * Sets the FooterRecord. - * @param newFooter The new FooterRecord for the sheet. - */ - public void setFooter (FooterRecord newFooter) - { - _footer = newFooter; - } - - /** - * Returns the PrintSetupRecord. - * @return PrintSetupRecord for the sheet. - */ - public PrintSetupRecord getPrintSetup () - { - return _printSetup; - } - - /** - * Sets the PrintSetupRecord. - * @param newPrintSetup The new PrintSetupRecord for the sheet. - */ - public void setPrintSetup (PrintSetupRecord newPrintSetup) - { - _printSetup = newPrintSetup; - } - - - private Margin getMarginRec(int marginIndex) { - switch (marginIndex) { - case InternalSheet.LeftMargin: return _leftMargin; - case InternalSheet.RightMargin: return _rightMargin; - case InternalSheet.TopMargin: return _topMargin; - case InternalSheet.BottomMargin: return _bottomMargin; - } - throw new IllegalArgumentException( "Unknown margin constant: " + marginIndex ); - } - - - /** - * Gets the size of the margin in inches. - * @param margin which margin to get - * @return the size of the margin - */ - public double getMargin(short margin) { - Margin m = getMarginRec(margin); - if (m != null) { - return m.getMargin(); - } - switch (margin) { - case InternalSheet.LeftMargin: return .75; - case InternalSheet.RightMargin: return .75; - case InternalSheet.TopMargin: return 1.0; - case InternalSheet.BottomMargin: return 1.0; - } - throw new IllegalArgumentException( "Unknown margin constant: " + margin ); - } - - /** - * Sets the size of the margin in inches. - * @param margin which margin to get - * @param size the size of the margin - */ - public void setMargin(short margin, double size) { - Margin m = getMarginRec(margin); - if (m == null) { - switch (margin) { - case InternalSheet.LeftMargin: - _leftMargin = new LeftMarginRecord(); - m = _leftMargin; - break; - case InternalSheet.RightMargin: - _rightMargin = new RightMarginRecord(); - m = _rightMargin; - break; - case InternalSheet.TopMargin: - _topMargin = new TopMarginRecord(); - m = _topMargin; - break; - case InternalSheet.BottomMargin: - _bottomMargin = new BottomMarginRecord(); - m = _bottomMargin; - break; - default : - throw new IllegalArgumentException( "Unknown margin constant: " + margin ); - } - } - m.setMargin( size ); - } - - /** - * Shifts all the page breaks in the range "count" number of rows/columns - * @param breaks The page record to be shifted - * @param start Starting "main" value to shift breaks - * @param stop Ending "main" value to shift breaks - * @param count number of units (rows/columns) to shift by - */ - private static void shiftBreaks(PageBreakRecord breaks, int start, int stop, int count) { - - Iterator<PageBreakRecord.Break> iterator = breaks.getBreaksIterator(); - List<PageBreakRecord.Break> shiftedBreak = new ArrayList<PageBreakRecord.Break>(); - while(iterator.hasNext()) - { - PageBreakRecord.Break breakItem = iterator.next(); - int breakLocation = breakItem.main; - boolean inStart = (breakLocation >= start); - boolean inEnd = (breakLocation <= stop); - if(inStart && inEnd) - shiftedBreak.add(breakItem); - } - - iterator = shiftedBreak.iterator(); - while (iterator.hasNext()) { - PageBreakRecord.Break breakItem = iterator.next(); - breaks.removeBreak(breakItem.main); - breaks.addBreak((short)(breakItem.main+count), breakItem.subFrom, breakItem.subTo); - } - } - - - /** - * Sets a page break at the indicated row - * @param row - */ - public void setRowBreak(int row, short fromCol, short toCol) { - getRowBreaksRecord().addBreak((short)row, fromCol, toCol); - } - - /** - * Removes a page break at the indicated row - * @param row - */ - public void removeRowBreak(int row) { - if (getRowBreaksRecord().getBreaks().length < 1) - throw new IllegalArgumentException("Sheet does not define any row breaks"); - getRowBreaksRecord().removeBreak((short)row); - } - - /** - * Queries if the specified row has a page break - * @param row - * @return true if the specified row has a page break - */ - public boolean isRowBroken(int row) { - return getRowBreaksRecord().getBreak(row) != null; - } - - - /** - * Queries if the specified column has a page break - * - * @return <code>true</code> if the specified column has a page break - */ - public boolean isColumnBroken(int column) { - return getColumnBreaksRecord().getBreak(column) != null; - } - - /** - * Shifts the horizontal page breaks for the indicated count - * @param startingRow - * @param endingRow - * @param count - */ - public void shiftRowBreaks(int startingRow, int endingRow, int count) { - shiftBreaks(getRowBreaksRecord(), startingRow, endingRow, count); - } - - /** - * Shifts the vertical page breaks for the indicated count - * @param startingCol - * @param endingCol - * @param count - */ - public void shiftColumnBreaks(short startingCol, short endingCol, short count) { - shiftBreaks(getColumnBreaksRecord(), startingCol, endingCol, count); - } - - /** - * @return all the horizontal page breaks, never <code>null</code> - */ - public int[] getRowBreaks() { - return getRowBreaksRecord().getBreaks(); - } - - /** - * @return the number of row page breaks - */ - public int getNumRowBreaks(){ - return getRowBreaksRecord().getNumBreaks(); - } - - /** - * @return all the column page breaks, never <code>null</code> - */ - public int[] getColumnBreaks(){ - return getColumnBreaksRecord().getBreaks(); - } - - /** - * @return the number of column page breaks - */ - public int getNumColumnBreaks(){ - return getColumnBreaksRecord().getNumBreaks(); - } - - public VCenterRecord getVCenter() { - return _vCenter; - } - - public HCenterRecord getHCenter() { - return _hCenter; - } - - /** - * HEADERFOOTER is new in 2007. Some apps seem to have scattered this record long after - * the {@link PageSettingsBlock} where it belongs. - */ - public void addLateHeaderFooter(HeaderFooterRecord rec) { - if (_headerFooter != null) { - throw new IllegalStateException("This page settings block already has a header/footer record"); - } - if (rec.getSid() != HeaderFooterRecord.sid) { - throw new RecordFormatException("Unexpected header-footer record sid: 0x" + Integer.toHexString(rec.getSid())); - } - _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; - } - } - } + default: + // all other record types are not part of the PageSettingsBlock + return false; + } + 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(); + } + return _rowBreaksRecord; + } + + private PageBreakRecord getColumnBreaksRecord() { + if (_columnBreaksRecord == null) { + _columnBreaksRecord = new VerticalPageBreakRecord(); + } + return _columnBreaksRecord; + } + + + /** + * Sets a page break at the indicated column + * + */ + public void setColumnBreak(short column, short fromRow, short toRow) { + getColumnBreaksRecord().addBreak(column, fromRow, toRow); + } + + /** + * Removes a page break at the indicated column + * + */ + public void removeColumnBreak(int column) { + getColumnBreaksRecord().removeBreak(column); + } + + + + + public void visitContainedRecords(RecordVisitor rv) { + // Replicates record order from Excel 2007, though this is not critical + + visitIfPresent(_rowBreaksRecord, rv); + visitIfPresent(_columnBreaksRecord, rv); + // Write out empty header / footer records if these are missing + if (_header == null) { + rv.visitRecord(new HeaderRecord("")); + } else { + rv.visitRecord(_header); + } + if (_footer == null) { + rv.visitRecord(new FooterRecord("")); + } else { + rv.visitRecord(_footer); + } + visitIfPresent(_hCenter, rv); + visitIfPresent(_vCenter, rv); + visitIfPresent(_leftMargin, rv); + visitIfPresent(_rightMargin, rv); + visitIfPresent(_topMargin, rv); + visitIfPresent(_bottomMargin, rv); + for (RecordAggregate pls : _plsRecords) { + pls.visitContainedRecords(rv); + } + visitIfPresent(_printSetup, rv); + visitIfPresent(_bitmap, rv); + visitIfPresent(_printSize, rv); + visitIfPresent(_headerFooter, rv); + } + private static void visitIfPresent(Record r, RecordVisitor rv) { + if (r != null) { + rv.visitRecord(r); + } + } + private static void visitIfPresent(PageBreakRecord r, RecordVisitor rv) { + if (r != null) { + if (r.isEmpty()) { + // its OK to not serialize empty page break records + return; + } + rv.visitRecord(r); + } + } + + /** + * creates the HCenter Record and sets it to false (don't horizontally center) + */ + private static HCenterRecord createHCenter() { + HCenterRecord retval = new HCenterRecord(); + + retval.setHCenter(false); + return retval; + } + + /** + * creates the VCenter Record and sets it to false (don't horizontally center) + */ + private static VCenterRecord createVCenter() { + VCenterRecord retval = new VCenterRecord(); + + retval.setVCenter(false); + return retval; + } + + /** + * creates the PrintSetup Record and sets it to defaults and marks it invalid + * @see org.apache.poi.hssf.record.PrintSetupRecord + * @see org.apache.poi.hssf.record.Record + * @return record containing a PrintSetupRecord + */ + private static PrintSetupRecord createPrintSetup() { + PrintSetupRecord retval = new PrintSetupRecord(); + + retval.setPaperSize(( short ) 1); + retval.setScale(( short ) 100); + retval.setPageStart(( short ) 1); + retval.setFitWidth(( short ) 1); + retval.setFitHeight(( short ) 1); + retval.setOptions(( short ) 2); + retval.setHResolution(( short ) 300); + retval.setVResolution(( short ) 300); + retval.setHeaderMargin( 0.5); + retval.setFooterMargin( 0.5); + retval.setCopies(( short ) 1); + return retval; + } + + + /** + * Returns the HeaderRecord. + * @return HeaderRecord for the sheet. + */ + public HeaderRecord getHeader () + { + return _header; + } + + /** + * Sets the HeaderRecord. + * @param newHeader The new HeaderRecord for the sheet. + */ + public void setHeader (HeaderRecord newHeader) + { + _header = newHeader; + } + + /** + * Returns the FooterRecord. + * @return FooterRecord for the sheet. + */ + public FooterRecord getFooter () + { + return _footer; + } + + /** + * Sets the FooterRecord. + * @param newFooter The new FooterRecord for the sheet. + */ + public void setFooter (FooterRecord newFooter) + { + _footer = newFooter; + } + + /** + * Returns the PrintSetupRecord. + * @return PrintSetupRecord for the sheet. + */ + public PrintSetupRecord getPrintSetup () + { + return _printSetup; + } + + /** + * Sets the PrintSetupRecord. + * @param newPrintSetup The new PrintSetupRecord for the sheet. + */ + public void setPrintSetup (PrintSetupRecord newPrintSetup) + { + _printSetup = newPrintSetup; + } + + + private Margin getMarginRec(int marginIndex) { + switch (marginIndex) { + case InternalSheet.LeftMargin: return _leftMargin; + case InternalSheet.RightMargin: return _rightMargin; + case InternalSheet.TopMargin: return _topMargin; + case InternalSheet.BottomMargin: return _bottomMargin; + } + throw new IllegalArgumentException( "Unknown margin constant: " + marginIndex ); + } + + + /** + * Gets the size of the margin in inches. + * @param margin which margin to get + * @return the size of the margin + */ + public double getMargin(short margin) { + Margin m = getMarginRec(margin); + if (m != null) { + return m.getMargin(); + } + switch (margin) { + case InternalSheet.LeftMargin: return .75; + case InternalSheet.RightMargin: return .75; + case InternalSheet.TopMargin: return 1.0; + case InternalSheet.BottomMargin: return 1.0; + } + throw new IllegalArgumentException( "Unknown margin constant: " + margin ); + } + + /** + * Sets the size of the margin in inches. + * @param margin which margin to get + * @param size the size of the margin + */ + public void setMargin(short margin, double size) { + Margin m = getMarginRec(margin); + if (m == null) { + switch (margin) { + case InternalSheet.LeftMargin: + _leftMargin = new LeftMarginRecord(); + m = _leftMargin; + break; + case InternalSheet.RightMargin: + _rightMargin = new RightMarginRecord(); + m = _rightMargin; + break; + case InternalSheet.TopMargin: + _topMargin = new TopMarginRecord(); + m = _topMargin; + break; + case InternalSheet.BottomMargin: + _bottomMargin = new BottomMarginRecord(); + m = _bottomMargin; + break; + default : + throw new IllegalArgumentException( "Unknown margin constant: " + margin ); + } + } + m.setMargin( size ); + } + + /** + * Shifts all the page breaks in the range "count" number of rows/columns + * @param breaks The page record to be shifted + * @param start Starting "main" value to shift breaks + * @param stop Ending "main" value to shift breaks + * @param count number of units (rows/columns) to shift by + */ + private static void shiftBreaks(PageBreakRecord breaks, int start, int stop, int count) { + + Iterator<PageBreakRecord.Break> iterator = breaks.getBreaksIterator(); + List<PageBreakRecord.Break> shiftedBreak = new ArrayList<PageBreakRecord.Break>(); + while(iterator.hasNext()) + { + PageBreakRecord.Break breakItem = iterator.next(); + int breakLocation = breakItem.main; + boolean inStart = (breakLocation >= start); + boolean inEnd = (breakLocation <= stop); + if(inStart && inEnd) + shiftedBreak.add(breakItem); + } + + iterator = shiftedBreak.iterator(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = iterator.next(); + breaks.removeBreak(breakItem.main); + breaks.addBreak((short)(breakItem.main+count), breakItem.subFrom, breakItem.subTo); + } + } + + + /** + * Sets a page break at the indicated row + * @param row + */ + public void setRowBreak(int row, short fromCol, short toCol) { + getRowBreaksRecord().addBreak((short)row, fromCol, toCol); + } + + /** + * Removes a page break at the indicated row + * @param row + */ + public void removeRowBreak(int row) { + if (getRowBreaksRecord().getBreaks().length < 1) + throw new IllegalArgumentException("Sheet does not define any row breaks"); + getRowBreaksRecord().removeBreak((short)row); + } + + /** + * Queries if the specified row has a page break + * @param row + * @return true if the specified row has a page break + */ + public boolean isRowBroken(int row) { + return getRowBreaksRecord().getBreak(row) != null; + } + + + /** + * Queries if the specified column has a page break + * + * @return <code>true</code> if the specified column has a page break + */ + public boolean isColumnBroken(int column) { + return getColumnBreaksRecord().getBreak(column) != null; + } + + /** + * Shifts the horizontal page breaks for the indicated count + * @param startingRow + * @param endingRow + * @param count + */ + public void shiftRowBreaks(int startingRow, int endingRow, int count) { + shiftBreaks(getRowBreaksRecord(), startingRow, endingRow, count); + } + + /** + * Shifts the vertical page breaks for the indicated count + * @param startingCol + * @param endingCol + * @param count + */ + public void shiftColumnBreaks(short startingCol, short endingCol, short count) { + shiftBreaks(getColumnBreaksRecord(), startingCol, endingCol, count); + } + + /** + * @return all the horizontal page breaks, never <code>null</code> + */ + public int[] getRowBreaks() { + return getRowBreaksRecord().getBreaks(); + } + + /** + * @return the number of row page breaks + */ + public int getNumRowBreaks(){ + return getRowBreaksRecord().getNumBreaks(); + } + + /** + * @return all the column page breaks, never <code>null</code> + */ + public int[] getColumnBreaks(){ + return getColumnBreaksRecord().getBreaks(); + } + + /** + * @return the number of column page breaks + */ + public int getNumColumnBreaks(){ + return getColumnBreaksRecord().getNumBreaks(); + } + + public VCenterRecord getVCenter() { + return _vCenter; + } + + public HCenterRecord getHCenter() { + return _hCenter; + } + + /** + * HEADERFOOTER is new in 2007. Some apps seem to have scattered this record long after + * the {@link PageSettingsBlock} where it belongs. + */ + public void addLateHeaderFooter(HeaderFooterRecord rec) { + if (_headerFooter != null) { + throw new IllegalStateException("This page settings block already has a header/footer record"); + } + if (rec.getSid() != HeaderFooterRecord.sid) { + throw new RecordFormatException("Unexpected header-footer record sid: 0x" + Integer.toHexString(rec.getSid())); + } + _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; + } + } + } /** * Some apps can define multiple HeaderFooterRecord records for a sheet. @@ -652,26 +654,31 @@ public final class PageSettingsBlock extends RecordAggregate { // Take a copy to loop over, so we can update the real one // without concurrency issues List<HeaderFooterRecord> hfRecordsToIterate = new ArrayList<HeaderFooterRecord>(_sviewHeaderFooters); - + + final Map<String, HeaderFooterRecord> hfGuidMap = new HashMap<String, HeaderFooterRecord>(); + + for(final HeaderFooterRecord hf : hfRecordsToIterate) { + hfGuidMap.put(HexDump.toHex(hf.getGuid()), hf); + } + // loop through HeaderFooterRecord records having not-empty GUID and match them with // CustomViewSettingsRecordAggregate blocks having UserSViewBegin with the same GUID - for(final HeaderFooterRecord hf : hfRecordsToIterate) { - 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); - _sviewHeaderFooters.remove(hf); - } + 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) { + String guid = HexDump.toHex(((UserSViewBegin) r).getGuid()); + HeaderFooterRecord hf = hfGuidMap.get(guid); + + if (hf != null) { + cv.append(hf); + _sviewHeaderFooters.remove(hf); } } - }); - } + } + }); } } } |