Apply, with some tweaks, the patch from bug #45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@678287 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-07-20 17:18:07 +00:00
parent 48168d4301
commit 26abbc707b
6 changed files with 1001 additions and 29 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.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does</action>
<action dev="POI-DEVELOPERS" type="fix">45414 - Don't add too many UncalcedRecords to sheets with charts in them</action> <action dev="POI-DEVELOPERS" type="fix">45414 - Don't add too many UncalcedRecords to sheets with charts in them</action>
<action dev="POI-DEVELOPERS" type="fix">45398 - Support detecting date formats containing "am/pm" as date times</action> <action dev="POI-DEVELOPERS" type="fix">45398 - Support detecting date formats containing "am/pm" as date times</action>
<action dev="POI-DEVELOPERS" type="fix">45410 - Removed dependency from contrib on commons beanutils,collections and lang</action> <action dev="POI-DEVELOPERS" type="fix">45410 - Removed dependency from contrib on commons beanutils,collections and lang</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.1.1-alpha1" date="2008-??-??"> <release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="add">45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does</action>
<action dev="POI-DEVELOPERS" type="fix">45414 - Don't add too many UncalcedRecords to sheets with charts in them</action> <action dev="POI-DEVELOPERS" type="fix">45414 - Don't add too many UncalcedRecords to sheets with charts in them</action>
<action dev="POI-DEVELOPERS" type="fix">45398 - Support detecting date formats containing "am/pm" as date times</action> <action dev="POI-DEVELOPERS" type="fix">45398 - Support detecting date formats containing "am/pm" as date times</action>
<action dev="POI-DEVELOPERS" type="fix">45410 - Removed dependency from contrib on commons beanutils,collections and lang</action> <action dev="POI-DEVELOPERS" type="fix">45410 - Removed dependency from contrib on commons beanutils,collections and lang</action>

View File

@ -32,6 +32,7 @@ import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.NumberRecord; import org.apache.poi.hssf.record.NumberRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.usermodel.HSSFDataFormat; import org.apache.poi.hssf.usermodel.HSSFDataFormat;
import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
import org.apache.poi.hssf.usermodel.HSSFDateUtil; import org.apache.poi.hssf.usermodel.HSSFDateUtil;
/** /**
@ -41,6 +42,7 @@ import org.apache.poi.hssf.usermodel.HSSFDateUtil;
*/ */
public class FormatTrackingHSSFListener implements HSSFListener { public class FormatTrackingHSSFListener implements HSSFListener {
private HSSFListener childListener; private HSSFListener childListener;
private HSSFDataFormatter formatter = new HSSFDataFormatter();
private Map customFormatRecords = new Hashtable(); private Map customFormatRecords = new Hashtable();
private List xfRecords = new ArrayList(); private List xfRecords = new ArrayList();
@ -102,32 +104,9 @@ public class FormatTrackingHSSFListener implements HSSFListener {
if(formatString == null) { if(formatString == null) {
return Double.toString(value); return Double.toString(value);
} else { } else {
// Is it a date? // Format, using the nice new
if(HSSFDateUtil.isADateFormat(formatIndex,formatString) && // HSSFDataFormatter to do the work for us
HSSFDateUtil.isValidExcelDate(value)) { return formatter.formatRawCellContents(value, formatIndex, formatString);
// 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);
}
} }
} }

View File

@ -681,10 +681,13 @@ public class HSSFCell
/** /**
* get the value of the cell as a number. For strings we throw an exception. * Get the value of the cell as a number.
* For strings we throw an exception.
* For blank cells we return a 0. * For blank cells we return a 0.
* See {@link HSSFDataFormatter} for turning this
* number into a string similar to that which
* Excel would render this number as.
*/ */
public double getNumericCellValue() public double getNumericCellValue()
{ {
if (cellType == CELL_TYPE_BLANK) if (cellType == CELL_TYPE_BLANK)
@ -718,8 +721,11 @@ public class HSSFCell
} }
/** /**
* get the value of the cell as a date. For strings we throw an exception. * Get the value of the cell as a date.
* For strings we throw an exception.
* For blank cells we return a null. * For blank cells we return a null.
* See {@link HSSFDataFormatter} for formatting
* this date into a string similar to how excel does.
*/ */
public Date getDateCellValue() public Date getDateCellValue()
{ {

View File

@ -0,0 +1,703 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.usermodel;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HSSFDataFormatter contains methods for formatting the value stored in an
* HSSFCell. This can be useful for reports and GUI presentations when you
* need to display data exactly as it appears in Excel. Supported formats
* include currency, SSN, percentages, decimals, dates, phone numbers, zip
* codes, etc.
* <p>
* Internally, formats will be implemented using subclasses of {@link Format}
* such as {@link DecimalFormat} and {@link SimpleDateFormat}. Therefore the
* formats used by this class must obey the same pattern rules as these Format
* subclasses. This means that only legal number pattern characters ("0", "#",
* ".", "," etc.) may appear in number formats. Other characters can be
* inserted <em>before</em> or <em> after</em> the number pattern to form a
* prefix or suffix.
* </p>
* <p>
* For example the Excel pattern <code>"$#,##0.00 "USD"_);($#,##0.00 "USD")"
* </code> will be correctly formatted as "$1,000.00 USD" or "($1,000.00 USD)".
* However the pattern <code>"00-00-00"</code> is incorrectly formatted by
* DecimalFormat as "000000--". For Excel formats that are not compatible with
* DecimalFormat, you can provide your own custom {@link Format} implementation
* via <code>HSSFDataFormatter.addFormat(String,Format)</code>. The following
* custom formats are already provided by this class:
* </p>
* <pre>
* <ul><li>SSN "000-00-0000"</li>
* <li>Phone Number "(###) ###-####"</li>
* <li>Zip plus 4 "00000-0000"</li>
* </ul>
* </pre>
* <p>
* If the Excel format pattern cannot be parsed successfully, then a default
* format will be used. The default number format will mimic the Excel General
* format: "#" for whole numbers and "#.##########" for decimal numbers. You
* can override the default format pattern with <code>
* HSSFDataFormatter.setDefaultNumberFormat(Format)</code>. <b>Note:</b> the
* default format will only be used when a Format cannot be created from the
* cell's data format string.
*
* @author James May (james dot may at fmr dot com)
*
*/
public class HSSFDataFormatter {
/** Pattern to find a number format: "0" or "#" */
protected Pattern numPattern;
/** Pattern to find days of week as text "ddd...." */
protected Pattern daysAsText;
/** Pattern to find "AM/PM" marker */
protected Pattern amPmPattern;
/** A regex to find patterns like [$$-1009] and [$<24>-452]. */
protected Pattern specialPatternGroup;
/** <em>General</em> format for whole numbers. */
protected Format generalWholeNumFormat;
/** <em>General</em> format for decimal numbers. */
protected Format generalDecimalNumFormat;
/** A default format to use when a number pattern cannot be parsed. */
protected Format defaultNumFormat;
/**
* A map to cache formats.
* Map<String,Format> formats
*/
protected Map formats;
/**
* Constructor
*/
public HSSFDataFormatter() {
numPattern = Pattern.compile("[0#]+");
daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE);
amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE);
specialPatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])");
generalWholeNumFormat = new DecimalFormat("#");
generalDecimalNumFormat = new DecimalFormat("#.##########");
formats = new HashMap();
// init built-in formats
init();
}
/**
* Initialize the formatter. Called after construction.
*/
protected void init() {
ZipPlusFourFormat zipFormat = new ZipPlusFourFormat();
addFormat("00000\\-0000", zipFormat);
addFormat("00000-0000", zipFormat);
PhoneFormat phoneFormat = new PhoneFormat();
// allow for format string variations
addFormat("[<=9999999]###\\-####;\\(###\\)\\ ###\\-####", phoneFormat);
addFormat("[<=9999999]###-####;(###) ###-####", phoneFormat);
addFormat("###\\-####;\\(###\\)\\ ###\\-####", phoneFormat);
addFormat("###-####;(###) ###-####", phoneFormat);
SSNFormat ssnFormat = new SSNFormat();
addFormat("000\\-00\\-0000", ssnFormat);
addFormat("000-00-0000", ssnFormat);
}
/**
* Return a Format for the given cell if one exists, otherwise try to
* create one. This method will return <code>null</code> if the any of the
* following is true:
* <ul>
* <li>the cell's style is null</li>
* <li>the style's data format string is null or empty</li>
* <li>the format string cannot be recognized as either a number or date</li>
* </ul>
*
* @param cell The cell to retrieve a Format for
* @return A Format for the format String
*/
protected Format getFormat(HSSFCell cell) {
if ( cell.getCellStyle() == null) {
return null;
}
int formatIndex = cell.getCellStyle().getDataFormat();
String formatStr = cell.getCellStyle().getDataFormatString();
if(formatStr == null || formatStr.trim().length() == 0) {
return null;
}
return getFormat(cell.getNumericCellValue(), formatIndex, formatStr);
}
private Format getFormat(double cellValue, int formatIndex, String formatStr) {
Format format = (Format)formats.get(formatStr);
if (format != null) {
return format;
} else if (formatStr.equals("General")) {
if (HSSFDataFormatter.isWholeNumber(cellValue)) {
return generalWholeNumFormat;
} else {
return generalDecimalNumFormat;
}
} else {
format = createFormat(cellValue, formatIndex, formatStr);
formats.put(formatStr, format);
return format;
}
}
/**
* Create and return a Format based on the format string from a cell's
* style. If the pattern cannot be parsed, return a default pattern.
*
* @param cell The Excel cell
* @return A Format representing the excel format. May return null.
*/
protected Format createFormat(HSSFCell cell) {
String sFormat = cell.getCellStyle().getDataFormatString();
int formatIndex = cell.getCellStyle().getDataFormat();
String formatStr = cell.getCellStyle().getDataFormatString();
return createFormat(cell.getNumericCellValue(), formatIndex, formatStr);
}
private Format createFormat(double cellValue, int formatIndex, String sFormat) {
// remove color formatting if present
String formatStr = sFormat.replaceAll("\\[[a-zA-Z]*\\]", "");
// try to extract special characters like currency
Matcher m = specialPatternGroup.matcher(formatStr);
try {
while(m.find()) {
String match = m.group();
String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-'));
if (symbol.indexOf('$') > -1) {
StringBuffer sb = new StringBuffer();
sb.append(symbol.substring(0, symbol.indexOf('$')));
sb.append('\\');
sb.append(symbol.substring(symbol.indexOf('$'), symbol.length()));
symbol = sb.toString();
}
formatStr = m.replaceAll(symbol);
}
} catch (Exception e) {
return getDefaultFormat(cellValue);
}
if(formatStr == null || formatStr.trim().length() == 0) {
return getDefaultFormat(cellValue);
}
Format returnVal = null;
StringBuffer sb = null;
if(HSSFDateUtil.isADateFormat(formatIndex,formatStr) &&
HSSFDateUtil.isValidExcelDate(cellValue)) {
formatStr = formatStr.replaceAll("\\\\-","-");
formatStr = formatStr.replaceAll("\\\\,",",");
formatStr = formatStr.replaceAll("\\\\ "," ");
formatStr = formatStr.replaceAll(";@", "");
boolean hasAmPm = false;
Matcher amPmMatcher = amPmPattern.matcher(formatStr);
while (amPmMatcher.find()) {
formatStr = amPmMatcher.replaceAll("a");
hasAmPm = true;
}
Matcher dateMatcher = daysAsText.matcher(formatStr);
if (dateMatcher.find()) {
String match = dateMatcher.group(0);
formatStr = dateMatcher.replaceAll(match.toUpperCase().replaceAll("D", "E"));
}
// Convert excel date format to SimpleDateFormat.
// Excel uses lower case 'm' for both minutes and months.
// From Excel help:
/*
The "m" or "mm" code must appear immediately after the "h" or"hh"
code or immediately before the "ss" code; otherwise, Microsoft
Excel displays the month instead of minutes."
*/
sb = new StringBuffer();
char[] chars = formatStr.toCharArray();
boolean mIsMonth = true;
List ms = new ArrayList();
for(int j=0; j<chars.length; j++) {
char c = chars[j];
if (c == 'h' || c == 'H') {
mIsMonth = false;
if (hasAmPm) {
sb.append('h');
} else {
sb.append('H');
}
}
else if (c == 'm') {
if(mIsMonth) {
sb.append('M');
ms.add(
new Integer(sb.length() -1)
);
} else {
sb.append('m');
}
}
else if (c == 's' || c == 'S') {
sb.append('s');
// if 'M' precedes 's' it should be minutes ('m')
for (int i = 0; i < ms.size(); i++) {
int index = ((Integer)ms.get(i)).intValue();
if (sb.charAt(index) == 'M') {
sb.replace(index, index+1, "m");
}
}
mIsMonth = true;
ms.clear();
}
else if (Character.isLetter(c)) {
mIsMonth = true;
ms.clear();
if (c == 'y' || c == 'Y') {
sb.append('y');
}
else if (c == 'd' || c == 'D') {
sb.append('d');
}
else {
sb.append(c);
}
}
else {
sb.append(c);
}
}
formatStr = sb.toString();
try {
returnVal = new SimpleDateFormat(formatStr);
} catch(IllegalArgumentException iae) {
// the pattern could not be parsed correctly,
// so fall back to the default number format
return getDefaultFormat(cellValue);
}
} else if (numPattern.matcher(formatStr).find()) {
sb = new StringBuffer(formatStr);
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
//handle (#,##0_);
if (c == '(') {
int idx = sb.indexOf(")", i);
if (idx > -1 && sb.charAt(idx -1) == '_') {
sb.deleteCharAt(idx);
sb.deleteCharAt(idx - 1);
sb.deleteCharAt(i);
i--;
}
} else if (c == ')' && i > 0 && sb.charAt(i - 1) == '_') {
sb.deleteCharAt(i);
sb.deleteCharAt(i - 1);
i--;
// remove quotes and back slashes
} else if (c == '\\' || c == '"') {
sb.deleteCharAt(i);
i--;
// for scientific/engineering notation
} else if (c == '+' && i > 0 && sb.charAt(i - 1) == 'E') {
sb.deleteCharAt(i);
i--;
}
}
formatStr = sb.toString();
try {
returnVal = new DecimalFormat(formatStr);
} catch(IllegalArgumentException iae) {
// the pattern could not be parsed correctly,
// so fall back to the default number format
return getDefaultFormat(cellValue);
}
}
return returnVal;
}
/**
* Return true if the double value represents a whole number
* @param d the double value to check
* @return true if d is a whole number
*/
private static boolean isWholeNumber(double d) {
return d == Math.floor(d);
}
/**
* Returns a default format for a cell.
* @param cell The cell
* @return a default format
*/
protected Format getDefaultFormat(HSSFCell cell) {
return getDefaultFormat(cell.getNumericCellValue());
}
private Format getDefaultFormat(double cellValue) {
// for numeric cells try user supplied default
if (defaultNumFormat != null) {
return defaultNumFormat;
// otherwise use general format
} else if (isWholeNumber(cellValue)){
return generalWholeNumFormat;
} else {
return generalDecimalNumFormat;
}
}
/**
* Returns the formatted value of an Excel date as a <tt>String</tt> based
* on the cell's <code>DataFormat</code>. i.e. "Thursday, January 02, 2003"
* , "01/02/2003" , "02-Jan" , etc.
*
* @param cell The cell
* @return a formatted date string
*/
protected String getFormattedDateString(HSSFCell cell) {
Format dateFormat = getFormat(cell);
Date d = cell.getDateCellValue();
if (dateFormat != null) {
return dateFormat.format(d);
} else {
return d.toString();
}
}
/**
* Returns the formatted value of an Excel number as a <tt>String</tt>
* based on the cell's <code>DataFormat</code>. Supported formats include
* currency, percents, decimals, phone number, SSN, etc.:
* "61.54%", "$100.00", "(800) 555-1234".
*
* @param cell The cell
* @return a formatted number string
*/
protected String getFormattedNumberString(HSSFCell cell) {
Format numberFormat = getFormat(cell);
double d = cell.getNumericCellValue();
if (numberFormat != null) {
return numberFormat.format(new Double(d));
} else {
return String.valueOf(d);
}
}
/**
* Formats the given raw cell value, based on the supplied
* format index and string, according to excel style rules.
* @see #formatCellValue(HSSFCell)
*/
public String formatRawCellContents(double value, int formatIndex, String formatString) {
// Is it a date?
if(HSSFDateUtil.isADateFormat(formatIndex,formatString) &&
HSSFDateUtil.isValidExcelDate(value)) {
Format dateFormat = getFormat(value, formatIndex, formatString);
Date d = HSSFDateUtil.getJavaDate(value);
if (dateFormat != null) {
return dateFormat.format(d);
} else {
return d.toString();
}
} else {
// Number
Format numberFormat = getFormat(value, formatIndex, formatString);
if (numberFormat != null) {
return numberFormat.format(new Double(value));
} else {
return String.valueOf(value);
}
}
}
/**
* <p>
* Returns the formatted value of a cell as a <tt>String</tt> regardless
* of the cell type. If the Excel format pattern cannot be parsed then the
* cell value will be formatted using a default format.
* </p>
* <p>When passed a null or blank cell, this method will return an empty
* String (""). Formulas in formula type cells will not be evaluated.
* </p>
*
* @param cell The cell
* @return the formatted cell value as a String
*/
public String formatCellValue(HSSFCell cell) {
return formatCellValue(cell, null);
}
/**
* <p>
* Returns the formatted value of a cell as a <tt>String</tt> regardless
* of the cell type. If the Excel format pattern cannot be parsed then the
* cell value will be formatted using a default format.
* </p>
* <p>When passed a null or blank cell, this method will return an empty
* String (""). Formula cells will be evaluated using the given
* {@link HSSFFormulaEvaluator} if the evaluator is non-null. If the
* evaluator is null, then the formula String will be returned. The caller
* is responsible for setting the currentRow on the evaluator, otherwise an
* IllegalArgumentException may be thrown.
*</p>
*
* @param cell The cell
* @param evaluator The HSSFFormulaEvaluator (can be null)
* @return a string value of the cell
* @throws IllegalArgumentException if cell type is <code>
* HSSFCell.CELL_TYPE_FORMULA</code> <b>and</b> evaluator is not null
* <b>and</b> the evlaluator's currentRow has not been set.
*/
public String formatCellValue(HSSFCell cell,
HSSFFormulaEvaluator evaluator) throws IllegalArgumentException {
String value = "";
if (cell == null) {
return value;
}
int cellType = cell.getCellType();
if (evaluator != null && cellType == HSSFCell.CELL_TYPE_FORMULA) {
try {
cellType = evaluator.evaluateFormulaCell(cell);
} catch (Throwable t) {
throw new IllegalArgumentException("Did you forget to set the current" +
" row on the HSSFFormulaEvaluator?", t);
}
}
switch (cellType)
{
case HSSFCell.CELL_TYPE_FORMULA :
// should only occur if evaluator is null
value = cell.getCellFormula();
break;
case HSSFCell.CELL_TYPE_NUMERIC :
if (HSSFDateUtil.isCellDateFormatted(cell)) {
value = getFormattedDateString(cell);
} else {
value = getFormattedNumberString(cell);
}
break;
case HSSFCell.CELL_TYPE_STRING :
value = cell.getRichStringCellValue().getString();
break;
case HSSFCell.CELL_TYPE_BOOLEAN :
value = String.valueOf(cell.getBooleanCellValue());
}
return value;
}
/**
* <p>
* Sets a default number format to be used when the Excel format cannot be
* parsed successfully. <b>Note:</b> This is a fall back for when an error
* occurs while parsing an Excel number format pattern. This will not
* affect cells with the <em>General</em> format.
* </p>
* <p>
* The value that will be passed to the Format's format method (specified
* by <code>java.text.Format#format</code>) will be a double value from a
* numeric cell. Therefore the code in the format method should expect a
* <code>Number</code> value.
* </p>
*
* @param format A Format instance to be used as a default
* @see java.text.Format#format
*/
public void setDefaultNumberFormat(Format format) {
Iterator itr = formats.entrySet().iterator();
while(itr.hasNext()) {
Map.Entry entry = (Map.Entry)itr.next();
if (entry.getValue() == generalDecimalNumFormat
|| entry.getValue() == generalWholeNumFormat) {
entry.setValue(format);
}
}
defaultNumFormat = format;
}
/**
* Adds a new format to the available formats.
* <p>
* The value that will be passed to the Format's format method (specified
* by <code>java.text.Format#format</code>) will be a double value from a
* numeric cell. Therefore the code in the format method should expect a
* <code>Number</code> value.
* </p>
* @param excelFormatStr The data format string
* @param format A Format instance
*/
public void addFormat(String excelFormatStr, Format format) {
formats.put(excelFormatStr, format);
}
// Some custom formats
/**
* Format class for Excel's SSN format. This class mimics Excel's built-in
* SSN formatting.
*
* @author James May
*/
static class SSNFormat extends Format {
private DecimalFormat df;
/** Constructor */
public SSNFormat() {
df = new DecimalFormat("000000000");
df.setParseIntegerOnly(true);
}
/** Format a number as an SSN */
public String format(Number num) {
String result = df.format(num);
StringBuffer sb = new StringBuffer();
sb.append(result.substring(0, 3)).append('-');
sb.append(result.substring(3, 5)).append('-');
sb.append(result.substring(5, 9));
return sb.toString();
}
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
/**
* Format class for Excel Zip + 4 format. This class mimics Excel's
* built-in formatting for Zip + 4.
* @author James May
*/
static class ZipPlusFourFormat extends Format {
private DecimalFormat df;
/** Constructor */
public ZipPlusFourFormat() {
df = new DecimalFormat("000000000");
df.setParseIntegerOnly(true);
}
/** Format a number as Zip + 4 */
public String format(Number num) {
String result = df.format(num);
StringBuffer sb = new StringBuffer();
sb.append(result.substring(0, 5)).append('-');
sb.append(result.substring(5, 9));
return sb.toString();
}
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
/**
* Format class for Excel phone number format. This class mimics Excel's
* built-in phone number formatting.
* @author James May
*/
static class PhoneFormat extends Format {
private DecimalFormat df;
/** Constructor */
public PhoneFormat() {
df = new DecimalFormat("##########");
df.setParseIntegerOnly(true);
}
/** Format a number as a phone number */
public String format(Number num) {
String result = df.format(num);
StringBuffer sb = new StringBuffer();
String seg1, seg2, seg3;
int len = result.length();
if (len <= 4) {
return result;
}
seg3 = result.substring(len - 4, len);
seg2 = result.substring(Math.max(0, len - 7), len - 4);
seg1 = result.substring(Math.max(0, len - 10), Math.max(0, len - 7));
if(seg1 != null && seg1.trim().length() > 0) {
sb.append('(').append(seg1).append(") ");
}
if(seg2 != null && seg2.trim().length() > 0) {
sb.append(seg2).append('-');
}
sb.append(seg3);
return sb.toString();
}
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
return toAppendTo.append(format((Number)obj));
}
public Object parseObject(String source, ParsePosition pos) {
return df.parseObject(source, pos);
}
}
}

View File

@ -0,0 +1,282 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.usermodel;
import java.text.DecimalFormat;
import java.text.Format;
import java.util.Iterator;
import junit.framework.TestCase;
/**
* Unit tests for HSSFDataFormatter.java
*
* @author James May (james dot may at fmr dot com)
*
*/
public class TestHSSFDataFormatter extends TestCase {
HSSFDataFormatter formatter;
HSSFWorkbook wb;
public TestHSSFDataFormatter() {
// create the formatter to test
formatter = new HSSFDataFormatter();
// create a workbook to test with
wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
HSSFDataFormat format = wb.createDataFormat();
// create a row and put some cells in it
HSSFRow row = sheet.createRow((short)0);
// date value for July 8 1901 1:19 PM
double dateNum = 555.555;
//valid date formats -- all should have "Jul" in output
String[] goodDatePatterns = new String[] {
"[$-F800]dddd\\,\\ mmmm\\ dd\\,\\ yyyy",
"mmm/d/yy\\ h:mm PM;@",
"mmmm/d/yy\\ h:mm;@",
"mmmm/d;@",
"mmmm/d/yy;@",
"mmm/dd/yy;@",
"[$-409]d\\-mmm;@",
"[$-409]d\\-mmm\\-yy;@",
"[$-409]dd\\-mmm\\-yy;@",
"[$-409]mmm\\-yy;@",
"[$-409]mmmm\\-yy;@",
"[$-409]mmmm\\ d\\,\\ yyyy;@",
"[$-409]mmm/d/yy\\ h:mm:ss;@",
"[$-409]mmmm/d/yy\\ h:mm:ss am;@",
"[$-409]mmmmm;@",
"[$-409]mmmmm\\-yy;@",
"mmmm/d/yyyy;@",
"[$-409]d\\-mmm\\-yyyy;@"
};
// valid number formats
String[] goodNumPatterns = new String[] {
"#,##0.0000",
"#,##0;[Red]#,##0",
"(#,##0.00_);(#,##0.00)",
"($#,##0.00_);[Red]($#,##0.00)",
"$#,##0.00",
"[$<24>-809]#,##0.00",
"[$<24>-2] #,##0.00",
"0000.00000%",
"0.000E+00",
"0.00E+00",
};
// invalid date formats -- will throw exception in DecimalFormat ctor
String[] badNumPatterns = new String[] {
"#,#$'#0.0000",
"'#','#ABC#0;##,##0",
"000 '123 4'5'6 000",
"#''0#0'1#10L16EE"
};
// create cells with good date patterns
for (int i = 0; i < goodDatePatterns.length; i++) {
HSSFCell cell = row.createCell((short) i);
cell.setCellValue(dateNum);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat(goodDatePatterns[i]));
cell.setCellStyle(cellStyle);
}
row = sheet.createRow(1);
// create cells with num patterns
for (int i = 0; i < goodNumPatterns.length; i++) {
HSSFCell cell = row.createCell((short) i);
cell.setCellValue(-1234567890.12345);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat(goodNumPatterns[i]));
cell.setCellStyle(cellStyle);
}
row = sheet.createRow(2);
// create cells with bad num patterns
for (int i = 0; i < badNumPatterns.length; i++) {
HSSFCell cell = row.createCell((short) i);
cell.setCellValue(1234567890.12345);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat(badNumPatterns[i]));
cell.setCellStyle(cellStyle);
}
// Built in formats
{ // Zip + 4 format
row = sheet.createRow(3);
HSSFCell cell = row.createCell((short) 0);
cell.setCellValue(123456789);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat("00000-0000"));
cell.setCellStyle(cellStyle);
}
{ // Phone number format
row = sheet.createRow(4);
HSSFCell cell = row.createCell((short) 0);
cell.setCellValue(5551234567D);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat("[<=9999999]###-####;(###) ###-####"));
cell.setCellStyle(cellStyle);
}
{ // SSN format
row = sheet.createRow(5);
HSSFCell cell = row.createCell((short) 0);
cell.setCellValue(444551234);
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat("000-00-0000"));
cell.setCellStyle(cellStyle);
}
{ // formula cell
row = sheet.createRow(6);
HSSFCell cell = row.createCell((short) 0);
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("SUM(12.25,12.25)/100");
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setDataFormat(format.getFormat("##.00%;"));
cell.setCellStyle(cellStyle);
}
}
/**
* Test getting formatted values from numeric and date cells.
*/
public void testGetFormattedCellValueHSSFCell() {
// Valid date formats -- cell values should be date formatted & not "555.555"
HSSFRow row = wb.getSheetAt(0).getRow(0);
Iterator it = row.cellIterator();
System.out.println("==== VALID DATE FORMATS ====");
while (it.hasNext()) {
HSSFCell cell = (HSSFCell) it.next();
System.out.println(formatter.formatCellValue(cell));
// should not be equal to "555.555"
assertTrue( ! "555.555".equals(formatter.formatCellValue(cell)));
// should contain "Jul" in the String
assertTrue( formatter.formatCellValue(cell).indexOf("Jul") > -1);
}
// test number formats
row = wb.getSheetAt(0).getRow(1);
it = row.cellIterator();
System.out.println("\n==== VALID NUMBER FORMATS ====");
while (it.hasNext()) {
HSSFCell cell = (HSSFCell) it.next();
System.out.println(formatter.formatCellValue(cell));
// should not be equal to "1234567890.12345"
assertTrue( ! "1234567890.12345".equals(formatter.formatCellValue(cell)));
}
// test bad number formats
row = wb.getSheetAt(0).getRow(2);
it = row.cellIterator();
System.out.println("\n==== INVALID NUMBER FORMATS ====");
while (it.hasNext()) {
HSSFCell cell = (HSSFCell) it.next();
System.out.println(formatter.formatCellValue(cell));
// should be equal to "1234567890.12345"
assertEquals("1234567890.12345", formatter.formatCellValue(cell));
}
// test Zip+4 format
row = wb.getSheetAt(0).getRow(3);
HSSFCell cell = row.getCell(0);
System.out.println("\n==== ZIP FORMAT ====");
System.out.println(formatter.formatCellValue(cell));
assertEquals("12345-6789", formatter.formatCellValue(cell));
// test phone number format
row = wb.getSheetAt(0).getRow(4);
cell = row.getCell(0);
System.out.println("\n==== PHONE FORMAT ====");
System.out.println(formatter.formatCellValue(cell));
assertEquals("(555) 123-4567", formatter.formatCellValue(cell));
// test SSN format
row = wb.getSheetAt(0).getRow(5);
cell = row.getCell(0);
System.out.println("\n==== SSN FORMAT ====");
System.out.println(formatter.formatCellValue(cell));
assertEquals("444-55-1234", formatter.formatCellValue(cell));
// null test-- null cell should result in empty String
assertEquals(formatter.formatCellValue(null), "");
// null test-- null cell should result in empty String
assertEquals(formatter.formatCellValue(null), "");
}
public void testGetFormattedCellValueHSSFCellHSSFFormulaEvaluator() {
// test formula format
HSSFRow row = wb.getSheetAt(0).getRow(6);
HSSFCell cell = row.getCell(0);
System.out.println("\n==== FORMULA CELL ====");
// first without a formula evaluator
System.out.println(formatter.formatCellValue(cell) + "\t (without evaluator)");
assertEquals("SUM(12.25,12.25)/100", formatter.formatCellValue(cell));
// now with a formula evaluator
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(wb.getSheetAt(0), wb);
//! must set current row !
evaluator.setCurrentRow(row);
System.out.println(formatter.formatCellValue(cell, evaluator) + "\t\t\t (with evaluator)");
assertEquals("24.50%", formatter.formatCellValue(cell,evaluator));
}
/**
* Test using a default number format. The format should be used when a
* format pattern cannot be parsed by DecimalFormat.
*/
public void testSetDefaultNumberFormat() {
HSSFRow row = wb.getSheetAt(0).getRow(2);
Iterator it = row.cellIterator();
Format defaultFormat = new DecimalFormat("Balance $#,#00.00 USD;Balance -$#,#00.00 USD");
formatter.setDefaultNumberFormat(defaultFormat);
double value = 10d;
System.out.println("\n==== DEFAULT NUMBER FORMAT ====");
while (it.hasNext()) {
HSSFCell cell = (HSSFCell) it.next();
cell.setCellValue(cell.getNumericCellValue() * Math.random() / 1000000 - 1000);
System.out.println(formatter.formatCellValue(cell));
assertTrue(formatter.formatCellValue(cell).startsWith("Balance "));
assertTrue(formatter.formatCellValue(cell).endsWith(" USD"));
}
}
public static void main(String [] args) {
System.out
.println("Testing org.apache.poi.hssf.usermodel.TestHSSFDataFormatter");
junit.textui.TestRunner.run(TestHSSFDataFormatter.class);
}
}