astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Excelとscalaのケースクラス間で入出力ができるライブラリをgithubに公開しました

f:id:astamuse:20180731192645j:plain scalaでバックエンドを開発しているaxtstar(@axtstart)です。

今回は、Excelをプログラムから扱うにあたって、面倒だなと感じていた部分をバインド変数の様なアイデアで少し楽にするライブラリを作成、公開しましたのでそちらの紹介をします。

面倒な話

Excelからデータを取得するのってなくなりそうで無くならないですよね。

例えばこんなExcelシートからデータを取得したいとします。

↓こんなの(data.xlsxとします)

f:id:astamuse:20180727120440p:plain

apache-poiを使ったプログラムの場合、下記のようなコードになると思います。

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

val workbook = WorkbookFactory.create(new File("data.xlsx"))

val sheet = workbook.getSheetAt(0)

val numeric = sheet.getRow(0).getCell(1).getNumericCellValue // numeric: Double = 111.0
val stringRow = sheet.getRow(1).getCell(1).getStringCellValue // stringRow: String = 111
val dateRow = sheet.getRow(2).getCell(1).getDateCellValue // dateRow: java.util.Date = Thu Jan 01 00:00:00 JST 1970
val formula = sheet.getRow(3).getCell(1).getStringCellValue // formula: String = 111
val bool = sheet.getRow(4).getCell(1).getBooleanCellValue// bool: Boolean = true
val time = sheet.getRow(5).getCell(1).getDateCellValue // time: java.util.Date = Sun Dec 31 17:25:47 JST 1899
val userDate = sheet.getRow(6).getCell(1).getDateCellValue // userDate: java.util.Date = Mon Jul 02 22:35:54 JST 2018

このようにロケーションの指定や、データ型毎の変換など、正確にデータを取得するのは結構大変な作業です。

また、Excelとの突き合わせが、行、列番号とのマッピングになるため、間違いやすいです。

場所がずれるなんてことも結構おこります。

Asta4e

そこで、弊社製Webフレームワークであるasta4dを参考にして、似たような*1テンプレート入出力の機能をapache-poiでExcelを使って行うライブラリを作成しました。

ソースはgithubで公開し、Maven Centralにおいてますのでdependencyに下記のように追加すれば試すことができます。

build.sbtの場合(scala2.11 scala2.12 共通)

libraryDependencies += "com.axtstar" %% "asta4e" % "0.0.6"

mavenの場合

scala2.11

<dependency>
    <groupId>com.axtstar</groupId>
    <artifactId>asta4e_2.11</artifactId>
    <version>0.0.6</version>
</dependency>

scala2.12

<dependency>
    <groupId>com.axtstar</groupId>
    <artifactId>asta4e_2.12</artifactId>
    <version>0.0.6</version>
</dependency>

テンプレートになるExcelシートに、変数となる値を${変数名}で記載します。

↓こんなかんじ(template.xlsxとします)

f:id:astamuse:20180727120001p:plain

データを取得したいExcelを指定します、先ほどの変数の場所に実際の値が入っているもの(つまりdata.xlsx)を、以下の要領で取得できます。

import com.axtstar.asta4e.ExcelMapper

val result = ExcelMapper.getData("template.xlsx" // 変数テンプレート(バインドする変数名を記載したExcel)
  ,"data.xlsx" // 入力用データ(読み込みたいExcel)
  ,List() // 無視するシート名(読み込まないシート名のリスト)
)

resultは下記のようになっています。

result: IndexedSeq[(String, Map[String,Any])] = 
Vector((
Sheet1,
Map(
userDate -> Mon Jul 02 22:35:54 JST 2018, 
string -> 111, 
bool -> true, 
date -> Thu Jan 01 00:00:00 JST 1970, 
formula -> 111, 
numeric -> 111.0, 
time -> Sun Dec 31 17:25:47 JST 1899)))

${変数名}で指定した変数名に対して、その位置上の値を取得格納し、 { シート名 ->Map(バインド変数名、値)} を返します。

こうすることで、セル位置の指定をコードから排除し、位置の指定をExcel上で行うようにして、生産性を向上することができます。

テンプレートエンジン機能

Excelを出力したい場合は、上記の考え方と同様にテンプレートのExcel上のバインド変数の位置にデータを出力できる関数を用意しました。(テンプレートエンジン機能)

ただ、出力はレイアウトのことを考えて、出力用フォーマットのExcelを別途指定して、そちらからコピーして出力を行います。*2

↓出力用フォーマット(レイアウト)

f:id:astamuse:20180731185046p:plain

import com.axtstar.asta4e.ExcelMapper

ExcelMapper.setData(
  "template.xlsx" // 変数テンプレートパス
  ,"layout.xlsx" // 出力用フォーマットExcelパス
  ,"output.xlsx" // 出力先のパス
  , result :_* // 出力用データ
)

↓出力結果

f:id:astamuse:20180731195036p:plain

ケースクラス変換

scalaではエンティティの定義をケースクラスで行うことがほとんどだと思いますので、Excelとケースクラスの相互変換の関数も用意しておきました。

テンプレートExcelと同じ変数名*3を持つケースクラスを作成します。

import java.util.Date

case class Data(
              numeric: Double,
              string:String,
              date:Date,
              formula:String,
              bool:Boolean,
              time:Date,
              userDate:Date
)

Excel → ケースクラス

下記の方法で先程のExcelからケースクラスを取得できます。

val data = ExcelMapper.by[Data].getDataAsAny("template.xlsx" // バインド変数テンプレート
  ,"data.xlsx" // 入力用データ
  ,List() // 無視するシート名
)

結果は下記です

data: IndexedSeq[(String, Option[Data])] = Vector((Sheet1,
Some(
Data(111.0,
111,
Thu Jan 01 00:00:00 JST 1970,
111,
true,
Sun Dec 31 17:25:47 JST 1899,
Mon Jul 02 22:35:54 JST 2018)
)
))

少しわかりにくいですが構造的に

Sheet名 --+ Option(ケースクラス) のリスト構造を持っています。

ケースクラス → Excel

こちらは逆変換、ケースクラスを含むIndexedSeq[(String, Option[ケースクラス])]を指定してExcelファイルの出力を行います。

ExcelMapper.By[Data].setData4cc("template.xlsx" // バインド変数テンプレート
  ,"layout.xlsx" // 出力用フォーマットExcelパス
  ,"output.xlsx" // 出力パス
  , data
)

上記でExcelファイルの作成ができます。

全体イメージ

f:id:astamuse:20180801023957p:plain

まとめ

Excelでの開発がだいぶ楽になりました。

いかがでしたでしょうか?

アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ下記バナーからご応募ください。

*1:Asta4dでは、CSSセレクタを使って、HTML要素にアクセスすることができます。

*2:変数テンプレートは位置だけを指定、実際のフォーマットはレイアウトである出力用フォーマットで指定する考え方

*3:今の実装だとケースクラスの変数が過剰にある場合は動作せずNoneが帰ります。不足している場合は動作します。

Copyright © astamuse company, ltd. all rights reserved.