diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 16a82aa3a..808bea2a9 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra 45373 - Improve the performance of HSSFSheet.shiftRows 45367 - Fixed bug when last row removed from sheet is row zero 45348 - Tweaks to RVA formula logic diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 7143b98d7..21d279ada 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45365 - Handle more excel number formatting rules in FormatTrackingHSSFListener / XLS2CSVmra 45373 - Improve the performance of HSSFSheet.shiftRows 45367 - Fixed bug when last row removed from sheet is row zero 45348 - Tweaks to RVA formula logic diff --git a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java index 1c9b22035..632eacf5c 100644 --- a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java +++ b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java @@ -20,10 +20,6 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.SimpleDateFormat; -import java.util.Date; import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; @@ -37,7 +33,6 @@ import org.apache.poi.hssf.model.FormulaParser; import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BoolErrRecord; -import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.LabelRecord; import org.apache.poi.hssf.record.LabelSSTRecord; @@ -47,7 +42,6 @@ import org.apache.poi.hssf.record.RKRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.StringRecord; -import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -180,7 +174,7 @@ public class XLS2CSVmra implements HSSFListener { nextRow = frec.getRow(); nextColumn = frec.getColumn(); } else { - thisStr = formatNumberDateCell(frec, frec.getValue()); + thisStr = formatListener.formatNumberDateCell(frec); } } else { thisStr = '"' + @@ -231,7 +225,7 @@ public class XLS2CSVmra implements HSSFListener { thisColumn = numrec.getColumn(); // Format - thisStr = formatNumberDateCell(numrec, numrec.getValue()); + thisStr = formatListener.formatNumberDateCell(numrec); break; case RKRecord.sid: RKRecord rkrec = (RKRecord) record; @@ -290,44 +284,6 @@ public class XLS2CSVmra implements HSSFListener { } } - /** - * Formats a number or date cell, be that a real number, or the - * answer to a formula - */ - private String formatNumberDateCell(CellValueRecordInterface cell, double value) { - // Get the built in format, if there is one - int formatIndex = formatListener.getFormatIndex(cell); - String formatString = formatListener.getFormatString(cell); - - if(formatString == null) { - return Double.toString(value); - } else { - // Is it a date? - if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && - HSSFDateUtil.isValidExcelDate(value)) { - // Java wants M not m for month - formatString = formatString.replace('m','M'); - // Change \- into -, if it's there - formatString = formatString.replaceAll("\\\\-","-"); - - // Format as a date - Date d = HSSFDateUtil.getJavaDate(value, false); - DateFormat df = new SimpleDateFormat(formatString); - return df.format(d); - } else { - if(formatString == "General") { - // Some sort of wierd default - return Double.toString(value); - } - - // Format as a number - DecimalFormat df = new DecimalFormat(formatString); - return df.format(value); - } - } - } - - public static void main(String[] args) throws Exception { if(args.length < 1) { System.err.println("Use:"); diff --git a/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java b/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java index b88143713..5a84f4564 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/FormatTrackingHSSFListener.java @@ -16,7 +16,11 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -24,8 +28,11 @@ import java.util.Map; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.FormatRecord; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.usermodel.HSSFDataFormat; +import org.apache.poi.hssf.usermodel.HSSFDateUtil; /** * A proxy HSSFListener that keeps track of the document @@ -69,6 +76,61 @@ public class FormatTrackingHSSFListener implements HSSFListener { } } + /** + * Formats the given numeric of date Cell's contents + * as a String, in as close as we can to the way + * that Excel would do so. + * Uses the various format records to manage this. + * + * TODO - move this to a central class in such a + * way that hssf.usermodel can make use of it too + */ + public String formatNumberDateCell(CellValueRecordInterface cell) { + double value; + if(cell instanceof NumberRecord) { + value = ((NumberRecord)cell).getValue(); + } else if(cell instanceof FormulaRecord) { + value = ((FormulaRecord)cell).getValue(); + } else { + throw new IllegalArgumentException("Unsupported CellValue Record passed in " + cell); + } + + // Get the built in format, if there is one + int formatIndex = getFormatIndex(cell); + String formatString = getFormatString(cell); + + if(formatString == null) { + return Double.toString(value); + } else { + // Is it a date? + if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && + HSSFDateUtil.isValidExcelDate(value)) { + // Java wants M not m for month + formatString = formatString.replace('m','M'); + // Change \- into -, if it's there + formatString = formatString.replaceAll("\\\\-","-"); + + // Format as a date + Date d = HSSFDateUtil.getJavaDate(value, false); + DateFormat df = new SimpleDateFormat(formatString); + return df.format(d); + } else { + if(formatString == "General") { + // Some sort of wierd default + return Double.toString(value); + } + if(formatString == "0.00E+00") { + // This seems to mean output as a normal double + return Double.toString(value); + } + + // Format as a number + DecimalFormat df = new DecimalFormat(formatString); + return df.format(value); + } + } + } + /** * Returns the format string, eg $##.##, for the * given number format index. diff --git a/src/testcases/org/apache/poi/hssf/data/45365.xls b/src/testcases/org/apache/poi/hssf/data/45365.xls new file mode 100644 index 000000000..fbf1d97c5 Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/45365.xls differ diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java index e52a3bc96..60f5d2ca7 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestFormatTrackingHSSFListener.java @@ -24,6 +24,9 @@ import java.util.List; import junit.framework.TestCase; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.CellValueRecordInterface; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** @@ -31,16 +34,17 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; */ public final class TestFormatTrackingHSSFListener extends TestCase { private FormatTrackingHSSFListener listener; - - public void setUp() { + private MockHSSFListener mockListen; + + private void processFile(String filename) throws Exception { HSSFRequest req = new HSSFRequest(); - MockHSSFListener mockListen = new MockHSSFListener(); + mockListen = new MockHSSFListener(); listener = new FormatTrackingHSSFListener(mockListen); req.addListenerForAllRecords(listener); HSSFEventFactory factory = new HSSFEventFactory(); try { - InputStream is = HSSFTestDataSamples.openSampleFileStream("MissingBits.xls"); + InputStream is = HSSFTestDataSamples.openSampleFileStream(filename); POIFSFileSystem fs = new POIFSFileSystem(is); factory.processWorkbookEvents(req, fs); } catch (IOException e) { @@ -49,11 +53,42 @@ public final class TestFormatTrackingHSSFListener extends TestCase { } public void testFormats() throws Exception { + processFile("MissingBits.xls"); + assertEquals("_(*#,##0_);_(*(#,##0);_(* \"-\"_);_(@_)", listener.getFormatString(41)); assertEquals("_($*#,##0_);_($*(#,##0);_($* \"-\"_);_(@_)", listener.getFormatString(42)); assertEquals("_(*#,##0.00_);_(*(#,##0.00);_(*\"-\"??_);_(@_)", listener.getFormatString(43)); } + /** + * Ensure that all number and formula records can be + * turned into strings without problems + */ + public void testTurnToString() throws Exception { + processFile("45365.xls"); + + for(int i=0; i 0); + } + } + + // TODO - test some specific format strings + } + private static final class MockHSSFListener implements HSSFListener { public MockHSSFListener() {} private final List _records = new ArrayList();