From eb2e8ffe0aa43c70afa095a4f4b3a85e4f56ce5c Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sun, 2 Feb 2014 16:39:53 +0000 Subject: [PATCH] Patch from Shaun Kalley from bug #56022 - XSSF Event Text Extractor header/footer support git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1563657 13f79535-47bb-0310-9956-ffa450edef68 --- .../XSSFEventBasedExcelExtractor.java | 110 ++++++++++++++++-- .../TestXSSFEventBasedExcelExtractor.java | 31 +++++ test-data/spreadsheet/headerFooterTest.xlsx | Bin 0 -> 27484 bytes 3 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 test-data/spreadsheet/headerFooterTest.xlsx diff --git a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java index 0bf911341c..3f7e5a392e 100644 --- a/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java +++ b/src/ooxml/java/org/apache/poi/xssf/extractor/XSSFEventBasedExcelExtractor.java @@ -18,8 +18,10 @@ package org.apache.poi.xssf.extractor; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; @@ -56,9 +58,10 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor private POIXMLProperties properties; private Locale locale; + private boolean includeTextBoxes = true; private boolean includeSheetNames = true; + private boolean includeHeadersFooters = true; private boolean formulasNotResults = false; - private boolean includeTextBoxes = true; public XSSFEventBasedExcelExtractor(String path) throws XmlException, OpenXML4JException, IOException { this(OPCPackage.open(path)); @@ -94,7 +97,12 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor public void setFormulasNotResults(boolean formulasNotResults) { this.formulasNotResults = formulasNotResults; } - + /** + * Should headers and footers be included? Default is true + */ + public void setIncludeHeadersFooters(boolean includeHeadersFooters) { + this.includeHeadersFooters = includeHeadersFooters; + } /** * Should text from textboxes be included? Default is true */ @@ -186,7 +194,7 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); StringBuffer text = new StringBuffer(); - SheetTextExtractor sheetExtractor = new SheetTextExtractor(text); + SheetTextExtractor sheetExtractor = new SheetTextExtractor(); while (iter.hasNext()) { InputStream stream = iter.next(); @@ -195,9 +203,17 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor text.append('\n'); } processSheet(sheetExtractor, styles, strings, stream); + if (includeHeadersFooters) { + sheetExtractor.appendHeaderText(text); + } + sheetExtractor.appendCellText(text); if (includeTextBoxes){ processShapes(iter.getShapes(), text); } + if (includeHeadersFooters) { + sheetExtractor.appendFooterText(text); + } + sheetExtractor.reset(); stream.close(); } @@ -238,10 +254,13 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor protected class SheetTextExtractor implements SheetContentsHandler { private final StringBuffer output; - private boolean firstCellOfRow = true; + private boolean firstCellOfRow; + private final Map headerFooterMap; - protected SheetTextExtractor(StringBuffer output) { - this.output = output; + protected SheetTextExtractor() { + this.output = new StringBuffer(); + this.firstCellOfRow = true; + this.headerFooterMap = includeHeadersFooters ? new HashMap() : null; } public void startRow(int rowNum) { @@ -262,7 +281,84 @@ public class XSSFEventBasedExcelExtractor extends POIXMLTextExtractor } public void headerFooter(String text, boolean isHeader, String tagName) { - // We don't include headers in the output yet, so ignore + if (headerFooterMap != null) { + headerFooterMap.put(tagName, text); + } + } + + + /** + * Append the text for the named header or footer if found. + */ + private void appendHeaderFooterText(StringBuffer buffer, String name) { + String text = headerFooterMap.get(name); + if (text != null && text.length() > 0) { + // this is a naive way of handling the left, center, and right + // header and footer delimiters, but it seems to be as good as + // the method used by XSSFExcelExtractor + text = handleHeaderFooterDelimiter(text, "&L"); + text = handleHeaderFooterDelimiter(text, "&C"); + text = handleHeaderFooterDelimiter(text, "&R"); + buffer.append(text).append('\n'); + } + } + /** + * Remove the delimiter if its found at the beginning of the text, + * or replace it with a tab if its in the middle. + */ + private String handleHeaderFooterDelimiter(String text, String delimiter) { + int index = text.indexOf(delimiter); + if (index == 0) { + text = text.substring(2); + } else if (index > 0) { + text = text.substring(0, index) + "\t" + text.substring(index + 2); + } + return text; + } + + + /** + * Append the text for each header type in the same order + * they are appended in XSSFExcelExtractor. + * @see XSSFExcelExtractor#getText() + * @see org.apache.poi.hssf.extractor.ExcelExtractor#_extractHeaderFooter(org.apache.poi.ss.usermodel.HeaderFooter) + */ + private void appendHeaderText(StringBuffer buffer) { + appendHeaderFooterText(buffer, "firstHeader"); + appendHeaderFooterText(buffer, "oddHeader"); + appendHeaderFooterText(buffer, "evenHeader"); + } + + /** + * Append the text for each footer type in the same order + * they are appended in XSSFExcelExtractor. + * @see XSSFExcelExtractor#getText() + * @see org.apache.poi.hssf.extractor.ExcelExtractor#_extractHeaderFooter(org.apache.poi.ss.usermodel.HeaderFooter) + */ + private void appendFooterText(StringBuffer buffer) { + // append the text for each footer type in the same order + // they are appended in XSSFExcelExtractor + appendHeaderFooterText(buffer, "firstFooter"); + appendHeaderFooterText(buffer, "oddFooter"); + appendHeaderFooterText(buffer, "evenFooter"); + } + + /** + * Append the cell contents we have collected. + */ + private void appendCellText(StringBuffer buffer) { + buffer.append(output); + } + + /** + * Reset this SheetTextExtractor for the next sheet. + */ + private void reset() { + output.setLength(0); + firstCellOfRow = true; + if (headerFooterMap != null) { + headerFooterMap.clear(); + } } } } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFEventBasedExcelExtractor.java b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFEventBasedExcelExtractor.java index 0fb28ef9dc..98aeb627f2 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFEventBasedExcelExtractor.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/extractor/TestXSSFEventBasedExcelExtractor.java @@ -209,4 +209,35 @@ public class TestXSSFEventBasedExcelExtractor extends TestCase { fixture.close(); } } + + /** + * Test that we return the same output headers and footers as the + * non-event-based XSSFExcelExtractor. + */ + public void testHeadersAndFootersComparedToNonEventBasedExtractor() + throws Exception { + + String expectedOutputWithHeadersAndFooters = + "Sheet1\n" + + "&\"Calibri,Regular\"&K000000top left\t&\"Calibri,Regular\"&K000000top center\t&\"Calibri,Regular\"&K000000top right\n" + + "abc\t123\n" + + "&\"Calibri,Regular\"&K000000bottom left\t&\"Calibri,Regular\"&K000000bottom center\t&\"Calibri,Regular\"&K000000bottom right\n"; + + String expectedOutputWithoutHeadersAndFooters = + "Sheet1\n" + + "abc\t123\n"; + + XSSFExcelExtractor extractor = new XSSFExcelExtractor( + XSSFTestDataSamples.openSampleWorkbook("headerFooterTest.xlsx")); + assertEquals(expectedOutputWithHeadersAndFooters, extractor.getText()); + extractor.setIncludeHeadersFooters(false); + assertEquals(expectedOutputWithoutHeadersAndFooters, extractor.getText()); + + XSSFEventBasedExcelExtractor fixture = + new XSSFEventBasedExcelExtractor( + XSSFTestDataSamples.openSamplePackage("headerFooterTest.xlsx")); + assertEquals(expectedOutputWithHeadersAndFooters, fixture.getText()); + fixture.setIncludeHeadersFooters(false); + assertEquals(expectedOutputWithoutHeadersAndFooters, fixture.getText()); + } } diff --git a/test-data/spreadsheet/headerFooterTest.xlsx b/test-data/spreadsheet/headerFooterTest.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2c721258503a08c016774233cd1733672d40faef GIT binary patch literal 27484 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 literal 0 HcmV?d00001 -- 2.39.5