14

我目前正在开发一个使用 Apache POI 读取 excel 文件的项目。

我的任务似乎很简单,我只需要获取在 excel 文件中显示的单元格值。我知道根据单元格的单元格类型执行 switch 语句。但是如果数据是这样的

9,000.00

POI9000.0在我做的时候给我getNumericCellValue()。当我强制单元格为字符串类型并执行getStringCellValue()它时,它会给我9000. 我需要的是数据在excel中的呈现方式。

我发现一些帖子告诉使用DataFormat类,但根据我的理解,它要求您的代码了解单元格的格式。就我而言,我不知道单元格可能具有的格式。

那么,如何检索单元格值,就像它在 Excel 中的呈现方式一样?

4

3 回答 3

29

Excel 将一些单元格存储为字符串,但大多数存储为应用了特殊格式规则的数字。您需要做的是让这些格式规则针对数字单元格运行,以生成看起来像它们在 Excel 中所做的字符串。

幸运的是,Apache POI 有一个类可以做到这一点 - DataFormatter

您需要做的就是:

 Workbook wb = WorkbookFactory.create(new File("myfile.xls"));
 DataFormatter df = new DataFormatter();

 Sheet s = wb.getSheetAt(0);
 Row r1 = s.getRow(0);
 Cell cA1 = r1.getCell(0);

 String asItLooksInExcel = df.formatCellValue(cA1);

无论单元格类型是什么,DataFormatter 都会使用 Excel 中应用的规则为您尽可能地格式化它

于 2013-10-16T11:12:41.507 回答
1

事实上,永远不可能使用写入单元格时定义的语言环境准确地获得格式化的单元格值。这是由于那里的语言环境观察者以及内部语言环境 excel 前缀在之后格式化时从未被重用的事实;

POI 3.17 的分析(可能会因为查看组件的内部完成方式而改变)

例如:Locale.US 的单元格样式的 dateConverted 格式(来自 CellStyle.getDataFormatString()),格式为 dd MMM yyyy hh:mm:ss 为:
“[$-0409]dd MMM yyyy hh:mm:ss; @" 其中本地 excel 内部前缀 = [$-0409]

它是从 DateFormatConverter.localPrefixes 私有静态映射中获得的。

这是一些解决此问题的代码:

/**
 * Missing method in POI to enable the visualisation asIs of an cell with a
 * different locale in a xls document.
 *
 * @param style
 *            the cell style localized.
 * @return the Locale found using internal locationPrefixes.
 */
private final Locale extractLocaleFromDateCellStyle(final CellStyle style) {

    final String reOpenedFormat = style.getDataFormatString();

    LOGGER.info("Data Format of CellStyle : " + reOpenedFormat);
    Locale locale = getLocaleFromPrefixes(extractPrefixeFromPattern(reOpenedFormat));
    LOGGER.info("Found locale : " + locale);
    return locale;
}

/**
 * Extracts the internal prefix that represent the local of the style.
 *
 * @param pattern
 *            the DataFormatString of the cell style.
 * @return the prefix found.
 */
private final String extractPrefixeFromPattern(final String pattern) {

    Pattern regex = Pattern.compile(REGEX_PREFIX_PATTERN);
    Matcher match = regex.matcher(pattern);

    if (match.find()) {
        LOGGER.info("Found prefix: " + match.group(1));
        // return only the prefix
        return match.group(1);
    }
    return null;
}

/**
 * Reverse obtain the locale from the internal prefix from
 * <code>DateFormatConverter.localePrefixes</code> private static field.
 * <p>
 * Uses reflection API.
 *
 * @param prefixes
 *            the prefixes
 * @return the local corresponding tho the prefixes.
 */
public static Locale getLocaleFromPrefixes(final String prefixes) {

    try {

        @SuppressWarnings("unchecked")
        Map<String, String> map = getStaticPrivateInternalMapLocalePrefix();

        String localPrefix = null;

        // find the language_Country value matching the internal excel
        // prefix.
        for (Map.Entry<String, String> entry : map.entrySet()) {

            LOGGER.info("value : " + entry.getValue() + ", key :"
                    + entry.getKey());

            if (entry.getValue().equals(prefixes)
                    && !StringUtils.isBlank(entry.getKey())) {
                localPrefix = entry.getKey();
                break;
            }
        }

        // Generate a Locale with language, uppercase(country) info.
        LOGGER.info(localPrefix);
        if (localPrefix.indexOf('_') > 0) {
            String[] languageCountry = localPrefix.split("_");
            return new Locale(languageCountry[0],
                    StringUtils.defaultString(languageCountry[1]
                            .toUpperCase()));
        }

        // nothing found.
        return null;

        // factorized the multiples exceptions.
    } catch (Exception e) {
        throw new UnsupportedOperationException(e);
    }

}

/**
 * gets the internal code map for locale used by Excel.
 * 
 * @return the internal map.
 * @throws NoSuchFieldException
 *             if the private field name changes.
 * @throws IllegalAccessException
 *             if the accessible is restricted.
 */
private static Map<String, String> getStaticPrivateInternalMapLocalePrefix()
        throws NoSuchFieldException, IllegalAccessException {
    // REFLECTION
    Class<?> clazz = DateFormatConverter.class;
    Field fieldlocalPrefixes = (Field) clazz
            .getDeclaredField(DATE_CONVERTER_PRIVATE_PREFIXES_MAP);

    // change from private to public.
    fieldlocalPrefixes.setAccessible(true);

    @SuppressWarnings("unchecked")
    Map<String, String> map = (Map<String, String>) fieldlocalPrefixes
            .get(clazz);

    LOGGER.info("MAP localPrefixes : " + map);
    return map;
}

因此,下面的简单代码应该可以解决问题。请注意,代码未针对空值进行全面测试,并且取决于您使用的 POI 版本,直到它们在那里更改LOCALE OBSERVER MADNESS :)

    ....
    final CellStyle cellStyle = reopenedCell.getCellStyle();

    Locale locale = extractLocaleFromDateCellStyle(cellStyle);
    LOGGER.info("FOUND LOCAL : " + locale);

    // use the same local from the cell style during writing.
    DataFormatter df = new DataFormatter(locale);

    String reOpenValue = df.formatCellValue(reopenedCell);

问候。

于 2018-08-20T23:13:40.470 回答
0

使用CellType你可以检查每一件事

if (cellValue.getCellType() == Cell.CELL_TYPE_NUMERIC)

// cellValue 是数字

if (cellValue.getCellType() == Cell.CELL_TYPE_STRING)

// cellValue 是一个字符串

日期也以数字形式给出,当时使用dateUtil检查给定单元格是否为日期

if (DateUtil.isCellDateFormatted(cellData))

在您可以将单元格值转换为日期之后

于 2013-10-16T10:55:22.367 回答