scalaでバックエンドを開発しているaxtstar(@axtstart)です。
今回は、Excelをプログラムから扱うにあたって、面倒だなと感じていた部分をバインド変数の様なアイデアで少し楽にするライブラリを作成、公開しましたのでそちらの紹介をします。
面倒な話
Excelからデータを取得するのってなくなりそうで無くならないですよね。
例えばこんなExcelシートからデータを取得したいとします。
↓こんなの(data.xlsxとします)
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とします)
データを取得したい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
↓出力用フォーマット(レイアウト)
import com.axtstar.asta4e.ExcelMapper ExcelMapper.setData( "template.xlsx" // 変数テンプレートパス ,"layout.xlsx" // 出力用フォーマットExcelパス ,"output.xlsx" // 出力先のパス , result :_* // 出力用データ )
↓出力結果
ケースクラス変換
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ファイルの作成ができます。
全体イメージ
まとめ
Excelでの開発がだいぶ楽になりました。
いかがでしたでしょうか?
アスタミューゼでは現在、エンジニア・デザイナーを募集中です。 興味のある方はぜひ下記バナーからご応募ください。