Fix bug #45540 - Fix XSSF header and footer support, and include headers and footers in the output of XSSFExcelExtractor

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@682674 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-08-05 11:26:38 +00:00
parent cd56a91ef9
commit b118d1afb9
8 changed files with 207 additions and 73 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.5.1-beta2" date="2008-??-??"> <release version="3.5.1-beta2" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45540 - Fix XSSF header and footer support, and include headers and footers in the output of XSSFExcelExtractor</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action> <action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action>
<action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action> <action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action> <action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.5.1-beta2" date="2008-??-??"> <release version="3.5.1-beta2" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45540 - Fix XSSF header and footer support, and include headers and footers in the output of XSSFExcelExtractor</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action> <action dev="POI-DEVELOPERS" type="add">45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning</action>
<action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action> <action dev="POI-DEVELOPERS" type="add">New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from</action>
<action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action> <action dev="POI-DEVELOPERS" type="add">45431 - Partial support for .xlm files. Not quite enough for excel to load them though</action>

View File

@ -25,9 +25,8 @@ import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Comment; import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.HeaderFooter; import org.apache.poi.ss.usermodel.HeaderFooter;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.openxml4j.exceptions.OpenXML4JException; import org.openxml4j.exceptions.OpenXML4JException;
@ -37,7 +36,7 @@ import org.openxml4j.opc.Package;
* Helper class to extract text from an OOXML Excel file * Helper class to extract text from an OOXML Excel file
*/ */
public class XSSFExcelExtractor extends POIXMLTextExtractor { public class XSSFExcelExtractor extends POIXMLTextExtractor {
private Workbook workbook; private XSSFWorkbook workbook;
private boolean includeSheetNames = true; private boolean includeSheetNames = true;
private boolean formulasNotResults = false; private boolean formulasNotResults = false;
private boolean includeCellComments = false; private boolean includeCellComments = false;
@ -91,18 +90,23 @@ public class XSSFExcelExtractor extends POIXMLTextExtractor {
StringBuffer text = new StringBuffer(); StringBuffer text = new StringBuffer();
for(int i=0; i<workbook.getNumberOfSheets(); i++) { for(int i=0; i<workbook.getNumberOfSheets(); i++) {
Sheet sheet = workbook.getSheetAt(i); XSSFSheet sheet = (XSSFSheet)workbook.getSheetAt(i);
if(includeSheetNames) { if(includeSheetNames) {
text.append(workbook.getSheetName(i) + "\n"); text.append(workbook.getSheetName(i) + "\n");
} }
// Header, if present // Header(s), if present
if(sheet.getHeader() != null) { text.append(
text.append( extractHeaderFooter(sheet.getFirstHeader())
extractHeaderFooter(sheet.getHeader()) );
); text.append(
} extractHeaderFooter(sheet.getOddHeader())
);
text.append(
extractHeaderFooter(sheet.getEvenHeader())
);
// Rows and cells
for (Object rawR : sheet) { for (Object rawR : sheet) {
Row row = (Row)rawR; Row row = (Row)rawR;
for(Iterator<Cell> ri = row.cellIterator(); ri.hasNext();) { for(Iterator<Cell> ri = row.cellIterator(); ri.hasNext();) {
@ -133,12 +137,16 @@ public class XSSFExcelExtractor extends POIXMLTextExtractor {
text.append("\n"); text.append("\n");
} }
// Finally footer, if present // Finally footer(s), if present
if(sheet.getFooter() != null) { text.append(
text.append( extractHeaderFooter(sheet.getFirstFooter())
extractHeaderFooter(sheet.getFooter()) );
); text.append(
} extractHeaderFooter(sheet.getOddFooter())
);
text.append(
extractHeaderFooter(sheet.getEvenFooter())
);
} }
return text.toString(); return text.toString();

View File

@ -24,21 +24,21 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHeaderFooter;
public abstract class XSSFHeaderFooter implements HeaderFooter { public abstract class XSSFHeaderFooter implements HeaderFooter {
private HeaderFooterHelper helper; private HeaderFooterHelper helper;
private CTHeaderFooter headerFooter; private CTHeaderFooter headerFooter;
private String value;
public XSSFHeaderFooter(CTHeaderFooter headerFooter) { public XSSFHeaderFooter(CTHeaderFooter headerFooter) {
this.headerFooter = headerFooter; this.headerFooter = headerFooter;
this.value = getText();
this.value = this.value != null ? this.value : "";
this.helper = new HeaderFooterHelper(); this.helper = new HeaderFooterHelper();
} }
public CTHeaderFooter getHeaderFooter() { public CTHeaderFooter getHeaderFooter() {
return this.headerFooter; return this.headerFooter;
} }
public String getValue() { public String getValue() {
return this.value; String value = getText();
if(value == null)
return "";
return value;
} }
public abstract String getText(); public abstract String getText();

View File

@ -19,55 +19,101 @@ package org.apache.poi.xssf.usermodel.helpers;
public class HeaderFooterHelper { public class HeaderFooterHelper {
// Note - XmlBeans handles entity encoding for us,
// so these should be & forms, not the &amp; ones!
private static final String HeaderFooterEntity_L = "&L";
private static final String HeaderFooterEntity_C = "&C";
private static final String HeaderFooterEntity_R = "&R";
private static final String HeaderFooterEntity = "&amp;"; // These are other entities that may be used in the
private static final String HeaderFooterEntity_R = "&amp;R"; // left, center or right. Not exhaustive
private static final String HeaderFooterEntity_L = "&amp;L"; public static final String HeaderFooterEntity_File = "&F";
private static final String HeaderFooterEntity_C = "&amp;C"; public static final String HeaderFooterEntity_Date = "&D";
public static final String HeaderFooterEntity_Time = "&T";
public String getCenterSection(String string) {
return getSection(string, HeaderFooterEntity_C);
}
public String getLeftSection(String string) { public String getLeftSection(String string) {
return getSection(string, HeaderFooterEntity_L); return getParts(string)[0];
}
public String getCenterSection(String string) {
return getParts(string)[1];
} }
public String getRightSection(String string) { public String getRightSection(String string) {
return getSection(string, HeaderFooterEntity_R); return getParts(string)[2];
} }
public String setCenterSection(String string, String newCenter) {
return setSection(string, newCenter, HeaderFooterEntity_C);
}
public String setLeftSection(String string, String newLeft) { public String setLeftSection(String string, String newLeft) {
return setSection(string, newLeft, HeaderFooterEntity_L); String[] parts = getParts(string);
parts[0] = newLeft;
return joinParts(parts);
}
public String setCenterSection(String string, String newCenter) {
String[] parts = getParts(string);
parts[1] = newCenter;
return joinParts(parts);
} }
public String setRightSection(String string, String newRight) { public String setRightSection(String string, String newRight) {
return setSection(string, newRight, HeaderFooterEntity_R); String[] parts = getParts(string);
parts[2] = newRight;
return joinParts(parts);
} }
public String setSection(String string, String newSection, String entity) { /**
string = string != null ? string : ""; * Split into left, center, right
String oldSection = getSection(string, entity); */
if (oldSection.equals("")) { private String[] getParts(String string) {
return string.concat(entity + newSection); String[] parts = new String[] { "", "", "" };
} if(string == null)
return string.replaceAll(entity + oldSection, entity + newSection); return parts;
// They can come in any order, which is just nasty
// Work backwards from the end, picking the last
// on off each time as we go
int lAt = 0;
int cAt = 0;
int rAt = 0;
while(
// Ensure all indicies get updated, then -1 tested
(lAt = string.indexOf(HeaderFooterEntity_L)) > -2 &&
(cAt = string.indexOf(HeaderFooterEntity_C)) > -2 &&
(rAt = string.indexOf(HeaderFooterEntity_R)) > -2 &&
(lAt > -1 || cAt > -1 || rAt > -1)
) {
// Pick off the last one
if(rAt > cAt && rAt > lAt) {
parts[2] = string.substring(rAt + HeaderFooterEntity_R.length());
string = string.substring(0, rAt);
} else if(cAt > rAt && cAt > lAt) {
parts[1] = string.substring(cAt + HeaderFooterEntity_C.length());
string = string.substring(0, cAt);
} else {
parts[0] = string.substring(lAt + HeaderFooterEntity_L.length());
string = string.substring(0, lAt);
}
}
return parts;
} }
private String joinParts(String[] parts) {
private String getSection(String string, String entity) { return joinParts(parts[0], parts[1], parts[2]);
if (string == null) {
return "";
}
String stringAfterEntity = "";
if (string.indexOf(entity) >= 0) {
stringAfterEntity = string.substring(string.indexOf(entity) + entity.length());
}
String nextEntity = "";
if (stringAfterEntity.indexOf(HeaderFooterEntity) > 0) {
nextEntity = stringAfterEntity.substring(stringAfterEntity.indexOf(HeaderFooterEntity), stringAfterEntity.indexOf(HeaderFooterEntity) + (HeaderFooterEntity.length()));
stringAfterEntity = stringAfterEntity.substring(0, stringAfterEntity.indexOf(nextEntity));
}
return stringAfterEntity;
} }
private String joinParts(String l, String c, String r) {
StringBuffer ret = new StringBuffer();
// Join as c, l, r
if(c.length() > 0) {
ret.append(HeaderFooterEntity_C);
ret.append(c);
}
if(l.length() > 0) {
ret.append(HeaderFooterEntity_L);
ret.append(l);
}
if(r.length() > 0) {
ret.append(HeaderFooterEntity_R);
ret.append(r);
}
return ret.toString();
}
} }

View File

@ -192,10 +192,10 @@ public class TestXSSFExcelExtractor extends TestCase {
/** /**
* From bug #45540 * From bug #45540
*/ */
public void BROKENtestHeaderFooter() throws Exception { public void testHeaderFooter() throws Exception {
String[] files = new String[] { String[] files = new String[] {
"45540_classic_Header.xlsx", "45540_form_Header.xlsx",
"45540_classic_Footer.xlsx", "45540_form_Footer.xlsx", "45540_classic_Footer.xlsx", "45540_form_Footer.xlsx",
"45540_classic_Header.xlsx", "45540_form_Header.xlsx"
}; };
for(String file : files) { for(String file : files) {
File xml = new File( File xml = new File(
@ -208,8 +208,8 @@ public class TestXSSFExcelExtractor extends TestCase {
new XSSFExcelExtractor(new XSSFWorkbook(xml.toString())); new XSSFExcelExtractor(new XSSFWorkbook(xml.toString()));
String text = extractor.getText(); String text = extractor.getText();
assertTrue("Unable to find expected word in text\n" + text, text.contains("testdoc")); assertTrue("Unable to find expected word in text from " + file + "\n" + text, text.contains("testdoc"));
assertTrue("Unable to find expected word in text\n" + text, text.contains("test phrase")); assertTrue("Unable to find expected word in text\n" + text, text.contains("test phrase"));
} }
} }
} }

View File

@ -17,6 +17,9 @@
package org.apache.poi.xssf.usermodel; package org.apache.poi.xssf.usermodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Iterator; import java.util.Iterator;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
@ -27,6 +30,7 @@ import org.apache.poi.ss.util.Region;
import org.apache.poi.xssf.model.CommentsTable; import org.apache.poi.xssf.model.CommentsTable;
import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.helpers.ColumnHelper; import org.apache.poi.xssf.usermodel.helpers.ColumnHelper;
import org.openxml4j.opc.Package;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCol;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCols; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCols;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTComment; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTComment;
@ -291,6 +295,74 @@ public class TestXSSFSheet extends TestCase {
assertEquals("test center footer", sheet.getOddFooter().getCenter()); assertEquals("test center footer", sheet.getOddFooter().getCenter());
} }
public void testExistingHeaderFooter() throws Exception {
File xml = new File(
System.getProperty("HSSF.testdata.path") +
File.separator + "45540_classic_Header.xlsx"
);
assertTrue(xml.exists());
XSSFWorkbook workbook = new XSSFWorkbook(xml.toString());
XSSFOddHeader hdr;
XSSFOddFooter ftr;
// Sheet 1 has a header with center and right text
XSSFSheet s1 = (XSSFSheet)workbook.getSheetAt(0);
assertNotNull(s1.getHeader());
assertNotNull(s1.getFooter());
hdr = (XSSFOddHeader)s1.getHeader();
ftr = (XSSFOddFooter)s1.getFooter();
assertEquals("&Ctestdoc&Rtest phrase", hdr.getText());
assertEquals(null, ftr.getText());
assertEquals("", hdr.getLeft());
assertEquals("testdoc", hdr.getCenter());
assertEquals("test phrase", hdr.getRight());
assertEquals("", ftr.getLeft());
assertEquals("", ftr.getCenter());
assertEquals("", ftr.getRight());
// Sheet 2 has a footer, but it's empty
XSSFSheet s2 = (XSSFSheet)workbook.getSheetAt(1);
assertNotNull(s2.getHeader());
assertNotNull(s2.getFooter());
hdr = (XSSFOddHeader)s2.getHeader();
ftr = (XSSFOddFooter)s2.getFooter();
assertEquals(null, hdr.getText());
assertEquals("&L&F", ftr.getText());
assertEquals("", hdr.getLeft());
assertEquals("", hdr.getCenter());
assertEquals("", hdr.getRight());
assertEquals("&F", ftr.getLeft());
assertEquals("", ftr.getCenter());
assertEquals("", ftr.getRight());
// Save and reload
ByteArrayOutputStream baos = new ByteArrayOutputStream();
workbook.write(baos);
XSSFWorkbook wb = new XSSFWorkbook(Package.open(
new ByteArrayInputStream(baos.toByteArray())
));
hdr = (XSSFOddHeader)wb.getSheetAt(0).getHeader();
ftr = (XSSFOddFooter)wb.getSheetAt(0).getFooter();
assertEquals("", hdr.getLeft());
assertEquals("testdoc", hdr.getCenter());
assertEquals("test phrase", hdr.getRight());
assertEquals("", ftr.getLeft());
assertEquals("", ftr.getCenter());
assertEquals("", ftr.getRight());
}
public void testGetAllHeadersFooters() { public void testGetAllHeadersFooters() {
XSSFWorkbook workbook = new XSSFWorkbook(); XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = (XSSFSheet) workbook.createSheet("Sheet 1"); XSSFSheet sheet = (XSSFSheet) workbook.createSheet("Sheet 1");
@ -626,7 +698,7 @@ public class TestXSSFSheet extends TestCase {
assertEquals(1, ctWorksheet.getColsArray(0).getColArray(0).getStyle()); assertEquals(1, ctWorksheet.getColsArray(0).getColArray(0).getStyle());
XSSFRow row = (XSSFRow) sheet.createRow(0); XSSFRow row = (XSSFRow) sheet.createRow(0);
XSSFCell cell = (XSSFCell) sheet.getRow(0).createCell(3); XSSFCell cell = (XSSFCell) sheet.getRow(0).createCell(3);
System.out.println(cell.getCellStyle()); //System.out.println(cell.getCellStyle());
} }

View File

@ -21,14 +21,20 @@ import junit.framework.TestCase;
import org.apache.poi.xssf.usermodel.helpers.HeaderFooterHelper; import org.apache.poi.xssf.usermodel.helpers.HeaderFooterHelper;
/**
* Test the header and footer helper.
* As we go through XmlBeans, should always use &,
* and not &amp;
*/
public class TestHeaderFooterHelper extends TestCase { public class TestHeaderFooterHelper extends TestCase {
public void testGetCenterLeftRightSection() { public void testGetCenterLeftRightSection() {
HeaderFooterHelper helper = new HeaderFooterHelper(); HeaderFooterHelper helper = new HeaderFooterHelper();
String headerFooter = "&amp;CTest the center section";
String headerFooter = "&CTest the center section";
assertEquals("Test the center section", helper.getCenterSection(headerFooter)); assertEquals("Test the center section", helper.getCenterSection(headerFooter));
headerFooter = "&amp;CTest the center section&amp;LThe left one&amp;RAnd the right one";
headerFooter = "&CTest the center section&LThe left one&RAnd the right one";
assertEquals("Test the center section", helper.getCenterSection(headerFooter)); assertEquals("Test the center section", helper.getCenterSection(headerFooter));
assertEquals("The left one", helper.getLeftSection(headerFooter)); assertEquals("The left one", helper.getLeftSection(headerFooter));
assertEquals("And the right one", helper.getRightSection(headerFooter)); assertEquals("And the right one", helper.getRightSection(headerFooter));
@ -44,15 +50,15 @@ public class TestHeaderFooterHelper extends TestCase {
headerFooter = helper.setRightSection(headerFooter, "First right"); headerFooter = helper.setRightSection(headerFooter, "First right");
assertEquals("First right", helper.getRightSection(headerFooter)); assertEquals("First right", helper.getRightSection(headerFooter));
assertEquals("&amp;CFirst added center section&amp;LFirst left&amp;RFirst right", headerFooter); assertEquals("&CFirst added center section&LFirst left&RFirst right", headerFooter);
headerFooter = helper.setRightSection(headerFooter, "First right&amp"); headerFooter = helper.setRightSection(headerFooter, "First right&F");
assertEquals("First right&amp", helper.getRightSection(headerFooter)); assertEquals("First right&F", helper.getRightSection(headerFooter));
assertEquals("&amp;CFirst added center section&amp;LFirst left&amp;RFirst right&amp", headerFooter); assertEquals("&CFirst added center section&LFirst left&RFirst right&F", headerFooter);
headerFooter = helper.setRightSection(headerFooter, "First right&amp;"); headerFooter = helper.setRightSection(headerFooter, "First right&");
assertEquals("First right", helper.getRightSection(headerFooter)); assertEquals("First right&", helper.getRightSection(headerFooter));
assertEquals("&amp;CFirst added center section&amp;LFirst left&amp;RFirst right&amp;", headerFooter); assertEquals("&CFirst added center section&LFirst left&RFirst right&", headerFooter);
} }
} }