【问题标题】:Parse Excel Decimal-Format with Apache-POI to Java BigDecimal使用 Apache-POI 将 Excel 十进制格式解析为 Java BigDecimal
【发布时间】:2018-02-09 08:33:01
【问题描述】:

我想在 Apache-POI(XSSF 和 SAX 事件 API)的帮助下导入 XLSX 文件。

因为 Excel 将数字存储为浮点数,所以在 java 中需要将它们格式化为它们在 Excel 中的原始格式。这可以通过读取单元格格式来实现:

String cellStyle = sheetReader.getAttributeValue(null, "s");
if (cellStyle != null) {
  // save the format of the cell for later use.
  int styleIndex = Integer.parseInt(cellStyle);
  XSSFCellStyle style = stylesTable.getStyleAt(styleIndex);
  formatIndex = style.getDataFormat();
  formatString = style.getDataFormatString();
  if (formatString == null) {
    // formatString could not be found, so it must be a builtin format.
    formatString = BuiltinFormats.getBuiltinFormat(formatIndex);
  }
}
...
// format the floating-point value
String xlsxValue = formatter.formatRawCellContents(
  Double.parseDouble(value),
  formatIndex,
  formatString);

上面的代码对我来说效果很好......但它给我的数字就像它们在德国语言环境中运行 Excel 时最初在 Excel 中格式化一样。此类数字的示例:

10,30
100.00.00,43

现在我要如何重新格式化这些数字,以便将它们提供给 Java Double 和 Java BigDecimal?

Apache-POI 貌似没有提供针对这种情况的 Utility-Classes,但是如何在 java 中处理这些数字呢?

我已经侵入了 poi 以使这种情况发生,但是没有其他方法吗?

// hack apache-poi classes that are private, so we can retrieve the 'format'
// which helps us to transform the formated value to the expected java-format
CellStyle style = new CellStyleHack(formatIndex, formatString);
Cell cell = new CellHack(Double.parseDouble(xlsxValue), style);

java.text.Format format = formatter.createFormat(cell);
if (format instanceof DecimalFormat) {
  DecimalFormat decimalFormat = ((DecimalFormat) format);
  char dSep = decimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
  char gSep = decimalFormat.getDecimalFormatSymbols().getGroupingSeparator();
  String cSymbol = decimalFormat.getDecimalFormatSymbols().getCurrencySymbol();

  // java always expects '.' as decimal seperator for BigDecimal and Double.
  xlsxValue = xlsxValue.replace("" + gSep, "");
  xlsxValue = xlsxValue.replace(dSep, '.');
  if (cSymbol != null) {
    xlsxValue = xlsxValue.replace(cSymbol, "").trim();
  }
}

【问题讨论】:

  • 考虑到浮点问题,我只想使用双打。但是您可以做的是获取双精度值,然后执行与 Excel 相同的操作,使用 BigDecimal.round 舍入到 15 个有效数字。我在这里展示了这个:stackoverflow.com/questions/41067328/….
  • @AxelRichter 是的,我可以这样做,谢谢指出。问题是我们的软件中有一个约定,即我们的 BigDecimals 在小数和最大值之前需要最多 9 位数字。小数后 7 位,因此它们可以存储在数据库中,我们可以确保它们不大于我们的数据库字段允许的大小。 Excel 的值例如是:62.474099999999993,大于我们的小数后 7 位。我不想切断其余的数字,我也不想失去价格价值的精确度。我上面的代码示例(这是一个 hack)没有这个问题。
  • 目前无法测试。但是你可以尝试使用DataFormatter(java.util.Locale locale)Locale.US 吗?或者在使用DataFormatter之前将LocaleUtil.setUserLocale设置为Locale.US
  • @AxelRichter 好主意。我已经尝试过如下:formatter = new DataFormatter(Locale.US)。我尝试使用formatRawCellContents --> 值:“1333”,格式字符串:“#,##0”结果:“1,333”。值:“1”,格式字符串:“0.00”,结果:“1.00”。所以它确实改变了一些东西,但它仍然产生“,”的结果。但谢谢这有帮助。如果使用 Locale.US 它永远不会产生逗号,这将是一个解决方案。
  • @AxelRichter 嘿,现在我想起来了。如果结果现在始终符合 Locale.US,它将始终具有“。”作为小数分隔符。酷谢谢。我认为这可以解决我的问题

标签: java excel apache-poi


【解决方案1】:

来自Apache POI docs

Cell.getNumericCellValue() 应该已经返回一个双精度值。

对于其他格式,使用 DataFormatter 类:

DataFormatter 包含用于格式化存储在 细胞。这对于报告和 GUI 演示很有用,当您 需要完全按照 Excel 中的数据显示数据。支持的格式 包括货币、SSN、百分比、小数、日期、电话号码、 邮政编码等

使用 XSSF SAX 事件 API 时,您没有这种访问权限,但幸运的是,在

https://svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/poi/xssf/eventusermodel/XLSX2CSV.java

展示了如何通过实现 SheetContentsHandler 接口并覆盖其 cellstartRow 来检索单元格的数字/格式化字符串值, endRow 等方法(在示例中,查找 XLSX2CSV.SheetToCSV.cell(...) 方法。

希望这会有所帮助。

【讨论】:

  • 因为我使用“XSSF 和 SAX 事件 API”(出于性能原因)我不能使用“单元格”。 Cell 仅适用于 SXSSF API。即使我可以使用 Cell。 Excel 会保存这样的数字:123.43500000009 如果我只在 java 中使用这些双精度值,我会遇到浮点问题,并且 Excel 的原始格式会丢失。 (原始数字是:“123.435”(小数点后 3 位)。我只希望 Apache-POI 给我的数字是 BigDecimal 或 Double 模拟已在 excel 中显示给用户的原始 Excel 数字,所以我可以在java中使用它
  • 即使在 XSSF Cell 类中,您也可以直接从单元格 (poi.apache.org/apidocs/org/apache/poi/xssf/usermodel/…) 中获取双精度数值,如果您需要与显示完全相同的值,则应使用 poi.apache.org/apidocs/org/apache/poi/xssf/usermodel/…。如果这还不够,您还可以使用 XSSFCell.getCTCell() 访问底层 CTCell 并从那里开始工作(原始数据)
  • 您对 XSSF Cell 的看法是正确的。但请参阅以下有关“XSSF 和 SAX 事件 API”的链接:poi.apache.org/spreadsheet/how-to.html#xssf_sax_api XSSF 单元仅用于“用户 API(HSSF 和 XSSF)”?当我使用 XMLStreamReader 解析工作表时,我没有 Cell-Object。
  • 然后检查示例代码:svn.apache.org/repos/asf/poi/trunk/src/examples/src/org/apache/…XLSX2CSV.SheetToCSV.cell() 覆盖的方法有你需要的(双精度和其他格式的单元格值解析)
【解决方案2】:

在@AxelRichter 的帮助下,以下解决方案现在解决了我的问题:

// we must use Locale.US, because we want to make sure that the DataFormatter will
// always product "." as decimal-separator and "," as thousands-separator.
this.formatter = new DataFormatter(Locale.US);

// format the floating-point value
String xlsxValue = formatter.formatRawCellContents(
        Double.parseDouble(value),
        formatIndex,
        formatString);

// xlsxValue may contain format-symbols, which we need to remove...
xlsxValue = xlsxValue.replaceAll("[^\\d.]", "");

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-02
    • 2014-08-30
    相关资源
    最近更新 更多