Apache POI 調査

Excelを操作したいみたいな状況は割とよくある。Pythonとかのスクリプト言語だと結構対処法をが見つかるけど、Scalaだと、Java資産のApache POIくらいしかない上に情報もそんなにない(poi.scala のようなラッパーライブラリもあるようだけど、プロダクションで使うには早計感ある)。Apache POIの使い方を調べて、人並みに使えるようになろうと思う。参考記載のJavaでのApache POIの使い方を元に、Scalaで書いて感じを掴む。

準備

必要なライブラリを build.sbt に追記する。

libraryDependencies ++= Seq(
      "org.apache.poi" % "poi"       % "5.0.0",
      "org.apache.poi" % "poi-ooxml" % "5.0.0"
)

Excelファイルの作成

build.sbt の準備ができたら、Excelファイルを作成していく。

XLS ファイルの作成

今だと使う機会は少ないかもしれないが一応。XLSファイルを扱うのは、上記のライブラリのうち poi の方だ。

package dev.tchiba.researchApachePoi

import org.apache.poi.hssf.usermodel.HSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLS {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xls"
    val workbook = new HSSFWorkbook() // XLSにはHSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

XLSX ファイルの作成

XLSXファイルを作成するには、build.sbt 記載のライブラリのうち、後者の poi-ooxml が必要だ。処理はほとんど同じで、生成するインスタンスのクラスのみが異なる。

package dev.tchiba.researchApachePoi

import org.apache.poi.xssf.usermodel.XSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLSX {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xlsx"
    val workbook = new XSSFWorkbook() // XLSX を生成する場合は、XSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

Excelファイルにデータを書き込む

空のExcelファイルが作れても一ミリも嬉しくないので、データを書き込んでいこう。サンプルに下記のようなデータモデルを用意する。

case class Person(
    firstName: String,
    lastName: String,
    birthDay: LocalDate,
    email: String,
    phoneNumber: String,
    married: Boolean
)

これをExcelに書き込むクラスを用意しよう。参考がJavaなので、あんまり Scala っぽくないけど、下手に外れると詰まるから諦める。

package dev.tchiba.researchApachePoi

import models.Person

import org.apache.poi.ss.usermodel.{Sheet, Workbook}
import org.apache.poi.xssf.usermodel.{XSSFSheet, XSSFWorkbook}

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

class PersonExcelWriter {

  def write(fileName: String, personList: List[Person]): Unit = {
    val workbook = prepareWorkbook(personList)
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }

  private def prepareWorkbook(personList: List[Person]): Workbook = {
    val workbook: XSSFWorkbook = new XSSFWorkbook()
    val personSheet: XSSFSheet = workbook.createSheet("Persons")
    prepareHeader(personSheet)
    prepareTable(personSheet, personList)
    workbook
  }

  private def prepareHeader(personSheet: Sheet): Unit = {
    // ヘッダー行を作成する
    val headerRow = personSheet.createRow(0)

    // セルを作成する
    val firstNameHeaderCell = headerRow.createCell(0)
    val lastNameHeaderCell  = headerRow.createCell(1)
    val birthdayHeaderCell  = headerRow.createCell(2)
    val emailHeaderCell     = headerRow.createCell(3)
    val phoneNumberCell     = headerRow.createCell(4)
    val marriedHeaderCell   = headerRow.createCell(5)

    // セルに値をセットする
    firstNameHeaderCell.setCellValue("First name")
    lastNameHeaderCell.setCellValue("Last name")
    birthdayHeaderCell.setCellValue("Birthday")
    emailHeaderCell.setCellValue("Email")
    phoneNumberCell.setCellValue("Phone number")
    marriedHeaderCell.setCellValue("Married")
  }

  private def prepareTable(personSheet: Sheet, personList: List[Person]): Unit = {
    personList.zipWithIndex.foreach { case (person, index) =>
      // 行を作成する
      val row = personSheet.createRow(index + 1)

      // セルを作成する
      val firstNameCell   = row.createCell(0)
      val lastNameCell    = row.createCell(1)
      val birthdayCell    = row.createCell(2)
      val emailCell       = row.createCell(3)
      val phoneNumberCell = row.createCell(4)
      val marriedCell     = row.createCell(5)

      // セルに値をセットする
      firstNameCell.setCellValue(person.firstName)
      lastNameCell.setCellValue(person.lastName)
      birthdayCell.setCellValue(person.birthDay.toString)
      emailCell.setCellValue(person.email)
      phoneNumberCell.setCellValue(person.phoneNumber)
      marriedCell.setCellValue(person.married)
    }
  }
}

手順としては

  1. ワークブックを用意する
  2. シートを用意する
  3. カラムにデータを埋めていく
  4. 最後に、ワークブックをファイルに保存する

となる。

これを使う処理を書こう。

package dev.tchiba.researchApachePoi

import models.Person

import java.time.LocalDate

object Main {
  def main(args: Array[String]): Unit = {
    val person1 = Person(
      firstName = "太郎",
      lastName = "田中",
      birthDay = LocalDate.of(1990, 2, 2),
      email = "taro.tanaka@xxx.com",
      phoneNumber = "090XXXXXXX",
      married = true
    )

    val person2 = Person(
      firstName = "花子",
      lastName = "佐藤",
      birthDay = LocalDate.of(1992, 9, 9),
      email = "hanako.sato@xxx.com",
      phoneNumber = "090YYYYYYY",
      married = false
    )

    val personList = List(person1, person2)

    val writer = new PersonExcelWriter()
    writer.write("person.xlsx", personList)
  }
}

実行して出力されたファイルを開いてみると、(私のPCにはExcel入っていないので、Numbersを利用)、想定通りに値が入っていることが分かる。

参考

Apache POI に関する体系的な情報が探しづらいなか、下記の本がKindle Unlimited にあったのが救いだった。