<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>
}
// 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();
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()));
}
}
*
*/
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());
+ }
}
*
* @author Josh Micich
*/
-abstract class HeaderFooterBase extends StandardRecord {
+public abstract class HeaderFooterBase extends StandardRecord {
private boolean field_2_hasMultibyte;
private String field_3_text;
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 = "";
}
}
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)");
}
}
package org.apache.poi.hssf.record;
-
/**
* Title: Header Record<P>
* Description: Specifies a header for a sheet<P>
* @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());
+ }
}
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);
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;
/**
* <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);
+ }
+ }
}
-
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;
/**
*
* @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);
+ }
+ }
}
-
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());
}
/**
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;
+ }
+ }
}
* @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
/**
* 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);
/**
* 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();
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
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
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
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;
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());
+ }
}
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;
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;
/**
}
/**
- * 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";
// 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");
+ }
}
/**
* 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));
}
}
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";
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();
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();
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");
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());
+ }
+}