]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 53204: Improve performance when opening certain Excel files (PageSettingsBlock)
authorYegor Kozlov <yegor@apache.org>
Sun, 22 Jul 2012 10:50:01 +0000 (10:50 +0000)
committerYegor Kozlov <yegor@apache.org>
Sun, 22 Jul 2012 10:50:01 +0000 (10:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1364254 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java

index 4d84732c4d2df8583e1c026d09859b55d9dc90b1..4e5d4384dcd19642d339388a1d4114c1a5c288af 100644 (file)
@@ -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>
index 676a8f983133560250c356cb8f01218a4ff6b6c1..30d3fc62957c97b7f4d01f1d22e858be5e7eb236 100644 (file)
 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);
                             }
                         }
-                    });
-                }
+                    }
+                });
             }
         }
     }