Excel操作ライブラリ Apache POIの使い方 (Kotlin)

Apache POI というEXCELを操作するためのJava用のライブラリですが、
この記事では、Kotlinで使う場合の例を記載していきます。

Gradleセットアップ

2021年11月時点でのApache POIの最新は、5.1.0です。

build.gradle.kts

plugins{
    kotlin("jvm")version"1.5.31"
}

repositories{
    mavenCentral()
}

dependencies{
    // Apache POI
    implementation("org.apache.poi:poi:5.1.0")
    implementation("org.apache.poi:poi-ooxml:5.1.0")

    testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.1")
}

tasks.test{
    useJUnitPlatform()
}

build.gradle.ktsに書かないといけない必須のdependenciesは、実は、implementation("org.apache.poi:poi-ooxml:5.1.0") くらいです。

コアライブラリは、依存ライブラリになってるので、特に指定しなくても暗黙的に読み込まれすが、一応上記のように書いています。

基本の使い方

最低限の記述で、エクセルファイルを作成してみましょう。

import org.apache.poi.ss.usermodel.WorkbookFactory
import java.io.File

fun main() {
    // 空のWorkbookインスタンスを作成
    // 引数はxssf形式かどうか (XSSFが最新のエクセルバージョン形式です)
    // closeを忘れないように、use拡張関数を使う
    WorkbookFactory.create(true).use { wb ->
        // シートの作成
        // 作成とともにワークブックインスタス内にシートが追加されている。
        val sheet = wb.createSheet("サンプルシート1")

        // 行の作成 (ゼロインデックス: 1行目がゼロ)
        val row = sheet.createRow(0)

        // セルの作成  (ゼロインデックス: A列がゼロ)
        val cell = row.createCell(0)

        // セルに値を書き込み
        cell.setCellValue("セルの値")

        // ファイルへ書き込み
        // OutputStreamのインスタンスへ書き込む
        wb.write(File("sample.xlsx").outputStream())
    }
}

作成したエクセルファイルを開いてみるとこんな感じ。

ワークブック操作

ワークブックインスタンスの取得には、WorkbookFactory.create メソッドを使います。
いくつかのオーバーロードメソッドがあり、FileやInputStreamから読み込んでインスタンスを作成することや、先の例のように空ワークブックを作成することができます。

また、このインスタンスは、Closeableなので、use拡張関数をつかって、自動closeするようにしておくと良いです。(Javaならtry-with-resourceで)
Fileインスタンスから作成して編集した場合、closeのタイミングで上書き保存となるので注意が必要です。

ワークブックの作成、読み取りと保存。

// 空のワークブック作成
WorkbookFactory.create(true).use { wb ->
    // TODO: ワークブック操作
}

// 既存ファイルの読み込みと終了時に書き込み
// useスコープを抜けるタイミング、close時に上書き保存されます。
WorkbookFactory.create(File("sample.xlsx")).use { wb ->
    // TODO: ワークブック操作
}

// 既存ファイルの読み込み。(読み取り専用)
// 第三引数に、readOnlyかどうかをBooleanで指定できます。
// ※close時に上書き保存はされません。
WorkbookFactory.create(File("sample.xlsx"), null, true).use { wb ->
    // TODO: ワークブック操作
}

// 既存ファイルの読み込み。(読み取り専用)
// InputStream経由の場合、エクセルファイルは読み込み時にすべてオンメモリ展開される。
// ※close時に上書き保存はされません。
WorkbookFactory.create(File("sample.xlsx").inputStream()).use { wb ->
    // TODO: ワークブック操作
}

保存は、Fileインスタンスを渡した場合は、closeの際に上書き保存されますが、
それ以外で保存したい場合は、writeメソッドをコールします。

// ファイルへ書き込み
// OutputStreamのインスタンスへ書き込む
wb.write(File("sample.xlsx").outputStream())

シート操作

Sheetインスタンスの取得と追加

// 既存のシートを取得
val sheet = wb.getSheet("サンプルシート1")

// 存在しないシートは、nullを返してくるので、
// 例外を出さずにnull判定したい場合は、Sheet? で変数宣言して扱うとよい。
val sheetOrNull: Sheet? = wb.getSheet("サンプルシート2")
if (sheetOrNull != null) {
    println(sheetOrNull.sheetName)
}

// イテレーションで取得
for (s in wb.sheetIterator()) {
    println(s.sheetName)
}

// 新規シートを作成
val newSheet = wb.createSheet("追加シート")

注意が必要なのは、存在しないシート名でインスタンス取得しようとする場合です。
Kotlinだと、Workbook#getSheet メソッドの返り値は Sheet! のようなプラットフォーム型(Null許容情報がない型)になります。

シートが存在しない場合には実際にnullが返るので、そのような状況を扱う場合は、Sheet? と変数宣言してnull判定してから利用すると良いでしょう。

セル操作

行とセルの取得と作成、セルの値取得と設定。

// 行の取得(ない場合はnullを返す)
val row = sheet.getRow(0)
// セルの取得(ない場合はnullを返す)
val cell = row.getCell(0)
// セルの値を取得(文字列型で)
val value = cell.stringCellValue
println(value)

// A2 のRowとCellを作成
val cellA2 = sheet.createRow(1).createCell(0)
cellA2.setCellValue("A2の値")

// CellUtil#getRow と、CellUtil#getCell は、なかったらcreateしてくれる。
val cellA3 = CellUtil.getCell(CellUtil.getRow(2, sheet), 0)
cellA3.setCellValue("A3の値")

Cellのインスタンスを取得するには、まずRowのインスタンスが必要になります。
どちらもSheetインスタンスから、Sheet#getXXXで取得できますが、作成されるまではnullが返ります。
作成するには、Sheet#createXXXを使用しましょう。

CellUtilクラスには、nullだったら内部でcreateしてくれる便利メソッドが用意されていますが、Kotlinで使うには少しまどろっこしいです。
下記のような拡張関数を用意しておくと便利です。

/**
 * Row, Cellがなかったらcreateして返す。
 */
fun Sheet.cell(rowIndex: Int, columnIndex: Int): Cell {
    return CellUtil.getCell(CellUtil.getRow(rowIndex, this), columnIndex)
}

/**
 * "A1"のようなアドレス指定で、セルを取得する。
 * なかったらcreateして返す。
 */
fun Sheet.cell(cellRef: String): Cell {
    val ref = CellReference(cellRef)
    check(ref.sheetName == null) { "Specifying sheet name is unsupported" }

    return this.cell(ref.row, ref.col.toInt())
}

CellReferenceには、シート名を含めた記述も出来ますが、
↑の拡張関数ではややこしくなるためサポートしないようにしています。

// 拡張関数を使ってセル取得 (row, col)
val cellB5 = sheet.cell(4, 1)
println(cellB5.stringCellValue)

// なじみのあるアドレス形式でセル取得
val cellA3 = sheet.cell("A3")
println(cellA3.stringCellValue)

セルの値

セルに値を設定するには、Cell#setCellValueを使用します。

設定した値によって、CELL_TYPEが固定されるので、取得時は正しいタイプで取得しないとnullや例外が発生します。
定義されているCELL_TYPEはこれだけあります。

  • _NONE
  • NUMERIC
  • STRING
  • FORMULA
  • BLANK
  • BOOLEAN
  • ERROR

Cellの値を文字列で取得する。

// 先のほどの自作の拡張関数でCellインスタンス取得
val cell = sheet.cell("A1")

// FORMULAの場合、キャッシュされてるタイプで判断する。
val cellType = cell.cellType.let { if (it == CellType.FORMULA) cell.cachedFormulaResultType else it }

// STRINGタイプ以外なら空文字で取得
val str = when (cellType) {
    CellType.STRING -> cell.stringCellValue ?: ""
    else -> ""
}

println("ret:\"$str\"")

↑の例では、文字列以外のセルタイプの場合は、空文字で取得しています。

もし、NUMERICで設定されているセルを文字列で取得したい場合は、
when式にNUMERICのケースを追加して、cell.numericCellValue.toString() とするのが良いでしょう。

FORMULAタイプは、エクセルの関数式がはいっているタイプになります。
計算済みの値がキャッシュされているので、キャッシュされているタイプを判断して取得することができます。

まとめ

エクセルをJavaから操作するためのライブラリ Apache POIについて、
Kotlinを使った簡単な例で紹介しました。
なかなか回りくどくて扱いづらいかもしれませんが、
Kotlinの拡張関数を定義すればラクに使うことができます。

コメント

タイトルとURLをコピーしました