javaでExcelファイルを出力する際に良くつかわれるPOIを拡張した、ExcelテンプレートエンジンのjXLSですが、forEachのループで行を複製した際に、テンプレートとなる範囲に行結合が含まれていると、結合範囲が下方向に伸びてしまい、レイアウトが崩れるという不具合があります。
原因は、net.sf.jxls.util.Util.copyRow(Sheet, Sheet, Row, Row)
にあることはわかっているのですが、なにぶん英語がうまく通じないからかどうか良くわかりませんが、コミッターに届けることができていません。
GPL系のライセンスはなんだか怖いイメージがあって、自前で修正したものを使うのもあれなもんで、jXLSのPoi Objects Accessの機能を使って回避することにしました。
まず、通常のループでは、次のように行結合したセルを含んでいると、結合範囲が下に伸びてしまい、意図した結果にはなりません。
↓変換後
(A2:B4,A5:B7,A8:B10)となってほしいのに、なぜか、(A2.B4,A5:B12)となってしまいます。
そこで、PoiObjectsAccessを使い、テンプレート中で、POIのメソッドを使って動的に結合します。
↓ ${sheet.addMergedRegion(new("org.apache.poi.ss.util.CellRangeAddress",r,hssfRow.rowNum,0,1))}
しかし、jXLSが使っている式言語エンジンのjEXLの仕様かどうかは知りませんが、返り値がある式の場合は、D列のようにその返り値がjXLSの変換結果に残ってしまいます。
これをまたPoiObjectsAccessで無理やり消してしまってもいいのですが、あんまり式が増えてくると、どんどんメンテナンスがしんどくなってくるので、Poi操作のメソッドをラッピングしたメソッドを作ってしまい、それを呼ぶことにしました。
行結合の場合はこんな感じ
public void mergeRegion(HSSFSheet sheet,int row1,int row2,int col1,int col2){
CellRangeAddress range = new CellRangeAddress(row1, row2, col1, col2);
sheet.addMergedRegion(range);
}
もう少し凝って、すでに結合されている場合とかもいろいろやりようがありますが、あまりやりすぎるとループ中で呼び出した場合のパフォーマンスに影響がでる可能性もあるのでほどほどに。
状態を持つ必要はないのでstaticなutilクラスのstaticメソッドにしたいところですが、jXLSのテンプレートからstaticクラスを呼び出す方法がよくわからないので、インスタンス化できるクラスとして作成し、XLSTransformerにdependent Java beans の一つとして渡してしまいます。
map.put("poiHelper", new PoiHelper());
XLSTransformer transformer = new XLSTransformer();
transformer.transformXLS(path, map,rowsMergeDestXls);
これで、テンプレート中で${poiHelper.mergeRegion(sheet,r,hssfRow.rowNum,1,1)}
のように自分の好きな形で呼び出すことができるようになり、ソースが多少すっきりして、かつvoidだと結果も出力されないので、工夫次第でメンテナンスが楽になります。
サンプルソースはこちらです。
Templateから呼び出すメソッドをいろいろ工夫するといろいろ面白いことができると思います。
私は、Ajaxの非同期アクセスをトリガーとして起動するWebのサーバー側での変換処理で、進捗状況のステータスを管理するオブジェクトをSessionと連動させ、HTMLのクライアント側でリアルタイムにプログレスバーに反映させる処理を実装したりしています。
なお、jXLSは、データ量が多かったりエクセル方眼紙の帳票等で扱うCellの数が多いと、すべてをメモリ上で展開してしまうため、急にパフォーマンスが悪くなる傾向があるという欠点があります。
うまくJVMのメモリ管理を設定すればもっと良い感じになるかもしれませんが、私はあまりその辺のチューニングに詳しくないため、今のところ運用や設計で回避している状態です。
そのため、次のプロジェクトでは、JETTというライブラリーに切り替えるつもりです。
こちらのライブラリを試してみたところ、エクセル方眼紙の台帳を1シートに500件出力した場合で、jXLSで20分かかったのが、同じものをJETTでは30秒で出力できることを確認しています。
(エクセル方眼紙台帳500件ってあんた!っていう突込みは無しでお願いします。)
機能もこちらの方が多いようなので、これから導入を検討している方はこちらを調査・検討してみるのもいいかもしれません。
Comparison of JETT to jXLS
ただ、まだ情報がほとんど出回っていないので、人柱になることは覚悟の上として、その辺のノウハウがたまればまた何か公開できればと考えています。