【问题标题】:Apache POI - Read a cell formatted by TEXT() formulaApache POI - 读取由 TEXT() 公式格式化的单元格
【发布时间】:2019-06-06 03:53:59
【问题描述】:

我有一个包含日期的 EXCEL 文件。它们被格式化为 TEXT,例如:=TEXT(TODAY(); "yyyy-MM-dd")

在 EXCEL 中,日期被正确格式化为文本,但是当我使用 Apache POI 读取单元格时,它将返回数值。 为什么?为什么 POI 不读取格式化文本值?

我不想在我的 JAVA 应用程序中格式化日期,因为 EXCEL 文件应该定义格式(每个值可能不同)。

这是我读取单元格值的代码:

private static String getString(Cell cell) {
 if (cell == null) return null; 

 if (cell.getCellTypeEnum() != CellType.FORMULA) { 
  switch (cell.getCellTypeEnum()) { 
   case STRING: 
    return cell.getStringCellValue().trim(); 
   case BOOLEAN: 
    return String.valueOf(cell.getBooleanCellValue());
   case NUMERIC: 
    return String.valueOf(cell.getNumericCellValue()); 
   case BLANK: 
    return null; 
   case ERROR: 
    throw new RuntimeException(ErrorEval.getText(cell.getErrorCellValue())); 
   default: 
    throw new RuntimeException("unexpected cell type " + cell.getCellTypeEnum());
  }
 } 
 FormulaEvaluator evaluator = cell.getSheet().getWorkbook().getCreationHelper().createFormulaEvaluator();
 try { 
  CellValue cellValue = evaluator.evaluate(cell); 
  switch (cellValue.getCellTypeEnum()) { 
   case NUMERIC: 
    return String.valueOf(cellValue.getNumberValue());
   case STRING: 
    return cellValue.getStringValue().trim(); 
   case BOOLEAN: 
    return String.valueOf(cellValue.getBooleanValue()); 
   case ERROR: 
    throw new RuntimeException(ErrorEval.getText(cellValue.getErrorValue())); 
   default: 
    throw new RuntimeException("unexpected
cell type " + cellValue.getCellTypeEnum()); 
  } 
 } catch (RuntimeException e) { 
  throw new RuntimeException("Could not evaluate the value of " + cell.getAddress() + " in sheet " + cell.getSheet().getSheetName(), e);
 }
}

【问题讨论】:

  • 如果我在 VBA 环境中遇到您的问题,我会使用 Format 函数将数值转换为文本,例如 MyDate$ = Format(Date, "dd/mm/yyyy")
  • 谢谢,但这不是我想要的。我不想在我的应用程序中定义格式。 exelfile 应该定义它。正如我所写,每个单元格之间的格式可能会发生变化。这就是我尝试使用 =TEXT(xxx, "format") 的原因
  • 对不起,不明白你的问题。如果您希望 Excel 确定格式 Excel 需要一个数字,但如果 Excel 确实得到一个数字,那么您的抱怨是什么?
  • 只有在使用的Excel 不是英文时才会出现问题。那么这个公式不是真的=TEXT(A2,"yyyy-MM-dd"),而是我的德语Excel中的=TEXT(A2,"JJJJ-MM-TT")。而且因为apache poiFormulaEvaluator 直到现在还没有语言环境设置,所以无法正确评估该公式。然后只能希望存储的单元格值应该是所需的字符串。因此,如果单元格公式以“TEXT”开头,则不要评估,而是从Excel 的最后一次评估中获取字符串单元格值。

标签: java excel apache-poi


【解决方案1】:

org/apache/poi/ss/formula/functions/TextFunction.java提供补丁

当然,我的第一个答案只是纠正症状。最终的解决方案显然应该是评估 TEXT 函数应该考虑不同的语言环境。

工作草案:

更改org/apache/poi/ss/formula/functions/TextFunction.java如下:

...
    /**
     * An implementation of the TEXT function<br>
     * TEXT returns a number value formatted with the given number formatting string. 
     * This function is not a complete implementation of the Excel function, but
     *  handles most of the common cases. All work is passed down to 
     *  {@link DataFormatter} to be done, as this works much the same as the
     *  display focused work that that does. 
     *
     * <b>Syntax<b>:<br> <b>TEXT</b>(<b>value</b>, <b>format_text</b>)<br>
     */
    public static final Function TEXT = new Fixed2ArgFunction() {

        public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, ValueEval arg1) {
            double s0;
            String s1;
            try {
                s0 = evaluateDoubleArg(arg0, srcRowIndex, srcColumnIndex);
                s1 = evaluateStringArg(arg1, srcRowIndex, srcColumnIndex);
            } catch (EvaluationException e) {
                return e.getErrorEval();
            }

            try {
            // Correct locale dependent format strings
                Locale locale = org.apache.poi.util.LocaleUtil.getUserLocale();
                if ("de".equals(locale.getLanguage())) {
                    s1 = s1.replace("T", "D"); // Tag = Day
                    // Monat = Month
                    s1 = s1.replace("J", "Y"); // Jahr = Year
                    //... further replacements
                } else if ("fr".equals(locale.getLanguage())) {
                    s1 = s1.replace("J", "D"); // Jour = Day
                    // Mois = Month
                    s1 = s1.replace("A", "Y"); // Année = Year
                    //... further replacements
                } //... further languages

            // Ask DataFormatter to handle the String for us
                String formattedStr = formatter.formatRawCellContents(s0, -1, s1);
                return new StringEval(formattedStr);
            } catch (Exception e) {
                return ErrorEval.VALUE_INVALID;
            }
        }
    };
...

那么获取内容就这么简单:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.util.*;
import org.apache.poi.util.LocaleUtil;

import java.io.FileInputStream;
import java.util.Locale;

class ExcelEvaluateDiffLocales {

 private static String getString(Cell cell, DataFormatter formatter, FormulaEvaluator evaluator) {
  String text = "";
  if (cell.getCellType() == CellType.FORMULA) {
   String cellFormula = cell.getCellFormula();
   text += cellFormula + ":= ";
  }
  try {
   text += formatter.formatCellValue(cell, evaluator);
  } catch (org.apache.poi.ss.formula.eval.NotImplementedException ex) {
   text += ex.toString();
  }
  return text;
 }

 public static void main(String[] args) throws Exception {

  //Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xls"));
  Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));

  Locale locale = new Locale("fr", "FR");
  LocaleUtil.setUserLocale(locale);
  DataFormatter formatter = new DataFormatter();
  FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();

  Sheet sheet = wb.getSheetAt(0);
  for (Row row : sheet) {
   for (Cell cell : row) {
    CellReference cellRef = new CellReference(row.getRowNum(), cell.getColumnIndex());
    System.out.print(cellRef.formatAsString());
    System.out.print(" - ");

    String text = "";
    text = getString(cell, formatter, evaluator);

    System.out.println(text);

   }
  }

  wb.close();

 }
}

【讨论】:

    【解决方案2】:

    仅当使用的Excel 不是英文时才会出现此问题。那么这个公式不是真的=TEXT(A2,"yyyy-MM-dd"),而是我的德语Excel中的=TEXT(A2,"JJJJ-MM-TT")

    如您所见,TEXT 函数中的格式部分将始终依赖于区域设置,尽管所有其他公式部分始终是 en_US 区域设置。这是因为该格式部分位于公式中的字符串中,不会更改。所以在德语中是 =TEXT(A2,"JJJJ-MM-TT") (Year = Jahr, Day = Tag),在法语中是 =TEXT(A2,"AAAA-MM-JJ") (Year = Année, Day = Jour)。

    而且由于 apache poiFormulaEvaluator 直到现在还没有区域设置,因此无法正确评估该公式。

    那么我们有两种可能。

    首先,我们希望存储的单元格值应该是所需的字符串。因此,如果单元格公式以“TEXT”开头并包含“JJJJ-MM-TT”,则不要评估,因为这将不正确。而是从Excel 的最后一次评估中获取字符串单元格值。

    其次,我们可以用公式中的 en_US 替换区域设置相关格式部分,然后让apache poi 进行评估。至少如果我们只想读取而不是重写 Excel 文件,这不会破坏 Excel 文件中的内容。


    代码优先方法:

    import org.apache.poi.ss.usermodel.*;
    import org.apache.poi.ss.usermodel.CellType;
    import org.apache.poi.ss.util.*;
    
    import org.apache.poi.ss.formula.eval.ErrorEval;
    
    import java.io.FileInputStream;
    
    class ReadExcelExample {
    
     private static String getString(Cell cell, FormulaEvaluator evaluator) {
      if (cell == null) return "null";
      String text = "";
      switch (cell.getCellType()) {
      //switch (cell.getCellTypeEnum()) {
       case STRING:
        text = cell.getRichStringCellValue().getString();
       break;
       case NUMERIC:
        if (DateUtil.isCellDateFormatted(cell)) {
         text = String.valueOf(cell.getDateCellValue());
        } else {
         text = String.valueOf(cell.getNumericCellValue());
        }
       break;
       case BOOLEAN:
        text = String.valueOf(cell.getBooleanCellValue());
       break;
       case FORMULA:
        text = cell.getCellFormula();
    
        //if formula is TEXT(...,"JJJJ-MM-TT") then do not evaluating:
        if (cell.getCellFormula().startsWith("TEXT") && cell.getCellFormula().contains("JJJJ-MM-TT")) {
         text = text + ": value got from cell = " + cell.getRichStringCellValue().getString();
    
        } else {
         CellValue cellValue = evaluator.evaluate(cell); 
         switch (cellValue.getCellType()) {
         //switch (cellValue.getCellTypeEnum()) {
          case STRING:
           text = text + ": " + cellValue.getStringValue();
          break;
          case NUMERIC:
           if (DateUtil.isCellDateFormatted(cell)) {
            text = text + ": " + String.valueOf(DateUtil.getJavaDate(cellValue.getNumberValue()));
           } else {
            text = text + ": " + String.valueOf(cellValue.getNumberValue());
           }
          break;
          case BOOLEAN:
           text = text + ": " + String.valueOf(cellValue.getBooleanValue());
          break;
          case ERROR:
           throw new RuntimeException("from CellValue: " + ErrorEval.getText(cellValue.getErrorValue()));
          default:
           throw new RuntimeException("unexpected cellValue type " + cellValue.getCellType()); 
         }
        }
       break;
       case ERROR:
        throw new RuntimeException("from Cell: " + ErrorEval.getText(cell.getErrorCellValue())); 
       case BLANK:
        text = "";
       break;
       default:
        throw new RuntimeException("unexpected cell type " + cell.getCellType());
      }
    
      return text;
     }
    
     public static void main(String[] args) throws Exception {
    
      //Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xls"));
      Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
    
      DataFormatter formatter = new DataFormatter(new java.util.Locale("en", "US"));
      FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
    
      Sheet sheet = wb.getSheetAt(0);
      for (Row row : sheet) {
       for (Cell cell : row) {
        CellReference cellRef = new CellReference(row.getRowNum(), cell.getColumnIndex());
        System.out.print(cellRef.formatAsString());
        System.out.print(" - ");
    
        String text = "";
        try {
        text = getString(cell, evaluator);
        } catch (Exception ex) {
         text = ex.toString();
        }
        System.out.println(text);
    
       }
      }
    
      wb.close();
    
     }
    }
    

    德语 Excel:

    结果:

    A1 - Value
    B1 - Formula
    A2 - Fri Jan 11 00:00:00 CET 2019
    B2 - TEXT(A2,"JJJJ-MM-TT"): value got from cell = 2019-01-11
    A3 - 123.45
    B3 - A3*2: 246.9
    B4 - java.lang.RuntimeException: from CellValue: #DIV/0!
    B5 - TODAY(): Fri Jan 11 00:00:00 CET 2019
    B6 - B5=A2: true
    A7 - java.lang.RuntimeException: from CellValue: #N/A
    B8 - TEXT(TODAY(),"JJJJ-MM-TT"): value got from cell = 2019-01-11
    

    英文计算器:

    结果:

    A1 - Value
    B1 - Formula
    A2 - Fri Jan 11 00:00:00 CET 2019
    B2 - TEXT(A2,"yyyy-MM-dd"): 2019-01-11
    A3 - 123.45
    B3 - A3*2: 246.9
    B4 - java.lang.RuntimeException: from CellValue: #DIV/0!
    B5 - TODAY(): Fri Jan 11 00:00:00 CET 2019
    B6 - B5=A2: true
    A7 - java.lang.RuntimeException: from CellValue: #N/A
    B8 - TEXT(TODAY(),"yyyy-MM-dd"): 2019-01-11
    

    代码第二种方法(将依赖于语言环境的格式部分替换为 en_US 格式部分):

    import org.apache.poi.ss.usermodel.*;
    import org.apache.poi.ss.usermodel.CellType;
    import org.apache.poi.ss.util.*;
    
    import java.io.FileInputStream;
    import java.util.Locale;
    
    class ExcelEvaluateTEXTDiffLocales {
    
     private static String getString(Cell cell, DataFormatter formatter, FormulaEvaluator evaluator, Locale locale) {
      String text = "";
      if (cell.getCellType() == CellType.FORMULA) {
       String cellFormula = cell.getCellFormula();
       text += cellFormula + ":= ";
    
       if (cellFormula.startsWith("TEXT")) {
        int startFormatPart = cellFormula.indexOf('"');
        int endFormatPart = cellFormula.lastIndexOf('"') + 1;
        String formatPartOld = cellFormula.substring(startFormatPart, endFormatPart);
        String formatPartNew = formatPartOld;
        if ("de".equals(locale.getLanguage())) {
         formatPartNew = formatPartNew.replace("T", "D"); // Tag = Day
         // Monat = Month
         formatPartNew = formatPartNew.replace("J", "Y"); // Jahr = Year
         //...
        } else if ("fr".equals(locale.getLanguage())) {
         formatPartNew = formatPartNew.replace("J", "D"); // Jour = Day
         // Mois = Month
         formatPartNew = formatPartNew.replace("A", "Y"); // Année = Year
         //...
        } //...
        cellFormula = cellFormula.replace(formatPartOld, formatPartNew);
        cell.setCellFormula(cellFormula);
       }
    
      }
      try {
       text += formatter.formatCellValue(cell, evaluator);
      } catch (org.apache.poi.ss.formula.eval.NotImplementedException ex) {
       text += ex.toString();
      }
    
      return text;
     }
    
     public static void main(String[] args) throws Exception {
    
      //Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xls"));
      Workbook wb  = WorkbookFactory.create(new FileInputStream("SAMPLE.xlsx"));
    
      Locale locale = new Locale("fr", "CH");
      DataFormatter formatter = new DataFormatter(locale);
      FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
    
      Sheet sheet = wb.getSheetAt(0);
      for (Row row : sheet) {
       for (Cell cell : row) {
        CellReference cellRef = new CellReference(row.getRowNum(), cell.getColumnIndex());
        System.out.print(cellRef.formatAsString());
        System.out.print(" - ");
    
        String text = "";
        text = getString(cell, formatter, evaluator, locale);
    
        System.out.println(text);
    
       }
      }
    
      wb.close();
    
     }
    }
    

    法语计算器:

    结果:

    A1 - Value
    B1 - Formula
    A2 - 1/11/2019
    B2 - TEXT(A2,"AAAA-MM-JJ"):= 2019-01-11
    A3 - 123.45
    B3 - A3*2:= 246.9
    B4 - 1/A4:= #DIV/0!
    B5 - TODAY():= 1/12/2019
    B6 - B5=A2:= FALSE
    A7 - NA():= #N/A
    B8 - TEXT(TODAY(),"AAAA-MM-JJ"):= 2019-01-12
    

    提示:这里使用的apache poi 版本是4.0.1。也许较低的版本可能会有进一步的评估问题。

    【讨论】:

    • 非常感谢! cell.getRichStringCellValue() 成功了!在我们的环境中,本地设置为英语,但日期格式为德语。 Locale.getDefault()LocaleUtil.getUserLocale() 将返回 en_US,但格式是用德语定义的。即_DD.MM.JJJJ_.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多