]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 47244 - Fixed HSSFSheet to handle missing header / footer records
authorJosh Micich <josh@apache.org>
Thu, 4 Jun 2009 03:38:52 +0000 (03:38 +0000)
committerJosh Micich <josh@apache.org>
Thu, 4 Jun 2009 03:38:52 +0000 (03:38 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@781645 13f79535-47bb-0310-9956-ffa450edef68

15 files changed:
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/extractor/ExcelExtractor.java
src/java/org/apache/poi/hssf/record/FooterRecord.java
src/java/org/apache/poi/hssf/record/HeaderFooterBase.java
src/java/org/apache/poi/hssf/record/HeaderRecord.java
src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java
src/java/org/apache/poi/hssf/usermodel/HSSFFooter.java
src/java/org/apache/poi/hssf/usermodel/HSSFHeader.java
src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java
src/java/org/apache/poi/hssf/usermodel/HeaderFooter.java
src/java/org/apache/poi/ss/usermodel/Sheet.java
src/testcases/org/apache/poi/hssf/data/noHeaderFooter47244.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/aggregates/TestPageSettingsBlock.java
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHeaderFooter.java

index f84c086a8a20381c53df3b1ee1dc895ceecb7adf..99942aaaeba75184e1dbf0512efda485d7db55c5 100644 (file)
@@ -35,6 +35,7 @@
         <release version="3.5-beta7" date="2009-??-??">
         </release>
         <release version="3.5-beta6" date="2009-06-11">
+           <action dev="POI-DEVELOPERS" type="fix">47244 - Fixed HSSFSheet to handle missing header / footer records</action>
            <action dev="POI-DEVELOPERS" type="fix">47312 - Fixed formula parser to properly reject cell references with a '0' row component</action>
            <action dev="POI-DEVELOPERS" type="fix">47199 - Fixed PageSettingsBlock/Sheet to tolerate margin records after other non-PSB records</action>
            <action dev="POI-DEVELOPERS" type="fix">47069 - Fixed HSSFSheet#getFirstRowNum and HSSFSheet#getLastRowNum to return correct values after removal of all rows</action>
index 262f10a93665b1ceb4c9978d42ecde0b5edc7c8f..dc70fef2fb0ff90dbd1138811229129840d265be 100644 (file)
@@ -293,10 +293,8 @@ public class ExcelExtractor extends POIOLE2TextExtractor implements org.apache.p
                        }
                        
                        // Header text, if there is any
-                       if(_includeHeadersFooters && sheet.getHeader() != null) {
-                               text.append(
-                                               _extractHeaderFooter(sheet.getHeader())
-                               );
+                       if(_includeHeadersFooters) {
+                               text.append(_extractHeaderFooter(sheet.getHeader()));
                        }
                        
                        int firstRow = sheet.getFirstRowNum();
@@ -382,11 +380,9 @@ public class ExcelExtractor extends POIOLE2TextExtractor implements org.apache.p
                                text.append("\n");
                        }
                        
-                       // Finally Feader text, if there is any
-                       if(_includeHeadersFooters && sheet.getFooter() != null) {
-                               text.append(
-                                               _extractHeaderFooter(sheet.getFooter())
-                               );
+                       // Finally Footer text, if there is any
+                       if(_includeHeadersFooters) {
+                               text.append(_extractHeaderFooter(sheet.getFooter()));
                        }
                }
                
index b08d3dca1105a664ee85efeee73343d42454bd69..823372fc68058fcd4b312c8b3fee5cc8de2666bc 100644 (file)
@@ -27,48 +27,30 @@ package org.apache.poi.hssf.record;
  *
  */
 public final class FooterRecord extends HeaderFooterBase {
-    public final static short sid = 0x0015;
+       public final static short sid = 0x0015;
 
-    public FooterRecord(String text) {
-       super(text);
-    }
+       public FooterRecord(String text) {
+               super(text);
+       }
 
-    public FooterRecord(RecordInputStream in) {
+       public FooterRecord(RecordInputStream in) {
                super(in);
        }
 
-     /**
-     * set the footer string
-     *
-     * @param footer string to display
-     */
-    public void setFooter(String footer) {
-        setText(footer);
-    }
-
-    /**
-     * get the footer string
-     *
-     * @return footer string to display
-     */
-    public String getFooter() {
-        return getText();
-    }
-
-    public String toString() {
-        StringBuffer buffer = new StringBuffer();
+       public String toString() {
+               StringBuffer buffer = new StringBuffer();
 
-        buffer.append("[FOOTER]\n");
-        buffer.append("    .footer = ").append(getText()).append("\n");
-        buffer.append("[/FOOTER]\n");
-        return buffer.toString();
-    }
+               buffer.append("[FOOTER]\n");
+               buffer.append("    .footer = ").append(getText()).append("\n");
+               buffer.append("[/FOOTER]\n");
+               return buffer.toString();
+       }
 
-    public short getSid() {
-        return sid;
-    }
+       public short getSid() {
+               return sid;
+       }
 
-    public Object clone() {
-        return new FooterRecord(getText());
-    }
+       public Object clone() {
+               return new FooterRecord(getText());
+       }
 }
index 84678b7b65be2e37f1bcd6bce936778b87d6f7a2..53d5f46926d3cc0f465e4deb98e8a014093d288a 100644 (file)
@@ -25,7 +25,7 @@ import org.apache.poi.util.StringUtil;
  *
  * @author Josh Micich
  */
-abstract class HeaderFooterBase extends StandardRecord {
+public abstract class HeaderFooterBase extends StandardRecord {
        private boolean field_2_hasMultibyte;
        private String field_3_text;
 
@@ -44,7 +44,8 @@ abstract class HeaderFooterBase extends StandardRecord {
                                field_3_text = in.readCompressedUnicode(field_1_footer_len);
                        }
                } else {
-                       // Note - this is unusual: when the text is empty string, the whole record is empty (just the 4 byte BIFF header)
+                       // Note - this is unusual for BIFF records in general, but normal for header / footer records:
+                       // when the text is empty string, the whole record is empty (just the 4 byte BIFF header)
                        field_3_text = "";
                }
        }
@@ -62,16 +63,9 @@ abstract class HeaderFooterBase extends StandardRecord {
                field_3_text = text;
 
                // Check it'll fit into the space in the record
-               if (field_2_hasMultibyte) {
-                       if (field_3_text.length() > 127) {
-                               throw new IllegalArgumentException(
-                                               "Footer string too long (limit is 127 for unicode strings)");
-                       }
-               } else {
-                       if (field_3_text.length() > 255) {
-                               throw new IllegalArgumentException(
-                                               "Footer string too long (limit is 255 for non-unicode strings)");
-                       }
+               if (getDataSize() > RecordInputStream.MAX_RECORD_DATA_SIZE) {
+                       throw new IllegalArgumentException("Header/Footer string too long (limit is "
+                                       + RecordInputStream.MAX_RECORD_DATA_SIZE + " bytes)");
                }
        }
 
index 188149d7df831807f87b01ff18feb5e5140da8f6..23adedc5a8e859c0cc3121b9dca8fb57bc0fbf3e 100644 (file)
@@ -17,7 +17,6 @@
 
 package org.apache.poi.hssf.record;
 
-
 /**
  * Title:        Header Record<P>
  * Description:  Specifies a header for a sheet<P>
@@ -27,49 +26,30 @@ package org.apache.poi.hssf.record;
  * @author Jason Height (jheight at chariot dot net dot au)
  */
 public final class HeaderRecord extends HeaderFooterBase {
-    public final static short sid = 0x0014;
-
-    public HeaderRecord(String text) {
-       super(text);
-    }
-
-    public HeaderRecord(RecordInputStream in) {
-       super(in);
-    }
-
-    /**
-     * set the header string
-     *
-     * @param header string to display
-     */
-    public void setHeader(String header) {
-       setText(header);
-    }
+       public final static short sid = 0x0014;
 
-    /**
-     * get the header string
-     *
-     * @return header string to display
-     */
-    public String getHeader() {
-        return getText();
-    }
+       public HeaderRecord(String text) {
+               super(text);
+       }
 
-    public String toString() {
-        StringBuffer buffer = new StringBuffer();
+       public HeaderRecord(RecordInputStream in) {
+               super(in);
+       }
 
-        buffer.append("[HEADER]\n");
-        buffer.append("    .header = ").append(getText()).append("\n");
-        buffer.append("[/HEADER]\n");
-        return buffer.toString();
-    }
+       public String toString() {
+               StringBuffer buffer = new StringBuffer();
 
+               buffer.append("[HEADER]\n");
+               buffer.append("    .header = ").append(getText()).append("\n");
+               buffer.append("[/HEADER]\n");
+               return buffer.toString();
+       }
 
-    public short getSid() {
-        return sid;
-    }
+       public short getSid() {
+               return sid;
+       }
 
-    public Object clone() {
-      return new HeaderRecord(getText());
-    }
+       public Object clone() {
+               return new HeaderRecord(getText());
+       }
 }
index 98b589e67e4855b24f258a894a7cc130b3a4465b..1b9228bec8259975975e5d4f96390d30510d2e84 100644 (file)
@@ -239,10 +239,21 @@ public final class PageSettingsBlock extends RecordAggregate {
 
 
        public void visitContainedRecords(RecordVisitor rv) {
+               // Replicates record order from Excel 2007, though this is not critical
+
                visitIfPresent(_rowBreaksRecord, rv);
                visitIfPresent(_columnBreaksRecord, rv);
-               visitIfPresent(_header, rv);
-               visitIfPresent(_footer, 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);
index 4f10ba8c9fbab683e9a1e098f6632219bbe880a0..da7d7403d35bb1a9613c353ad8d99a8b6d403ecf 100644 (file)
    limitations under the License.
 ==================================================================== */
 
-
 package org.apache.poi.hssf.usermodel;
 
 import org.apache.poi.hssf.record.FooterRecord;
+import org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
 import org.apache.poi.ss.usermodel.Footer;
 
 /**
@@ -33,59 +33,29 @@ import org.apache.poi.ss.usermodel.Footer;
  * <P>
  * @author Shawn Laubach (slaubach at apache dot org)
  */
-public class HSSFFooter extends HeaderFooter implements Footer {
-    private FooterRecord footerRecord;
-
-    /**
-     * Constructor.  Creates a new footer interface from a footer record
-     * @param footerRecord Footer record to create the footer with
-     */
-    protected HSSFFooter(FooterRecord footerRecord) {
-       super(footerRecord.getFooter());
-       this.footerRecord = footerRecord;
-    }
-
-    /**
-     * Sets the left string.
-     * @param newLeft The string to set as the left side.
-     */
-    public void setLeft(String newLeft) {
-       left = newLeft;
-       createFooterString();
-    }
-
-    /**
-     * Sets the center string.
-     * @param newCenter The string to set as the center.
-     */
-    public void setCenter(String newCenter) {
-       center = newCenter;
-       createFooterString();
-    }
-
-    /**
-     * Sets the right string.
-     * @param newRight The string to set as the right side.
-     */
-    public void setRight(String newRight) {
-       right = newRight;
-       createFooterString();
-    }
-    
-    protected String getRawFooter() {
-       return footerRecord.getFooter();
-    }
-
-
-    /**
-     * Creates the complete footer string based on the left, center, and middle
-     * strings.
-     */
-    private void createFooterString() {
-       footerRecord.setFooter(
-                              "&C" + (center == null ? "" : center) +
-                              "&L" + (left == null ? "" : left) +
-                              "&R" + (right == null ? "" : right));
-    }
+public final class HSSFFooter extends HeaderFooter implements Footer {
+       private final PageSettingsBlock _psb;
+
+       protected HSSFFooter(PageSettingsBlock psb) {
+               _psb = psb;
+       }
+
+       protected String getRawText() {
+               FooterRecord hf = _psb.getFooter();
+               if (hf == null) {
+                       return "";
+               }
+               return hf.getText();
+       }
+
+       @Override
+       protected void setHeaderFooterText(String text) {
+               FooterRecord hfr = _psb.getFooter();
+               if (hfr == null) {
+                       hfr = new FooterRecord(text);
+                       _psb.setFooter(hfr);
+               } else {
+                       hfr.setText(text);
+               }
+       }
 }
-
index 36dfc969b049b05a5e73a7c9d7184e324967e180..4d706159eef8c73b0bfd7825ecb7fc4877047ab9 100644 (file)
@@ -18,6 +18,7 @@
 package org.apache.poi.hssf.usermodel;
 
 import org.apache.poi.hssf.record.HeaderRecord;
+import org.apache.poi.hssf.record.aggregates.PageSettingsBlock;
 import org.apache.poi.ss.usermodel.Header;
 
 /**
@@ -33,66 +34,30 @@ import org.apache.poi.ss.usermodel.Header;
  *
  * @author Shawn Laubach (slaubach at apache dot org)
  */
-public class HSSFHeader extends HeaderFooter implements Header {
-       private HeaderRecord headerRecord;
-
-    /**
-     * Constructor.  Creates a new header interface from a header record
-     *
-     * @param headerRecord Header record to create the header with
-     */
-    protected HSSFHeader( HeaderRecord headerRecord ) {
-       super(headerRecord.getHeader());
-        this.headerRecord = headerRecord;
-    }
-
-    /**
-     * Sets the left string.
-     *
-     * @param newLeft The string to set as the left side.
-     */
-    public void setLeft( String newLeft )
-    {
-        left = newLeft;
-        createHeaderString();
-    }
-
-    /**
-     * Sets the center string.
-     *
-     * @param newCenter The string to set as the center.
-     */
-    public void setCenter( String newCenter )
-    {
-        center = newCenter;
-        createHeaderString();
-    }
-
-    /**
-     * Sets the right string.
-     *
-     * @param newRight The string to set as the right side.
-     */
-    public void setRight( String newRight )
-    {
-        right = newRight;
-        createHeaderString();
-    }
-    
-    protected String getRawHeader() {
-       return headerRecord.getHeader();
-    }
-
-    /**
-     * Creates the complete header string based on the left, center, and middle
-     * strings.
-     */
-    private void createHeaderString()
-    {
-        headerRecord.setHeader( "&C" + ( center == null ? "" : center ) +
-                "&L" + ( left == null ? "" : left ) +
-                "&R" + ( right == null ? "" : right ) );
-    }
-
+public final class HSSFHeader extends HeaderFooter implements Header {
+
+       private final PageSettingsBlock _psb;
+
+       protected HSSFHeader(PageSettingsBlock psb) {
+               _psb = psb;
+       }
+
+       protected String getRawText() {
+               HeaderRecord hf = _psb.getHeader();
+               if (hf == null) {
+                       return "";
+               }
+               return hf.getText();
+       }
+
+       @Override
+       protected void setHeaderFooterText(String text) {
+               HeaderRecord hfr = _psb.getHeader();
+               if (hfr == null) {
+                       hfr = new HeaderRecord(text);
+                       _psb.setHeader(hfr);
+               } else {
+                       hfr.setText(text);
+               }
+       }
 }
-
index 0067a6bb8c6071037daa2582eddbbfd93bcc76f4..83474957e68c59432ef6400dda42f3f0961883f7 100644 (file)
@@ -913,20 +913,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
         return new HSSFPrintSetup(_sheet.getPageSettings().getPrintSetup());
     }
 
-    /**
-     * Gets the user model for the document header.
-     * @return The Document header.
-     */
     public HSSFHeader getHeader() {
-        return new HSSFHeader(_sheet.getPageSettings().getHeader());
+        return new HSSFHeader(_sheet.getPageSettings());
     }
 
-    /**
-     * Gets the user model for the document footer.
-     * @return The Document footer.
-     */
     public HSSFFooter getFooter() {
-        return new HSSFFooter(_sheet.getPageSettings().getFooter());
+        return new HSSFFooter(_sheet.getPageSettings());
     }
 
     /**
index 8e091ec0032c644c7355654d22babd6a1e29cd78..4fd7c89cc04d2649a9702ad319f3bff44d72917d 100644 (file)
    See the License for the specific language governing permissions and
    limitations under the License.
 ==================================================================== */
+
 package org.apache.poi.hssf.usermodel;
 
-import java.util.ArrayList;
 
 /**
- * Common class for {@link HSSFHeader} and
- *  {@link HSSFFooter}.
+ * Common class for {@link HSSFHeader} and {@link HSSFFooter}.
  */
 public abstract class HeaderFooter implements org.apache.poi.ss.usermodel.HeaderFooter {
-       protected String left;
-       protected String center;
-       protected String right;
+
+       protected HeaderFooter() {
+               //
+       }
        
-       private boolean stripFields = false;
+       /**
+        * @return the internal text representation (combining center, left and right parts).
+        * Possibly empty string if no header or footer is set.  Never <code>null</code>.
+        */
+       protected abstract String getRawText(); 
        
-       protected HeaderFooter(String text) {
-               while (text != null && text.length() > 1) {
-                   int pos = text.length();
-                   switch (text.substring(1, 2).charAt(0)) {
-                           case 'L' :
+       private String[] splitParts() {
+               String text = getRawText();
+               // default values
+               String _left = "";
+               String _center = "";
+               String _right = "";
+
+               while (text.length() > 1) {
+                       int pos = text.length();
+                       switch (text.charAt(1)) {
+                       case 'L':
                                if (text.indexOf("&C") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&C"));
-                               } 
+                                       pos = Math.min(pos, text.indexOf("&C"));
+                               }
                                if (text.indexOf("&R") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&R"));
-                               } 
-                               left = text.substring(2, pos);
+                                       pos = Math.min(pos, text.indexOf("&R"));
+                               }
+                               _left = text.substring(2, pos);
                                text = text.substring(pos);
                                break;
-                   case 'C' : 
+                       case 'C':
                                if (text.indexOf("&L") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&L"));
-                               } 
+                                       pos = Math.min(pos, text.indexOf("&L"));
+                               }
                                if (text.indexOf("&R") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&R"));
-                               } 
-                               center = text.substring(2, pos);
+                                       pos = Math.min(pos, text.indexOf("&R"));
+                               }
+                               _center = text.substring(2, pos);
                                text = text.substring(pos);
                                break;
-                   case 'R' : 
+                       case 'R':
                                if (text.indexOf("&C") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&C"));
-                               } 
+                                       pos = Math.min(pos, text.indexOf("&C"));
+                               }
                                if (text.indexOf("&L") >= 0) {
-                                   pos = Math.min(pos, text.indexOf("&L"));
-                               } 
-                               right = text.substring(2, pos);
+                                       pos = Math.min(pos, text.indexOf("&L"));
+                               }
+                               _right = text.substring(2, pos);
                                text = text.substring(pos);
                                break;
-                   default: 
-                       text = null;
-                   }
+                       default:
+                               throw new IllegalStateException("bad text '" + getRawText() + "'.");
+                       }
                }
+               return new String[] { _left, _center, _right, };
        }
-       
-    /**
-     * Get the left side of the header or footer.
-     * @return The string representing the left side.
-     */
-    public String getLeft() {
-       if(stripFields)
-               return stripFields(left);
-               return left;
+
+       /**
+        * @return the left side of the header or footer.
+        */
+       public final String getLeft() {
+               return splitParts()[0];
+       }
+
+       /**
+        * @param newLeft The string to set as the left side.
+        */
+       public final void setLeft(String newLeft) {
+               updatePart(0, newLeft); 
+       }
+
+       /**
+        * @return the center of the header or footer.
+        */
+       public final String getCenter() {
+               return splitParts()[1];
+       }
+
+       /**
+        * @param newCenter The string to set as the center.
+        */
+       public final void setCenter(String newCenter) {
+               updatePart(1, newCenter); 
+       }
+
+       /**
+        * @return The right side of the header or footer.
+        */
+       public final String getRight() {
+               return splitParts()[2];
+       }
+
+       /**
+        * @param newRight The string to set as the right side.
+        */
+       public final void setRight(String newRight) {
+               updatePart(2, newRight); 
        }
-    public abstract void setLeft( String newLeft );
-
-    /**
-     * Get the center of the header or footer.
-     * @return The string representing the center.
-     */
-    public String getCenter() {
-       if(stripFields)
-               return stripFields(center);
-       return center;
-    }
-    public abstract void setCenter( String newCenter );
-
-    /**
-     * Get the right side of the header or footer.
-     * @return The string representing the right side.
-     */
-    public String getRight() {
-       if(stripFields)
-               return stripFields(right);
-       return right;
-    }
-    public abstract void setRight( String newRight );
-
-
-    /**
-     * Returns the string that represents the change in font size.
-     *
-     * @param size the new font size
-     * @return The special string to represent a new font size
-     */
-    public static String fontSize( short size )
-    {
-        return "&" + size;
-    }
-
-    /**
-     * Returns the string that represents the change in font.
-     *
-     * @param font  the new font
-     * @param style the fonts style, one of regular, italic, bold, italic bold or bold italic
-     * @return The special string to represent a new font size
-     */
-    public static String font( String font, String style )
-    {
-        return "&\"" + font + "," + style + "\"";
-    }
-
-    /**
-     * Returns the string representing the current page number
-     *
-     * @return The special string for page number
-     */
-    public static String page() {
-       return PAGE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the number of pages.
-     *
-     * @return The special string for the number of pages
-     */
-    public static String numPages() {
-       return NUM_PAGES_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the current date
-     *
-     * @return The special string for the date
-     */
-    public static String date() {
-       return DATE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the current time
-     *
-     * @return The special string for the time
-     */
-    public static String time() {
-       return TIME_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the current file name
-     *
-     * @return The special string for the file name
-     */
-    public static String file() {
-       return FILE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the current tab (sheet) name
-     *
-     * @return The special string for tab name
-     */
-    public static String tab() {
-       return SHEET_NAME_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the start bold
-     *
-     * @return The special string for start bold
-     */
-    public static String startBold() {
-       return BOLD_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the end bold
-     *
-     * @return The special string for end bold
-     */
-    public static String endBold() {
-       return BOLD_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the start underline
-     *
-     * @return The special string for start underline
-     */
-    public static String startUnderline() {
-       return UNDERLINE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the end underline
-     *
-     * @return The special string for end underline
-     */
-    public static String endUnderline() {
-       return UNDERLINE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the start double underline
-     *
-     * @return The special string for start double underline
-     */
-    public static String startDoubleUnderline() {
-       return DOUBLE_UNDERLINE_FIELD.sequence;
-    }
-
-    /**
-     * Returns the string representing the end double underline
-     *
-     * @return The special string for end double underline
-     */
-    public static String endDoubleUnderline() {
-       return DOUBLE_UNDERLINE_FIELD.sequence;
-    }
-    
-    
-    /**
-     * Removes any fields (eg macros, page markers etc)
-     *  from the string.
-     * Normally used to make some text suitable for showing
-     *  to humans, and the resultant text should not normally
-     *  be saved back into the document!
-     */
-    public static String stripFields(String text) {
-       int pos;
-       
-       // Check we really got something to work on
-       if(text == null || text.length() == 0) {
-               return text;
-       }
-       
-       // Firstly, do the easy ones which are static
-       for(int i=0; i<Field.ALL_FIELDS.size(); i++) {
-               String seq = ((Field)Field.ALL_FIELDS.get(i)).sequence;
-               while((pos = text.indexOf(seq)) > -1) {
-                       text = text.substring(0, pos) +
-                               text.substring(pos+seq.length());
-               }
-       }
-       
-       // Now do the tricky, dynamic ones
-       // These are things like font sizes and font names
-       text = text.replaceAll("\\&\\d+", "");
-       text = text.replaceAll("\\&\".*?,.*?\"", "");
-       
-       // All done
-       return text;
-    }
-    
        
+       private void updatePart(int partIndex, String newValue) {
+               String[] parts = splitParts();
+               parts[partIndex] = newValue == null ? "" : newValue;
+               updateHeaderFooterText(parts);
+       }
+       /**
+        * Creates the complete footer string based on the left, center, and middle
+        * strings.
+        */
+       private void updateHeaderFooterText(String[] parts) {
+               String _left = parts[0];
+               String _center = parts[1];
+               String _right = parts[2];
+               
+               if (_center.length() < 1 && _left.length() < 1 && _right.length() < 1) {
+                       setHeaderFooterText("");
+                       return;
+               }
+               StringBuilder sb = new StringBuilder(64);
+               sb.append("&C");
+               sb.append(_center);
+               sb.append("&L");
+               sb.append(_left);
+               sb.append("&R");
+               sb.append(_right);
+               String text = sb.toString();
+               setHeaderFooterText(text);
+       }
+
+       /**
+        * @param text the new header footer text (contains mark-up tags). Possibly
+        *            empty string never <code>null</code>
+        */
+       protected abstract void setHeaderFooterText(String text);
+
+       /**
+        * @param size
+        *            the new font size
+        * @return The mark-up tag representing a new font size
+        */
+       public static String fontSize(short size) {
+               return "&" + size;
+       }
+
+       /**
+        * @param font
+        *            the new font
+        * @param style
+        *            the fonts style, one of regular, italic, bold, italic bold or
+        *            bold italic
+        * @return The mark-up tag representing a new font size
+        */
+       public static String font(String font, String style) {
+               return "&\"" + font + "," + style + "\"";
+       }
+
+       /**
+        * @return The mark-up tag representing the current page number
+        */
+       public static String page() {
+               return MarkupTag.PAGE_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag representing the number of pages
+        */
+       public static String numPages() {
+               return MarkupTag.NUM_PAGES_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag representing the current date date
+        */
+       public static String date() {
+               return MarkupTag.DATE_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag representing current time
+        */
+       public static String time() {
+               return MarkupTag.TIME_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag representing the current file name
+        */
+       public static String file() {
+               return MarkupTag.FILE_FIELD.getRepresentation();
+       }
+
        /**
-        * Are fields currently being stripped from
-        *  the text that this {@link HeaderFooter} returns?
-        *  Default is false, but can be changed
+        * @return The mark-up tag representing the current tab (sheet) name
         */
-       public boolean areFieldsStripped() {
-               return stripFields;
+       public static String tab() {
+               return MarkupTag.SHEET_NAME_FIELD.getRepresentation();
        }
+
+       /**
+        * @return The mark-up tag for start bold
+        */
+       public static String startBold() {
+               return MarkupTag.BOLD_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag for end bold
+        */
+       public static String endBold() {
+               return MarkupTag.BOLD_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag for start underline
+        */
+       public static String startUnderline() {
+               return MarkupTag.UNDERLINE_FIELD.getRepresentation();
+       }
+
        /**
-        * Should fields (eg macros) be stripped from
-        *  the text that this class returns?
-        * Default is not to strip.
-        * @param stripFields
+        * @return The mark-up tag for end underline
         */
-       public void setAreFieldsStripped(boolean stripFields) {
-               this.stripFields = stripFields;
+       public static String endUnderline() {
+               return MarkupTag.UNDERLINE_FIELD.getRepresentation();
        }
 
-    
-    public static final Field SHEET_NAME_FIELD = new Field("&A");
-    public static final Field DATE_FIELD = new Field("&D");
-    public static final Field FILE_FIELD = new Field("&F");
-    public static final Field FULL_FILE_FIELD = new Field("&Z");
-    public static final Field PAGE_FIELD = new Field("&P");
-    public static final Field TIME_FIELD = new Field("&T");
-    public static final Field NUM_PAGES_FIELD = new Field("&N");
-    
-    public static final Field PICTURE_FIELD = new Field("&G");
-    
-    public static final PairField BOLD_FIELD = new PairField("&B");
-    public static final PairField ITALIC_FIELD = new PairField("&I");
-    public static final PairField STRIKETHROUGH_FIELD = new PairField("&S");
-    public static final PairField SUBSCRIPT_FIELD = new PairField("&Y");
-    public static final PairField SUPERSCRIPT_FIELD = new PairField("&X");
-    public static final PairField UNDERLINE_FIELD = new PairField("&U");
-    public static final PairField DOUBLE_UNDERLINE_FIELD = new PairField("&E");
-    
-    /**
-     * Represents a special field in a header or footer,
-     *  eg the page number
-     */
-    public static class Field {
-       private static ArrayList ALL_FIELDS = new ArrayList();
-       /** The character sequence that marks this field */
-       public final String sequence;
-       private Field(String sequence) {
-               this.sequence = sequence;
-               ALL_FIELDS.add(this);
-       }
-    }
-    /**
-     * A special field that normally comes in a pair, eg
-     *  turn on underline / turn off underline
-     */
-    public static class PairField extends Field {
-       private PairField(String sequence) {
-               super(sequence);
-       }
-    }
+       /**
+        * @return The mark-up tag for start double underline
+        */
+       public static String startDoubleUnderline() {
+               return MarkupTag.DOUBLE_UNDERLINE_FIELD.getRepresentation();
+       }
+
+       /**
+        * @return The mark-up tag for end double underline
+        */
+       public static String endDoubleUnderline() {
+               return MarkupTag.DOUBLE_UNDERLINE_FIELD.getRepresentation();
+       }
+
+       /**
+        * Removes any fields (eg macros, page markers etc) from the string.
+        * Normally used to make some text suitable for showing to humans, and the
+        * resultant text should not normally be saved back into the document!
+        */
+       public static String stripFields(String pText) {
+               int pos;
+
+               // Check we really got something to work on
+               if (pText == null || pText.length() == 0) {
+                       return pText;
+               }
+
+               String text = pText;
+
+               // Firstly, do the easy ones which are static
+               for (MarkupTag mt : MarkupTag.values()) {
+                       String seq = mt.getRepresentation();
+                       while ((pos = text.indexOf(seq)) > -1) {
+                               text = text.substring(0, pos) + text.substring(pos + seq.length());
+                       }
+               }
+
+               // Now do the tricky, dynamic ones
+               // These are things like font sizes and font names
+               text = text.replaceAll("\\&\\d+", "");
+               text = text.replaceAll("\\&\".*?,.*?\"", "");
+
+               // All done
+               return text;
+       }
+
+       private enum MarkupTag {
+               SHEET_NAME_FIELD ("&A", false),
+               DATE_FIELD       ("&D", false),
+               FILE_FIELD       ("&F", false),
+               FULL_FILE_FIELD  ("&Z", false),
+               PAGE_FIELD       ("&P", false),
+               TIME_FIELD       ("&T", false),
+               NUM_PAGES_FIELD  ("&N", false),
+
+               PICTURE_FIELD    ("&G", false),
+
+               BOLD_FIELD             ("&B", true),
+               ITALIC_FIELD           ("&I", true),
+               STRIKETHROUGH_FIELD    ("&S", true),
+               SUBSCRIPT_FIELD        ("&Y", true),
+               SUPERSCRIPT_FIELD      ("&X", true),
+               UNDERLINE_FIELD        ("&U", true),
+               DOUBLE_UNDERLINE_FIELD ("&E", true),
+               ;
+               
+               private final String _representation;
+               private final boolean _occursInPairs;
+               private MarkupTag(String sequence, boolean occursInPairs) {
+                       _representation = sequence;
+                       _occursInPairs = occursInPairs;
+               }
+               /**
+                * @return The character sequence that marks this field
+                */
+               public String getRepresentation() {
+                       return _representation;
+               }
+
+               /**
+                * @return true if this markup tag normally comes in a pair, eg turn on
+                *         underline / turn off underline
+                */
+               public boolean occursPairs() {
+                       return _occursInPairs;
+               }
+       }
 }
index ffeb96abda8d80f7b2f3e740a832d8bf2c7387e4..1ec70bd3d790e7a23e634ec2add9f9b20e6ed72f 100644 (file)
@@ -182,7 +182,7 @@ public interface Sheet extends Iterable<Row> {
      * @param height default row height
      */
     void setDefaultRowHeightInPoints(float height);
-    
+
     /**
      * Returns the CellStyle that applies to the given
      *  (0 based) column, or null if no style has been
@@ -275,7 +275,7 @@ public interface Sheet extends Iterable<Row> {
     /**
      * Set whether the window should show 0 (zero) in cells containing zero value.
      * When false, cells with zero value appear blank instead of showing the number zero.
-     * 
+     *
      * @param value whether to display or hide all zero values on the worksheet
      */
     void setDisplayZeros(boolean value);
@@ -407,18 +407,19 @@ public interface Sheet extends Iterable<Row> {
 
     /**
      * Gets the user model for the default document header.
-     * <p>
+     * <p/>
      * Note that XSSF offers more kinds of document headers than HSSF does
      * </p>
-     * @return the document header.
+     * @return the document header. Never <code>null</code>
      */
     Header getHeader();
 
     /**
      * Gets the user model for the default document footer.
+     * <p/>
      * Note that XSSF offers more kinds of document footers than HSSF does.
      *
-     * @return the document footer.
+     * @return the document footer. Never <code>null</code>
      */
     Footer getFooter();
 
@@ -473,7 +474,7 @@ public interface Sheet extends Iterable<Row> {
     void setZoom(int numerator, int denominator);
 
     /**
-     * The top row in the visible view when the sheet is 
+     * The top row in the visible view when the sheet is
      * first viewed after opening it in a viewer
      *
      * @return short indicating the rownum (0 based) of the top row
@@ -481,7 +482,7 @@ public interface Sheet extends Iterable<Row> {
     short getTopRow();
 
     /**
-     * The left col in the visible view when the sheet is 
+     * The left col in the visible view when the sheet is
      * first viewed after opening it in a viewer
      *
      * @return short indicating the rownum (0 based) of the top row
@@ -489,7 +490,7 @@ public interface Sheet extends Iterable<Row> {
     short getLeftCol();
 
     /**
-     * Sets desktop window pane display area, when the 
+     * Sets desktop window pane display area, when the
      * file is first opened in a viewer.
      *
      * @param toprow the top row to show in desktop window pane
diff --git a/src/testcases/org/apache/poi/hssf/data/noHeaderFooter47244.xls b/src/testcases/org/apache/poi/hssf/data/noHeaderFooter47244.xls
new file mode 100644 (file)
index 0000000..1323ab3
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/noHeaderFooter47244.xls differ
index 9380b6d2f8e9924029bf1eb8d474388af8f03d60..f3730efd7fb51b0f3f812af63ff93c9ea31f0842 100644 (file)
@@ -30,12 +30,14 @@ import org.apache.poi.hssf.record.BottomMarginRecord;
 import org.apache.poi.hssf.record.DimensionsRecord;
 import org.apache.poi.hssf.record.EOFRecord;
 import org.apache.poi.hssf.record.FooterRecord;
+import org.apache.poi.hssf.record.HCenterRecord;
 import org.apache.poi.hssf.record.HeaderRecord;
 import org.apache.poi.hssf.record.IndexRecord;
 import org.apache.poi.hssf.record.NumberRecord;
 import org.apache.poi.hssf.record.Record;
 import org.apache.poi.hssf.record.RecordFormatException;
 import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.VCenterRecord;
 import org.apache.poi.hssf.record.WindowTwoRecord;
 import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
 import org.apache.poi.hssf.usermodel.HSSFSheet;
@@ -236,4 +238,39 @@ public final class TestPageSettingsBlock extends TestCase {
        private static UnknownRecord ur(int sid, String hexData) {
                return new UnknownRecord(sid, HexRead.readFromString(hexData));
        }
+       
+       /**
+        * Excel tolerates missing header / footer records, but adds them (empty) in when re-saving.
+        * This is not critical functionality but it has been decided to keep POI consistent with
+        * Excel in this regard.
+        */
+       public void testMissingHeaderFooter() {
+               // initialise PSB with some records, but not the header / footer
+               Record[] recs = {
+                               new HCenterRecord(),
+                               new VCenterRecord(),
+               };
+               RecordStream rs = new RecordStream(Arrays.asList(recs), 0);
+               PageSettingsBlock psb = new PageSettingsBlock(rs);
+
+               // serialize the PSB to see what records come out
+               RecordCollector rc = new RecordCollector();
+               psb.visitContainedRecords(rc);
+               Record[] outRecs = rc.getRecords();
+               
+               if (outRecs.length == 2) {
+                       throw new AssertionFailedError("PageSettingsBlock didn't add missing header/footer records");
+               }
+               assertEquals(4, outRecs.length);
+               assertEquals(HeaderRecord.class, outRecs[0].getClass());
+               assertEquals(FooterRecord.class, outRecs[1].getClass());
+               assertEquals(HCenterRecord.class, outRecs[2].getClass());
+               assertEquals(VCenterRecord.class, outRecs[3].getClass());
+               
+               // make sure the added header / footer records are empty 
+               HeaderRecord hr = (HeaderRecord) outRecs[0];
+               assertEquals("", hr.getText());
+               FooterRecord fr = (FooterRecord) outRecs[1];
+               assertEquals("", fr.getText());
+       }
 }
index ae650432713d123766077ca43068bb07903c4f51..771dd838b72fc6d31db958957f475d864a0f9f5b 100644 (file)
 
 package org.apache.poi.hssf.usermodel;
 
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 
 import junit.framework.AssertionFailedError;
-import junit.framework.TestCase;
 
+import org.apache.poi.hssf.HSSFITestDataProvider;
 import org.apache.poi.hssf.HSSFTestDataSamples;
 import org.apache.poi.hssf.OldExcelFormatException;
-import org.apache.poi.hssf.HSSFITestDataProvider;
 import org.apache.poi.hssf.model.Workbook;
 import org.apache.poi.hssf.record.CellValueRecordInterface;
 import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord;
@@ -34,11 +38,7 @@ import org.apache.poi.hssf.record.NameRecord;
 import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate;
 import org.apache.poi.hssf.record.formula.DeletedArea3DPtg;
 import org.apache.poi.hssf.record.formula.Ptg;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.RichTextString;
-import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues;
-import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.util.TempFile;
 
 /**
@@ -1247,16 +1247,18 @@ public final class TestBugs extends BaseTestBugzillaIssues {
     }
 
     /**
-     * header / footer text too long
+     * The resolution for bug 45777 assumed that the maximum text length in a header / footer
+     * record was 256 bytes.  This assumption appears to be wrong.  Since the fix for bug 47244,
+     * POI now supports header / footer text lengths beyond 256 bytes.  
      */
     public void test45777() {
         HSSFWorkbook wb = new HSSFWorkbook();
         HSSFSheet s = wb.createSheet();
 
-        String s248 = "";
-        for(int i=0; i<248; i++) {
-            s248 += "x";
-        }
+        char[] cc248 = new char[248];
+        Arrays.fill(cc248, 'x');
+        String s248 = new String(cc248);
+
         String s249 = s248 + "1";
         String s250 = s248 + "12";
         String s251 = s248 + "123";
@@ -1268,42 +1270,45 @@ public final class TestBugs extends BaseTestBugzillaIssues {
 
         // Try on headers
         s.getHeader().setCenter(s248);
-        assertEquals(254, s.getHeader().getRawHeader().length());
+        assertEquals(254, s.getHeader().getRawText().length());
         writeOutAndReadBack(wb);
 
-        s.getHeader().setCenter(s249);
-        assertEquals(255, s.getHeader().getRawHeader().length());
+        s.getHeader().setCenter(s251);
+        assertEquals(257, s.getHeader().getRawText().length());
         writeOutAndReadBack(wb);
 
         try {
-            s.getHeader().setCenter(s250); // 256
-            fail();
-        } catch(IllegalArgumentException e) {}
+            s.getHeader().setCenter(s250); // 256 bytes required
+        } catch(IllegalArgumentException e) {
+            throw new AssertionFailedError("Identified bug 47244b - header can be more than 256 bytes");
+        }
 
         try {
-            s.getHeader().setCenter(s251); // 257
-            fail();
-        } catch(IllegalArgumentException e) {}
-
+            s.getHeader().setCenter(s251); // 257 bytes required
+        } catch(IllegalArgumentException e) {
+            throw new AssertionFailedError("Identified bug 47244b - header can be more than 256 bytes");
+        }
 
         // Now try on footers
         s.getFooter().setCenter(s248);
-        assertEquals(254, s.getFooter().getRawFooter().length());
+        assertEquals(254, s.getFooter().getRawText().length());
         writeOutAndReadBack(wb);
 
-        s.getFooter().setCenter(s249);
-        assertEquals(255, s.getFooter().getRawFooter().length());
+        s.getFooter().setCenter(s251);
+        assertEquals(257, s.getFooter().getRawText().length());
         writeOutAndReadBack(wb);
 
         try {
-            s.getFooter().setCenter(s250); // 256
-            fail();
-        } catch(IllegalArgumentException e) {}
+            s.getFooter().setCenter(s250); // 256 bytes required
+        } catch(IllegalArgumentException e) {
+            throw new AssertionFailedError("Identified bug 47244b - footer can be more than 256 bytes");
+        }
 
         try {
-            s.getFooter().setCenter(s251); // 257
-            fail();
-        } catch(IllegalArgumentException e) {}
+            s.getFooter().setCenter(s251); // 257 bytes required
+        } catch(IllegalArgumentException e) {
+            throw new AssertionFailedError("Identified bug 47244b - footer can be more than 256 bytes");
+        }
     }
 
     /**
@@ -1469,24 +1474,24 @@ public final class TestBugs extends BaseTestBugzillaIssues {
      * java.io.IOException: block[ 0 ] already removed
      * (is an excel 95 file though)
      */
-    public void test46904() throws IOException {
-       try {
-               HSSFWorkbook wb = openSample("46904.xls");
-               fail();
-       } catch(OldExcelFormatException e) {
-               assertTrue(e.getMessage().startsWith(
-                               "The supplied spreadsheet seems to be Excel"
-               ));
-       }
+    public void test46904() {
+        try {
+            openSample("46904.xls");
+            fail();
+        } catch(OldExcelFormatException e) {
+            assertTrue(e.getMessage().startsWith(
+                    "The supplied spreadsheet seems to be Excel"
+            ));
+        }
     }
     
     /**
      * java.lang.NegativeArraySizeException reading long
      *  non-unicode data for a name record
      */
-    public void test47034() throws IOException {
-               HSSFWorkbook wb = openSample("47034.xls");
-               assertEquals(893, wb.getNumberOfNames());
-               assertEquals("Matthew\\Matthew11_1\\Matthew2331_1\\Matthew2351_1\\Matthew2361_1___lab", wb.getNameName(300));
+    public void test47034() {
+        HSSFWorkbook wb = openSample("47034.xls");
+        assertEquals(893, wb.getNumberOfNames());
+        assertEquals("Matthew\\Matthew11_1\\Matthew2331_1\\Matthew2351_1\\Matthew2361_1___lab", wb.getNameName(300));
     }
 }
index 3ae1efcaaba00066b25632c40b1facaac13d6859..d2e820013296a735738d7eb9cbf491d9fac9e5df 100644 (file)
 
 package org.apache.poi.hssf.usermodel;
 
+import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 
 import org.apache.poi.hssf.HSSFTestDataSamples;
 
 /**
- * Tests row shifting capabilities.
- *
+ * Tests for {@link HSSFHeader} / {@link HSSFFooter}
  *
  * @author Shawn Laubach (slaubach at apache dot com)
  */
 public final class TestHSSFHeaderFooter extends TestCase {
 
        /**
-        * Tests that get header retreives the proper values.
+        * Tests that get header retrieves the proper values.
         *
         * @author Shawn Laubach (slaubach at apache dot org)
         */
        public void testRetrieveCorrectHeader() {
-               // Read initial file in
+
                HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("EmbeddedChartHeaderTest.xls");
                HSSFSheet s = wb.getSheetAt( 0 );
-       HSSFHeader head = s.getHeader();
-    
-       assertEquals("Top Left", head.getLeft());
-       assertEquals("Top Center", head.getCenter());
-       assertEquals("Top Right", head.getRight());
+               HSSFHeader head = s.getHeader();
+
+               assertEquals("Top Left", head.getLeft());
+               assertEquals("Top Center", head.getCenter());
+               assertEquals("Top Right", head.getRight());
        }
-       
+
        public void testSpecialChars() {
                assertEquals("&U", HSSFHeader.startUnderline());
                assertEquals("&U", HSSFHeader.endUnderline());
                assertEquals("&P", HSSFHeader.page());
-               
+
                assertEquals("&22", HSSFFooter.fontSize((short)22));
                assertEquals("&\"Arial,bold\"", HSSFFooter.font("Arial", "bold"));
        }
-       
+
        public void testStripFields() {
                String simple = "I am a test header";
                String withPage = "I am a&P test header";
@@ -61,54 +61,51 @@ public final class TestHSSFHeaderFooter extends TestCase {
                String withFont = "I&22 am a&\"Arial,bold\" test header";
                String withOtherAnds = "I am a&P test header&&";
                String withOtherAnds2 = "I am a&P test header&a&b";
-               
+
                assertEquals(simple, HSSFHeader.stripFields(simple));
                assertEquals(simple, HSSFHeader.stripFields(withPage));
                assertEquals(simple, HSSFHeader.stripFields(withLots));
                assertEquals(simple, HSSFHeader.stripFields(withFont));
                assertEquals(simple + "&&", HSSFHeader.stripFields(withOtherAnds));
                assertEquals(simple + "&a&b", HSSFHeader.stripFields(withOtherAnds2));
-               
+
                // Now test the default strip flag
                HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("EmbeddedChartHeaderTest.xls");
                HSSFSheet s = wb.getSheetAt( 0 );
-       HSSFHeader head = s.getHeader();
-    
-       assertEquals("Top Left", head.getLeft());
-       assertEquals("Top Center", head.getCenter());
-       assertEquals("Top Right", head.getRight());
-       
-       head.setLeft("Top &P&F&D Left");
-       assertEquals("Top &P&F&D Left", head.getLeft());
-       assertFalse(head.areFieldsStripped());
-       
-       head.setAreFieldsStripped(true);
-       assertEquals("Top  Left", head.getLeft());
-       assertTrue(head.areFieldsStripped());
-       
-       // Now even more complex
-       head.setCenter("HEADER TEXT &P&N&D&T&Z&F&F&A&G&X END");
-       assertEquals("HEADER TEXT  END", head.getCenter());
+               HSSFHeader head = s.getHeader();
+
+               assertEquals("Top Left", head.getLeft());
+               assertEquals("Top Center", head.getCenter());
+               assertEquals("Top Right", head.getRight());
+
+               head.setLeft("Top &P&F&D Left");
+               assertEquals("Top &P&F&D Left", head.getLeft());
+
+               assertEquals("Top  Left", HeaderFooter.stripFields(head.getLeft()));
+
+               // Now even more complex
+               head.setCenter("HEADER TEXT &P&N&D&T&Z&F&F&A&G&X END");
+               assertEquals("HEADER TEXT  END",  HeaderFooter.stripFields(head.getCenter()));
        }
 
        /**
-        * Tests that get header retreives the proper values.
+        * Tests that get header retrieves the proper values.
         *
         * @author Shawn Laubach (slaubach at apache dot org)
         */
        public void testRetrieveCorrectFooter() {
-               // Read initial file in
+
                HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("EmbeddedChartHeaderTest.xls");
-               HSSFSheet s = wb.getSheetAt( 0 );
-       HSSFFooter foot = s.getFooter();
-    
-       assertEquals("Bottom Left", foot.getLeft());
-       assertEquals("Bottom Center", foot.getCenter());
-       assertEquals("Bottom Right", foot.getRight());
+               HSSFSheet s = wb.getSheetAt(0);
+               HSSFFooter foot = s.getFooter();
+
+               assertEquals("Bottom Left", foot.getLeft());
+               assertEquals("Bottom Center", foot.getCenter());
+               assertEquals("Bottom Right", foot.getRight());
        }
-       
+
        /**
-        * Testcase for Bug 17039 HSSFHeader  doesnot support DBCS 
+        * Testcase for Bug 17039 HSSFHeader  does not support DBCS
         */
        public void testHeaderHas16bitCharacter() {
                HSSFWorkbook b = new HSSFWorkbook();
@@ -117,17 +114,17 @@ public final class TestHSSFHeaderFooter extends TestCase {
                h.setLeft("\u0391");
                h.setCenter("\u0392");
                h.setRight("\u0393");
-               
+
                HSSFWorkbook b2 = HSSFTestDataSamples.writeOutAndReadBack(b);
                HSSFHeader h2 = b2.getSheet("Test").getHeader();
-               
+
                assertEquals(h2.getLeft(),"\u0391");
                assertEquals(h2.getCenter(),"\u0392");
                assertEquals(h2.getRight(),"\u0393");
        }
-       
+
        /**
-        * Testcase for Bug 17039 HSSFFooter doesnot support DBCS 
+        * Testcase for Bug 17039 HSSFFooter does not support DBCS
         */
         public void testFooterHas16bitCharacter() {
                HSSFWorkbook b = new HSSFWorkbook();
@@ -136,10 +133,10 @@ public final class TestHSSFHeaderFooter extends TestCase {
                f.setLeft("\u0391");
                f.setCenter("\u0392");
                f.setRight("\u0393");
-               
+
                HSSFWorkbook b2 = HSSFTestDataSamples.writeOutAndReadBack(b);
                HSSFFooter f2 = b2.getSheet("Test").getFooter();
-               
+
                assertEquals(f2.getLeft(),"\u0391");
                assertEquals(f2.getCenter(),"\u0392");
                assertEquals(f2.getRight(),"\u0393");
@@ -152,11 +149,38 @@ public final class TestHSSFHeaderFooter extends TestCase {
                assertEquals("Header Left " ,h.getLeft(),"\u090f\u0915");
                assertEquals("Header Center " ,h.getCenter(),"\u0939\u094b\u0917\u093e");
                assertEquals("Header Right " ,h.getRight(),"\u091c\u093e");
-               
+
                HSSFFooter f = s.getFooter();
                assertEquals("Footer Left " ,f.getLeft(),"\u091c\u093e");
                assertEquals("Footer Center " ,f.getCenter(),"\u091c\u093e");
                assertEquals("Footer Right " ,f.getRight(),"\u091c\u093e");
        }
-}
 
+       /**
+        * Excel tolerates files with missing HEADER/FOOTER records.  POI should do the same.
+        */
+       public void testMissingHeaderFooterRecord_bug47244() {
+               // noHeaderFooter47244.xls was created by a slightly modified POI
+               // which omitted the HEADER/FOOTER records
+               HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("noHeaderFooter47244.xls");
+               HSSFSheet sheet = wb.getSheetAt(0);
+               HSSFFooter footer;
+               try {
+                       footer = sheet.getFooter();
+               } catch (NullPointerException e) {
+                       throw new AssertionFailedError("Identified bug 47244a");
+               }
+               assertEquals("", footer.getRawText());
+               HSSFHeader header = sheet.getHeader();
+               assertEquals("", header.getRawText());
+
+               // make sure header / footer is properly linked to underlying data 
+               HSSFHeader header2 = sheet.getHeader();
+               header.setCenter("foo");
+               assertEquals("foo", header2.getCenter());
+
+               HSSFFooter footer2 = sheet.getFooter();
+               footer.setCenter("bar");
+               assertEquals("bar", footer2.getCenter());
+       }
+}