読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

JavaFX + Apache POIでSpreadsheet操作 2

JavaFX Java ApachePOI

はじめに

前回の続きです。

今回は名前付きのセルから値を取得するなど、Apache POIを使ってSpreadsheetから値を取得したり、書き込んだりした内容をまとめます。

セルの名前からWorkbook上のセルの列番号を取得する

まずはWorkbookを開いて、セルの名前(「ID」)を元に対象セルの列番号を取得します。
さらに値が含まれる最大の行番号を取得して、IDの最大値を取得してみます。

FileInputStream fileStream = new FileInputStream(filePath);
// Workbookを開く.
Workbook workbook = WorkbookFactory.create(fileStream);
if(workbook == null){
    observer.onError(new Throwable("Workbookの取得に失敗しました。"));
}
// とりあえずSheet名は固定.
Sheet sheet = workbook.getSheet("Sheet1");
if(sheet == null){
    observer.onError(new Throwable("Sheetの取得に失敗しました。"));
}
// 「ID」という名前がつけられたセルを取得する.
Name name = targetWorkbook.getName("ID");
CellReference reference = new CellReference(name.getRefersToFormula());

//  対象セルの列番号を取得.
int idColumnNum = reference.getCol();
// 対象Sheetの値が入った最終行番号を取得する.
int lastRowNum = sheet.getLastRowNum();
int newId = 1;

// 0行目は項目名を入力しているため値は取得しない.
if(lastRowNum > 0){
    Row lastRow = sheet.getRow(lastRowNum);
    if(lastRow != null){
        Cell lastCell = lastRow.getCell(idColumnNum);
        // 数値を取得すると、「1.0」のような形になるため一旦float型で受取り、その後intに変換する.
        float lastId = NumParser.TryParseFloat(lastCell.toString());
        newId = (int)lastId + 1;
    }
}
else{
    // IDが未登録の場合は1を付与する.
    newId = 1;
}

セルの値を取得する

以前触れましたが、セルの値を取得するには「Cell.getStringCellValue」のように型を指定した上で行います。

この型が実際のセルの型と違っている場合はエラーが発生するため、「Cell.getCellType」で型を確認した上で値を取得していました。

しかし、POI ver.3.15ではこの「getCellType」がdeprecatedで表示されます。

ver.3系ではintが返るのですが、ver.4系ではこれが廃止され、enumが返る「getCellTypeEnum」が「getCellType」に改名される、という変更のためのようです。
とりあえずver.3.15では「getCellType」への対処方法が見つからなかったため、deprecatedでも気にせず使うか、上記のように「Cell.toString」とString型として一旦受け取るなどする必要がありそうです。

指定シート上のセルの名前からセルの列番号を取得する

上記のコードは、セルの名前がWorkbook全体に適用されている、またはWorkbook内に該当の名前が一つしかない場合は問題なく動作します。
しかし、セルの名前がSheet単位で登録されていて、Workbook全体で見ると同一の名前が複数登録されている場合は一番左にあるSheetの値が返ってしまいます。

CellReferenceがWorkbookではなくSheetから取得できれば良さそうですが、調べた限り難しいようでした。

あれこれ試した結果、Workbookに付与されている名前を一旦全て取得して、対象のシートのものだけを取り出すことで解決できました。
(もう少しスマートな方法があるような気はするのですが)

CellValueGetter.java

public class CellValueGetter {
    private int columnNum;
    public List getTargetCellValueList(Workbook workbook, String sheetName){
        // 指定されたSheet内のセルの名前を全て取得する.
        List targetNames = workbook.getAllNames();
        return targetNames.stream()
                .filter(filteredName -> filteredName.getSheetName().equals(sheetName))
                .map(object -> (Name)object)
                .collect(Collectors.toList());
    }
    public int getTargetCellColumnNum(List cellNameList, String targetCellName){
        // targetCellNameの名前が付けられたセルの列番号を返す.
        // 対象のNameを取得.
        Optional foundCellName = cellNameList.stream().filter(name -> name.getNameName().equals(targetCellName)).findFirst();

        // Nullでなければ列番号を取得.
        Optional foundColumnNum = foundCellName.map(cellName -> {
            CellReference reference = new CellReference(cellName.getRefersToFormula());
            if(reference == null){
                return -1;
            }
            int gotColmnNum = (int)reference.getCol();
            return gotColmnNum;
        });
        columnNum = -1;
        foundColumnNum.ifPresent(num -> columnNum = num);
        return columnNum;
    }
}

下記のように呼び出します。

// 指定したシート内の名前を全て取得する.
List allCellNameList = cellValueGetter.getTargetCellValueList(workbook, sheet.getSheetName());
// 「ID」と名付けられたセルの列番号を取得する.
int idColumnNum = cellValueGetter.getTargetCellColumnNum(allCellNameList, "ID");

セルの追加

取得したIDから次のIDを発行し、書き込みます。

// 現在値が入っている行番号最大値を取得する.
int lastRowNum = sheet.getLastRowNum();
int newId = 1;
if(lastRowNum > 0){
    Row lastRow = sheet.getRow(lastRowNum);
    if(lastRow != null){
        Cell lastCell = lastRow.getCell(idColumnNum);

        float lastId = NumParser.TryParseFloat(lastCell.toString());
        newId = (int)lastId + 1;
    }
}
else{
    // IDが未登録の場合は1を付与する.
    newId = 1;
}
// 行を追加する.
Row newRow = sheet.createRow(lastRowNum + 1);

if(newRow != null){
    // セルを追加する.
    newRow.createCell(idColumnNum).setCellValue(newId);
    newRow.createCell(titleColumnNum).setCellValue(title);

    // 現在の日付を取得.
    LocalDate currentDate = LocalDate.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
    newRow.createCell(lastUpdateDateColumnNum).setCellValue(currentDate.format(formatter));

    FileOutputStream outputStream = new FileOutputStream(filePath);
    // Workbookへの書き込み.
    workbook.write(outputStream);
    outputStream.close();
}
  • 新しい行(値の入ったセルが一つもない行)、新しいセルに値を追加する場合はCreateを行う必要があります。
  • 今回更新日はString型でセットしていますが、POIでセルに値をセットする場合に扱うことのできる型にCalendarがあるため、日付型を使いたい場合はCalendar型にしてあげると良いかと思います。

終わりに

さて、これでID取得部分はできるようになりました。

次はこれを使ってもう一つのSpreadsheetに値を入れたり、シートの表示・非表示切り替えを行いたいと思います。

参考

Apache POI

Stream

Optional

Date