From b118d1afb98210558aea6811810ce7973be67dc6 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 5 Aug 2008 11:26:38 +0000 Subject: [PATCH] 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 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../xssf/extractor/XSSFExcelExtractor.java | 42 ++++--- .../extensions/XSSFHeaderFooter.java | 10 +- .../usermodel/helpers/HeaderFooterHelper.java | 118 ++++++++++++------ .../extractor/TestXSSFExcelExtractor.java | 8 +- .../poi/xssf/usermodel/TestXSSFSheet.java | 74 ++++++++++- .../helpers/TestHeaderFooterHelper.java | 26 ++-- 8 files changed, 207 insertions(+), 73 deletions(-) diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 46f8c4389..a37166aac 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45540 - Fix XSSF header and footer support, and include headers and footers in the output of XSSFExcelExtractor 45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from 45431 - Partial support for .xlm files. Not quite enough for excel to load them though diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index af9648e52..14d942dc1 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45540 - Fix XSSF header and footer support, and include headers and footers in the output of XSSFExcelExtractor 45431 - Support for .xlsm files, sufficient for simple files to be loaded by excel without warning New class org.apache.poi.hssf.record.RecordFormatException, which DDF uses instead of the HSSF version, and the HSSF version inherits from 45431 - Partial support for .xlm files. Not quite enough for excel to load them though diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java index 113436dc4..b026baf0e 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFExcelExtractor.java @@ -25,9 +25,8 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Comment; import org.apache.poi.ss.usermodel.HeaderFooter; 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.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.xmlbeans.XmlException; import org.openxml4j.exceptions.OpenXML4JException; @@ -37,7 +36,7 @@ import org.openxml4j.opc.Package; * Helper class to extract text from an OOXML Excel file */ public class XSSFExcelExtractor extends POIXMLTextExtractor { - private Workbook workbook; + private XSSFWorkbook workbook; private boolean includeSheetNames = true; private boolean formulasNotResults = false; private boolean includeCellComments = false; @@ -91,18 +90,23 @@ public class XSSFExcelExtractor extends POIXMLTextExtractor { StringBuffer text = new StringBuffer(); for(int i=0; i ri = row.cellIterator(); ri.hasNext();) { @@ -133,12 +137,16 @@ public class XSSFExcelExtractor extends POIXMLTextExtractor { text.append("\n"); } - // Finally footer, if present - if(sheet.getFooter() != null) { - text.append( - extractHeaderFooter(sheet.getFooter()) - ); - } + // Finally footer(s), if present + text.append( + extractHeaderFooter(sheet.getFirstFooter()) + ); + text.append( + extractHeaderFooter(sheet.getOddFooter()) + ); + text.append( + extractHeaderFooter(sheet.getEvenFooter()) + ); } return text.toString(); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/extensions/XSSFHeaderFooter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/extensions/XSSFHeaderFooter.java index 6d1b152e0..8c79761e9 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/extensions/XSSFHeaderFooter.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/extensions/XSSFHeaderFooter.java @@ -24,21 +24,21 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHeaderFooter; public abstract class XSSFHeaderFooter implements HeaderFooter { private HeaderFooterHelper helper; private CTHeaderFooter headerFooter; - private String value; public XSSFHeaderFooter(CTHeaderFooter headerFooter) { this.headerFooter = headerFooter; - this.value = getText(); - this.value = this.value != null ? this.value : ""; this.helper = new HeaderFooterHelper(); } public CTHeaderFooter getHeaderFooter() { return this.headerFooter; } - + public String getValue() { - return this.value; + String value = getText(); + if(value == null) + return ""; + return value; } public abstract String getText(); diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/HeaderFooterHelper.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/HeaderFooterHelper.java index 18fddf52a..41e3ea462 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/HeaderFooterHelper.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/HeaderFooterHelper.java @@ -19,55 +19,101 @@ package org.apache.poi.xssf.usermodel.helpers; public class HeaderFooterHelper { + // Note - XmlBeans handles entity encoding for us, + // so these should be & forms, not the & 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 = "&"; - private static final String HeaderFooterEntity_R = "&R"; - private static final String HeaderFooterEntity_L = "&L"; - private static final String HeaderFooterEntity_C = "&C"; + // These are other entities that may be used in the + // left, center or right. Not exhaustive + public static final String HeaderFooterEntity_File = "&F"; + 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) { - return getSection(string, HeaderFooterEntity_L); + return getParts(string)[0]; + } + public String getCenterSection(String string) { + return getParts(string)[1]; } 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) { - 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) { - 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 : ""; - String oldSection = getSection(string, entity); - if (oldSection.equals("")) { - return string.concat(entity + newSection); - } - return string.replaceAll(entity + oldSection, entity + newSection); + /** + * Split into left, center, right + */ + private String[] getParts(String string) { + String[] parts = new String[] { "", "", "" }; + if(string == null) + 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 getSection(String string, String entity) { - 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[] parts) { + return joinParts(parts[0], parts[1], parts[2]); } + 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(); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExcelExtractor.java b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExcelExtractor.java index 93f29065c..70773ad54 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExcelExtractor.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFExcelExtractor.java @@ -192,10 +192,10 @@ public class TestXSSFExcelExtractor extends TestCase { /** * From bug #45540 */ - public void BROKENtestHeaderFooter() throws Exception { + public void testHeaderFooter() throws Exception { String[] files = new String[] { + "45540_classic_Header.xlsx", "45540_form_Header.xlsx", "45540_classic_Footer.xlsx", "45540_form_Footer.xlsx", - "45540_classic_Header.xlsx", "45540_form_Header.xlsx" }; for(String file : files) { File xml = new File( @@ -208,8 +208,8 @@ public class TestXSSFExcelExtractor extends TestCase { new XSSFExcelExtractor(new XSSFWorkbook(xml.toString())); 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\n" + text, text.contains("test phrase")); + 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")); } } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java index ad8dfc9ae..5964f69e0 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java @@ -17,6 +17,9 @@ package org.apache.poi.xssf.usermodel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; import java.util.Iterator; import junit.framework.TestCase; 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.StylesTable; 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.CTCols; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTComment; @@ -291,6 +295,74 @@ public class TestXSSFSheet extends TestCase { 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() { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = (XSSFSheet) workbook.createSheet("Sheet 1"); @@ -626,7 +698,7 @@ public class TestXSSFSheet extends TestCase { assertEquals(1, ctWorksheet.getColsArray(0).getColArray(0).getStyle()); XSSFRow row = (XSSFRow) sheet.createRow(0); XSSFCell cell = (XSSFCell) sheet.getRow(0).createCell(3); - System.out.println(cell.getCellStyle()); + //System.out.println(cell.getCellStyle()); } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestHeaderFooterHelper.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestHeaderFooterHelper.java index 9c61d3dfb..2d25e3e95 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestHeaderFooterHelper.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestHeaderFooterHelper.java @@ -21,14 +21,20 @@ import junit.framework.TestCase; import org.apache.poi.xssf.usermodel.helpers.HeaderFooterHelper; - +/** + * Test the header and footer helper. + * As we go through XmlBeans, should always use &, + * and not & + */ public class TestHeaderFooterHelper extends TestCase { public void testGetCenterLeftRightSection() { HeaderFooterHelper helper = new HeaderFooterHelper(); - String headerFooter = "&CTest the center section"; + + String headerFooter = "&CTest the center section"; assertEquals("Test the center section", helper.getCenterSection(headerFooter)); - headerFooter = "&CTest the center section&LThe left one&RAnd the right one"; + + headerFooter = "&CTest the center section<he left one&RAnd the right one"; assertEquals("Test the center section", helper.getCenterSection(headerFooter)); assertEquals("The left one", helper.getLeftSection(headerFooter)); assertEquals("And the right one", helper.getRightSection(headerFooter)); @@ -44,15 +50,15 @@ public class TestHeaderFooterHelper extends TestCase { headerFooter = helper.setRightSection(headerFooter, "First right"); assertEquals("First right", helper.getRightSection(headerFooter)); - assertEquals("&CFirst added center section&LFirst left&RFirst right", headerFooter); + assertEquals("&CFirst added center section&LFirst left&RFirst right", headerFooter); - headerFooter = helper.setRightSection(headerFooter, "First right&"); - assertEquals("First right&", helper.getRightSection(headerFooter)); - assertEquals("&CFirst added center section&LFirst left&RFirst right&", headerFooter); + headerFooter = helper.setRightSection(headerFooter, "First right&F"); + assertEquals("First right&F", helper.getRightSection(headerFooter)); + assertEquals("&CFirst added center section&LFirst left&RFirst right&F", headerFooter); - headerFooter = helper.setRightSection(headerFooter, "First right&"); - assertEquals("First right", helper.getRightSection(headerFooter)); - assertEquals("&CFirst added center section&LFirst left&RFirst right&", headerFooter); + headerFooter = helper.setRightSection(headerFooter, "First right&"); + assertEquals("First right&", helper.getRightSection(headerFooter)); + assertEquals("&CFirst added center section&LFirst left&RFirst right&", headerFooter); } }