diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 43d6bb7b16..12bce0ccf5 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -51,6 +51,7 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does 45414 - Don't add too many UncalcedRecords to sheets with charts in them 45398 - Support detecting date formats containing "am/pm" as date times diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index f1f81d0699..3b6644db96 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -48,6 +48,7 @@ Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx + 45437 - Detect encrypted word documents, and throw an EncryptedDocumentException instead of a OOM 45404 - New class, hssf.usermodel.HSSFDataFormatter, for formatting numbers and dates in the same way that Excel does 45414 - Don't add too many UncalcedRecords to sheets with charts in them 45398 - Support detecting date formats containing "am/pm" as date times diff --git a/src/java/org/apache/poi/EncryptedDocumentException.java b/src/java/org/apache/poi/EncryptedDocumentException.java new file mode 100644 index 0000000000..4922d1c81d --- /dev/null +++ b/src/java/org/apache/poi/EncryptedDocumentException.java @@ -0,0 +1,24 @@ +/* ==================================================================== + 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; + +public class EncryptedDocumentException extends IllegalStateException +{ + public EncryptedDocumentException(String s) { + super(s); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormatter.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormatter.java index c1701e22b3..67291a22f1 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormatter.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDataFormatter.java @@ -68,78 +68,64 @@ import java.util.regex.Pattern; * can override the default format pattern with * HSSFDataFormatter.setDefaultNumberFormat(Format). Note: the * default format will only be used when a Format cannot be created from the - * cell's data format string. - * + * cell's data format string. + * * @author James May (james dot may at fmr dot com) * */ -public class HSSFDataFormatter { +public final class HSSFDataFormatter { /** Pattern to find a number format: "0" or "#" */ - protected Pattern numPattern; - + private static final Pattern numPattern = Pattern.compile("[0#]+"); + /** Pattern to find days of week as text "ddd...." */ - protected Pattern daysAsText; - + private static final Pattern daysAsText = Pattern.compile("([d]{3,})", Pattern.CASE_INSENSITIVE); + /** Pattern to find "AM/PM" marker */ - protected Pattern amPmPattern; + private static final Pattern amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE); /** A regex to find patterns like [$$-1009] and [$�-452]. */ - protected Pattern specialPatternGroup; - + private static final Pattern specialPatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); + /** General format for whole numbers. */ - protected Format generalWholeNumFormat; - + private static final Format generalWholeNumFormat = new DecimalFormat("#"); + /** General format for decimal numbers. */ - protected Format generalDecimalNumFormat; - + private static final Format generalDecimalNumFormat = new DecimalFormat("#.##########"); + /** A default format to use when a number pattern cannot be parsed. */ - protected Format defaultNumFormat; - - /** + private Format defaultNumFormat; + + /** * A map to cache formats. * Map formats */ - protected Map formats; - + private final 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(); + // init built-in formats + + Format zipFormat = ZipPlusFourFormat.instance; addFormat("00000\\-0000", zipFormat); addFormat("00000-0000", zipFormat); - - PhoneFormat phoneFormat = new PhoneFormat(); + + Format phoneFormat = PhoneFormat.instance; // 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); + addFormat("###-####;(###) ###-####", phoneFormat); + + Format ssnFormat = SSNFormat.instance; + 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 null if the any of the @@ -149,15 +135,15 @@ public class HSSFDataFormatter { *
  • the style's data format string is null or empty
  • *
  • the format string cannot be recognized as either a number or date
  • * - * + * * @param cell The cell to retrieve a Format for * @return A Format for the format String */ - protected Format getFormat(HSSFCell cell) { + private 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) { @@ -165,380 +151,375 @@ public class HSSFDataFormatter { } 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 (formatStr.equals("General")) { if (HSSFDataFormatter.isWholeNumber(cellValue)) { return generalWholeNumFormat; - } else { - return generalDecimalNumFormat; } - } else { - format = createFormat(cellValue, formatIndex, formatStr); - formats.put(formatStr, format); - return format; + return generalDecimalNumFormat; } + 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(); - + public Format createFormat(HSSFCell cell) { + int formatIndex = cell.getCellStyle().getDataFormat(); String formatStr = cell.getCellStyle().getDataFormatString(); - return createFormat(cell.getNumericCellValue(), formatIndex, formatStr); + 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); + Matcher m = specialPatternGroup.matcher(formatStr); + 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(); } - } catch (Exception e) { - return getDefaultFormat(cellValue); + formatStr = m.replaceAll(symbol); + m = specialPatternGroup.matcher(formatStr); } - + 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 -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--; - } + if(HSSFDateUtil.isADateFormat(formatIndex,formatStr) && + HSSFDateUtil.isValidExcelDate(cellValue)) { + return createDateFormat(formatStr, cellValue); + } + if (numPattern.matcher(formatStr).find()) { + return createNumberFormat(formatStr, cellValue); + } + // TODO - when does this occur? + return null; + } + + private Format createDateFormat(String pFormatStr, double cellValue) { + String formatStr = pFormatStr; + 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("@"); + hasAmPm = true; + amPmMatcher = amPmPattern.matcher(formatStr); + } + formatStr = formatStr.replaceAll("@", "a"); + + + 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." + */ + + StringBuffer sb = new StringBuffer(); + char[] chars = formatStr.toCharArray(); + boolean mIsMonth = true; + List ms = new ArrayList(); + for(int j=0; j -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--; + } + } + + try { + return new DecimalFormat(sb.toString()); + } catch(IllegalArgumentException iae) { + + // the pattern could not be parsed correctly, + // so fall back to the default number format + return getDefaultFormat(cellValue); + } + } + /** * 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 + * @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) { + public 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; + + // otherwise use general format } + if (isWholeNumber(cellValue)){ + return generalWholeNumFormat; + } + return generalDecimalNumFormat; } - + /** * Returns the formatted value of an Excel date as a String based * on the cell's DataFormat. 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(); - } - } - + */ + private String getFormattedDateString(HSSFCell cell) { + Format dateFormat = getFormat(cell); + Date d = cell.getDateCellValue(); + if (dateFormat != null) { + return dateFormat.format(d); + } + return d.toString(); + } + /** * Returns the formatted value of an Excel number as a String * based on the cell's DataFormat. 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); - } - } + */ + private String getFormattedNumberString(HSSFCell cell) { + + Format numberFormat = getFormat(cell); + double d = cell.getNumericCellValue(); + if (numberFormat == null) { + return String.valueOf(d); + } + return numberFormat.format(new Double(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 d.toString(); + } + return dateFormat.format(d); + } + // else Number + Format numberFormat = getFormat(value, formatIndex, formatString); + if (numberFormat == null) { + return String.valueOf(value); + } + return numberFormat.format(new Double(value)); + } - /** - * 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); - } - } - } - /** *

    * Returns the formatted value of a cell as a String regardless * of the cell type. If the Excel format pattern cannot be parsed then the - * cell value will be formatted using a default format. + * cell value will be formatted using a default format. *

    *

    When passed a null or blank cell, this method will return an empty * String (""). Formulas in formula type cells will not be evaluated. *

    - * + * * @param cell The cell * @return the formatted cell value as a String */ public String formatCellValue(HSSFCell cell) { return formatCellValue(cell, null); } - + /** *

    * Returns the formatted value of a cell as a String regardless * of the cell type. If the Excel format pattern cannot be parsed then the - * cell value will be formatted using a default format. + * cell value will be formatted using a default format. *

    *

    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. + * is responsible for setting the currentRow on the evaluator *

    - * - * @param cell The cell + * + * @param cell The cell (can be null) * @param evaluator The HSSFFormulaEvaluator (can be null) * @return a string value of the cell - * @throws IllegalArgumentException if cell type is - * HSSFCell.CELL_TYPE_FORMULA and evaluator is not null - * and the evlaluator's currentRow has not been set. */ - public String formatCellValue(HSSFCell cell, + public String formatCellValue(HSSFCell cell, HSSFFormulaEvaluator evaluator) throws IllegalArgumentException { - String value = ""; if (cell == null) { - return value; + return ""; } - + 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); + } catch (RuntimeException e) { + throw new RuntimeException("Did you forget to set the current" + + " row on the HSSFFormulaEvaluator?", e); } - } + } switch (cellType) - { - case HSSFCell.CELL_TYPE_FORMULA : - // should only occur if evaluator is null - value = cell.getCellFormula(); - break; + { + case HSSFCell.CELL_TYPE_FORMULA : + // should only occur if evaluator is null + return cell.getCellFormula(); - case HSSFCell.CELL_TYPE_NUMERIC : - - if (HSSFDateUtil.isCellDateFormatted(cell)) { - value = getFormattedDateString(cell); - } else { - value = getFormattedNumberString(cell); - } - break; + case HSSFCell.CELL_TYPE_NUMERIC : - case HSSFCell.CELL_TYPE_STRING : - value = cell.getRichStringCellValue().getString(); - break; - - case HSSFCell.CELL_TYPE_BOOLEAN : - value = String.valueOf(cell.getBooleanCellValue()); - } - return value; + if (HSSFDateUtil.isCellDateFormatted(cell)) { + return getFormattedDateString(cell); + } + return getFormattedNumberString(cell); + + case HSSFCell.CELL_TYPE_STRING : + return cell.getRichStringCellValue().getString(); + + case HSSFCell.CELL_TYPE_BOOLEAN : + return String.valueOf(cell.getBooleanCellValue()); + case HSSFCell.CELL_TYPE_BLANK : + return ""; + } + throw new RuntimeException("Unexpected celltype (" + cellType + ")"); } - - + + /** *

    * Sets a default number format to be used when the Excel format cannot be @@ -552,7 +533,7 @@ public class HSSFDataFormatter { * numeric cell. Therefore the code in the format method should expect a * Number value. *

    - * + * * @param format A Format instance to be used as a default * @see java.text.Format#format */ @@ -566,8 +547,8 @@ public class HSSFDataFormatter { } } defaultNumFormat = format; - } - + } + /** * Adds a new format to the available formats. *

    @@ -575,7 +556,7 @@ public class HSSFDataFormatter { * by java.text.Format#format) will be a double value from a * numeric cell. Therefore the code in the format method should expect a * Number value. - *

    + *

    * @param excelFormatStr The data format string * @param format A Format instance */ @@ -584,24 +565,30 @@ public class HSSFDataFormatter { } // Some custom formats - + + /** + * @return a DecimalFormat with parseIntegerOnly set true + */ + /* package */ static DecimalFormat createIntegerOnlyFormat(String fmt) { + DecimalFormat result = new DecimalFormat(fmt); + result.setParseIntegerOnly(true); + return result; + } /** * 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); + private static final class SSNFormat extends Format { + public static final Format instance = new SSNFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + private SSNFormat() { + // enforce singleton } - + /** Format a number as an SSN */ - public String format(Number num) { + public static String format(Number num) { String result = df.format(num); StringBuffer sb = new StringBuffer(); sb.append(result.substring(0, 3)).append('-'); @@ -609,66 +596,60 @@ public class HSSFDataFormatter { sb.append(result.substring(5, 9)); return sb.toString(); } - - public StringBuffer format(Object obj, StringBuffer toAppendTo, - FieldPosition pos) { + + 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); + */ + private static final class ZipPlusFourFormat extends Format { + public static final Format instance = new ZipPlusFourFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("000000000"); + private ZipPlusFourFormat() { + // enforce singleton } - + /** Format a number as Zip + 4 */ - public String format(Number num) { + public static 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) { + + 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); + */ + private static final class PhoneFormat extends Format { + public static final Format instance = new PhoneFormat(); + private static final DecimalFormat df = createIntegerOnlyFormat("##########"); + private PhoneFormat() { + // enforce singleton } - + /** Format a number as a phone number */ - public String format(Number num) { + public static String format(Number num) { String result = df.format(num); StringBuffer sb = new StringBuffer(); String seg1, seg2, seg3; @@ -676,28 +657,27 @@ public class HSSFDataFormatter { if (len <= 4) { return result; } - + seg3 = result.substring(len - 4, len); - seg2 = result.substring(Math.max(0, len - 7), len - 4); + 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(seg2).append('-'); } sb.append(seg3); return sb.toString(); } - - public StringBuffer format(Object obj, StringBuffer toAppendTo, - FieldPosition pos) { + + 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); } - } + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java b/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java index 77f93a10f6..08eabd223b 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java +++ b/src/scratchpad/src/org/apache/poi/hslf/exceptions/EncryptedPowerPointFileException.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,19 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - - - package org.apache.poi.hslf.exceptions; +import org.apache.poi.EncryptedDocumentException; + /** * This exception is thrown when we try to open a PowerPoint file, and * discover that it is encrypted - * - * @author Nick Burch */ - -public class EncryptedPowerPointFileException extends IllegalStateException +public class EncryptedPowerPointFileException extends EncryptedDocumentException { public EncryptedPowerPointFileException(String s) { super(s); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index a43357f021..c97d6a8bf0 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -28,6 +28,7 @@ import java.io.ByteArrayInputStream; import java.util.Iterator; +import org.apache.poi.EncryptedDocumentException; import org.apache.poi.POIDocument; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -174,9 +175,13 @@ public class HWPFDocument extends POIDocument directory.createDocumentInputStream("WordDocument").read(_mainStream); - // use the fib to determine the name of the table stream. + // Create our FIB, and check for the doc being encrypted _fib = new FileInformationBlock(_mainStream); + if(_fib.isFEncrypted()) { + throw new EncryptedDocumentException("Cannot process encrypted word files!"); + } + // use the fib to determine the name of the table stream. String name = "0Table"; if (_fib.isFWhichTblStm()) { diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/PasswordProtected.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/PasswordProtected.doc new file mode 100644 index 0000000000..0d6c169063 Binary files /dev/null and b/src/scratchpad/testcases/org/apache/poi/hwpf/data/PasswordProtected.doc differ diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java index 23681486f3..764b3239dc 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java @@ -22,6 +22,7 @@ import java.io.FileOutputStream; import junit.framework.TestCase; +import org.apache.poi.EncryptedDocumentException; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.model.StyleSheet; @@ -138,4 +139,18 @@ public class TestProblems extends TestCase { assertEquals(newLength, totalLength - deletedLength); } + + /** + * With an encrypted file, we should give a suitable + * exception, and not OOM + */ + public void testEncryptedFile() throws Exception { + try { + new HWPFDocument(new FileInputStream( + new File(dirname, "PasswordProtected.doc"))); + fail(); + } catch(EncryptedDocumentException e) { + // Good + } + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java index 15a19c0912..772a4fdfc7 100755 --- a/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/AllUserModelTests.java @@ -44,6 +44,7 @@ public class AllUserModelTests { result.addTestSuite(TestHSSFClientAnchor.class); result.addTestSuite(TestHSSFComment.class); result.addTestSuite(TestHSSFConditionalFormatting.class); + result.addTestSuite(TestHSSFDataFormatter.class); result.addTestSuite(TestHSSFDateUtil.class); result.addTestSuite(TestHSSFHeaderFooter.class); result.addTestSuite(TestHSSFHyperlink.class); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java index 39baedd885..f865d6e494 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDataFormatter.java @@ -25,141 +25,141 @@ import junit.framework.TestCase; /** * Unit tests for HSSFDataFormatter.java - * + * * @author James May (james dot may at fmr dot com) * */ -public class TestHSSFDataFormatter extends TestCase { +public final class TestHSSFDataFormatter extends TestCase { + + private final HSSFDataFormatter formatter; + private final HSSFWorkbook wb; - 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); + wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFDataFormat format = wb.createDataFormat(); - // 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", - "[$�-809]#,##0.00", - "[$�-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 + // create a row and put some cells in it + HSSFRow row = sheet.createRow(0); - { // 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); - } + // date value for July 8 1901 1:19 PM + double dateNum = 555.555; + + //valid date formats -- all should have "Jul" in output + String[] goodDatePatterns ={ + "[$-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 = { + "#,##0.0000", + "#,##0;[Red]#,##0", + "(#,##0.00_);(#,##0.00)", + "($#,##0.00_);[Red]($#,##0.00)", + "$#,##0.00", + "[$�-809]#,##0.00", + "[$�-2] #,##0.00", + "0000.00000%", + "0.000E+00", + "0.00E+00", + }; + + // invalid date formats -- will throw exception in DecimalFormat ctor + String[] badNumPatterns = { + "#,#$'#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); + } } /** @@ -169,14 +169,14 @@ public class TestHSSFDataFormatter extends TestCase { // 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 ===="); + log("==== VALID DATE FORMATS ===="); while (it.hasNext()) { HSSFCell cell = (HSSFCell) it.next(); - System.out.println(formatter.formatCellValue(cell)); + log(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); } @@ -184,70 +184,70 @@ public class TestHSSFDataFormatter extends TestCase { // test number formats row = wb.getSheetAt(0).getRow(1); it = row.cellIterator(); - System.out.println("\n==== VALID NUMBER FORMATS ===="); + log("\n==== VALID NUMBER FORMATS ===="); while (it.hasNext()) { HSSFCell cell = (HSSFCell) it.next(); - System.out.println(formatter.formatCellValue(cell)); - + log(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 ===="); + log("\n==== INVALID NUMBER FORMATS ===="); while (it.hasNext()) { HSSFCell cell = (HSSFCell) it.next(); - System.out.println(formatter.formatCellValue(cell)); + log(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)); + log("\n==== ZIP FORMAT ===="); + log(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)); + log("\n==== PHONE FORMAT ===="); + log(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)); - + log("\n==== SSN FORMAT ===="); + log(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), ""); - + 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 ===="); - + log("\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 + log(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)"); + log(formatter.formatCellValue(cell, evaluator) + "\t\t\t (with evaluator)"); assertEquals("24.50%", formatter.formatCellValue(cell,evaluator)); } @@ -259,24 +259,23 @@ public class TestHSSFDataFormatter extends TestCase { */ public void testSetDefaultNumberFormat() { HSSFRow row = wb.getSheetAt(0).getRow(2); - Iterator it = row.cellIterator(); + 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 ===="); + log("\n==== DEFAULT NUMBER FORMAT ===="); while (it.hasNext()) { - HSSFCell cell = (HSSFCell) it.next(); + HSSFCell cell = (HSSFCell) it.next(); cell.setCellValue(cell.getNumericCellValue() * Math.random() / 1000000 - 1000); - System.out.println(formatter.formatCellValue(cell)); + log(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); - } - + private static void log(String msg) { + if (false) { // successful tests should be silent + System.out.println(msg); + } + } }