2010年08月19日

POIでセルの値をとるのは大変 数式編

前回までに数値と日付について、表示されている内容で取得することが困難なことを述べててきました。
今回ご紹介する数式の場合も厄介です。

数式そのものを取得するには Cell#getCellFormula() で取得できますが、欲しいのは計算された結果(表示されている内容)です。
数式の結果が数値になるとあらかじめ判別できれば、Cell#getNumericCellValue()で取得できますが、必ずしも数値になるとは限りませんし、先の数値編で説明したとおり、表示されている内容での取得は、残念ながらこのままではできません。
また、数式の結果が文字列の場合にも、Cell#getRichStringCellValue().getString()として取得しようとすると、数式が返ってきます。
ここでちょっと検索してみると、FormulaEvaluatorというインターフェースがあることがわかりました。
数式の結果を取得するためのインターフェースです。
処理概要は、
 1.ワークブックオブジェクトからCrationHelperインターフェースを取得
 2.CrationHelper#createFormulaEvaluator()でFormulaEvaluatorを取得
 3.FormulaEvaluator#evaluate()を利用してCellValueを取得
 4.Cellと同様にCellValue#getCellType()に対応したメソッドを使用して値を取得
となります。
これでめでたしめでたしとなりそうですが、ちょっと問題があります。

何が問題なのでしょうか?
上記手順をコーディングすると下記のようになります。
  switch(cell.getCellType()) {
  case
   ・・・
  case Cell.CELL_TYPE_FORMULA:
    Workbook wb = cell.getSheet().getWorkbook();
    CreationHelper crateHelper = wb.getCreationHelper();
    FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    CellValue cellValue = evaluator.evaluate(cell);
    switch (cellValue.getCellType()) {
    case Cell.CELL_TYPE_BOOLEAN:
      return String.valueOf(cellValue.getBooleanValue());
    case Cell.CELL_TYPE_NUMERIC:
      if (DateUtil.isCellDateFormatted(cell)
        || CellDateFormat.contains(cell.getCellStyle().getDataFormat())
      ) {      //日付
        Date theDate = cell.getDateCellValue();
        DateFormat dateFormat = CellDateFormat.getFormt(cellStyle.getDataFormat()).getDateFormat();
        return dateFormat.format(theDate);
      } else { // 数値
        DataFormatter formatter = new DataFormatter();
        return formatter.formatCellValue(cell);
      }
      break;
     ・・・
  case
   ・・・
数式の処理についてのみ注目して、さらにだいぶ省略して記載していますが、それでも長いソースです。
もちろん関数化を行って少しはすっきりすると思いますが、やっぱり冗長していると感じます。
見た目が美しくないことが問題なのでしょうか?
すでにお気づきの方もいらっしゃると思いますが、このソースにはバグが含まれています。
日付・数値の判定処理・値取得に、Cellオブジェクトではなく、CellValueオブジェクトを使用しています。
このままでは正しく判定して値取得ができません。

ではどうやって解決すればよいのでしょう?
ここまで根気よく読んできてくれた方は想像がつくかもしれませんが、ここでもやはり妥協します。
どのような妥協をするかというと、値の取得しか行わないことを前提に、FormulaEvaluator#evaluateInCell()を使用して、セルの値を計算式の結果にしてしまいます
今回の妥協はかなり強引ですので、使用方法は十分に注意してください。
セルから値を取得するメソッドに対し、数式の場合のみFormulaEvaluator#evaluateInCell()を使用してセルを計算結果に置き換えて再帰的に値取得メソッドを呼び出します。
極力見た目どおりにString型で取得する場合のコーディング例を記載します。
public String getCellValue(Cell cell) {
  switch(cell.getCellType()) {
  case Cell.CELL_TYPE_BLANK:
    return "";
  case Cell.CELL_TYPE_STRING:
    return cell.getRichStringCellValue().getString();
  case Cell.CELL_TYPE_BOOLEAN:
    return String.valueOf(cell.getBooleanCellValue());
  case Cell.CELL_TYPE_NUMERIC:
    // 日付・整数・少数の判別を行う
    if (DateUtil.isCellDateFormatted(cell)
      || CellDateFormat.contains(cell.getCellStyle().getDataFormat())
    ) {       //日付
      Date theDate = cell.getDateCellValue();
      DateFormat dateFormat = CellDateFormat.getFormt(cellStyle.getDataFormat()).getDateFormat();
      return dateFormat.format(theDate);
    } else {  // 数値
      DataFormatter formatter = new DataFormatter();
      String retValue = formatter.formatCellValue(cell);
      if (retValue.endsWith("_ ")) {
        retValue = retValue.substring(0, retValue.length() -2);
      }
      return retValue;
    }
  case Cell.CELL_TYPE_FORMULA:
    Workbook wb = cell.getSheet().getWorkbook();
    CreationHelper crateHelper = wb.getCreationHelper();
    FormulaEvaluator evaluator = crateHelper.createFormulaEvaluator();
    return getCellValue(evaluator.evaluateInCell(cell));
  default:
    return "";
  }
}
ここまでの感想として、苦労して値を取得してもすべてを救えないなら、
 予め決められた書式を使用し、対応するプログラムを書く
ようにしたほうが良いのではないかと思ってしまいます。
ExcelとPOJOマッピングライブラリでは、これまでのことを踏まえた上で、取得する型を指定し、値変換可能な処理にしています。
興味がございましたら、こちらもご参考にしてください。
ラベル:Poi java ExcelPOJO
posted by しん at 00:31| Comment(1) | POI | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
if (retValue.endsWith("_ ")) {
retValue = retValue.substring(0, retValue.length() -2);
}

どうして上記のソースを使いますか。
Posted by yzbh at 2014年10月01日 15:30
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。