Apache POIでの列削除 と行シフト

JavaからMicrosoft Officeのファイル(Wrod、Excel、PowerPoint 2003形式からOOXMLまで)を読み込み、作成編集が出来る大変便利なライブラリ Apache POIの話。

便利……なのですが、Excelファイルを操作する際に行の削除やシフト(一定範囲の行を上や下にずらす)は出来ても、列の削除(シフト)については特にそういうメソッドが用意されていません。結構困る仕様なんですが不思議な事に情報を探していてもあまり触れられていない感じなのでメモとして。Apache POI 3.9準拠です。

 

1.列シフト削除

手順としてはセルの入れ替えを行ごとにちまちま行っていきます。

  1. Sheet.getRow()で行(Row)を読み込む。<ループ1>
  2. Row.getCell()でx列目(削除の起点となる列)からのセルにアクセス。<ループ2>
  3. Row.getCell()で1列目の列削除したい分y個右の列にあたるセルにアクセス。Row.getLastCellNum()で範囲外のようであれば随時よろしい感じの処理をする。
  4. 2のセルを3で取得した内容に書き換える。CellTypeの値も同様にによろしくやる。Stringセルなら中の文字列の移動、Integerなら~、blankなら~という部分はif elseで処理。
  5. 3のセルをRow.removeCell()で削除。
  6. ループの終端処理はSheet.getLastRowNum()辺りを見て止めます。

次に結合セルをどうにかします。結合セルの情報は上記セルやRowとは別次元の領域に存在するらしく、セルの移動を行ってもどこからどこまでのセルが結合されているか、という情報は書き変わりません。

  1. Sheet.getMargedRegion()で結合セル情報を取り出します。添え字が必要になるのでgetNumMergedRegions()最大値を取得して回します。取り出した結果はCellRangeAdress型で、[A3:A5] といった形のセル範囲の情報を保持しています。
  2. 上記の取出しループの中でCellRangeAdressの列に対して、列を削除する分列の値をマイナスした値をリストに保存しておきます。列がマイナスになるようであれば適宜省いてしまうなどの処理を行います。この辺りはよろしくやってください。
  3. 既存の結合セルをすべてSheet.removeMergedRegion()で削除します。この時も添え字が必要になるのですが、削除するごとに残った結合セルの添え字が変化するような不思議な挙動をするので単純なforでは削除できません。Iteratorのremove()でも怪しい(出来る?)ので、常に0番目の結合セルアイテムを結合セル数が0になるまで回すような処理をします。
  4. リストに保存しておいたシフト後結合セル達をSheetaddMergedRegion()で入れなおしてあげます。

他にも効率のいい禍機方が有るかもしれませんが、とりあえずこれで列のシフト(削除)が可能です。

2. 行シフト

行のシフトはSheet.shiftRows()でおk、シンプルで楽だ―。

と、思いきや、結合セルの情報がやはり列シフト同様そのまま? のようです。出力したExcelファイルは意図した通りになっていたりするので要検証ですが、先述のような作法でずらしていく処理を入れた方が無難かもしれません。

また、シートの最下段に当たる部分を上にシフトした場合、OOXML(Excel2007/2010/2013)形式、というよりもXSSFWorkBookの場合はシートの最終行も合わせて上にシフトするような動きで、おそらく「普通」の動きなのですが、HSSF(Excel2003以前)の場合は最下段部分を上にシフトしても、跡地に行が残ってしまい、Sheet.getLastRowNum()などがそこを見に行ってしまう事が有ります。跡地にあたるRowがnullでないようであれば削除するような後始末処理を入れておいた方が無難かもしれません。

前述のように便利なのは確かなのですが、「あれ、無いんだ」がちょくちょく有ったり、なぜそうなる!という動きが有ったりと素直なライブラリではないので癖を見つけて対処していかないといけないですね。XSSF利用時にシートを削除するとファイルが壊れてしまう不具合もやっと3.10beta版で修正が入った?ようなので、3.10の正式版リリースが待たれます。

※実はもっと使いやすいメソッドが別のクラスに会ったりするんでしょうか? あまり全体を探し回ったりはしていないのでもっといい方法が有るかもしれません。