2011年11月25日金曜日

Groovy + POI

Groovy では、クロージャを使って若干の下準備(下例では TableBuilder)をしておくと、以下のようなコードが書ける。

static void main(args) {
   new TableBuilder().with {
     sheet(   "test"
         )(   style(header)
         )(   "No"   )(   "名前"      )( nextRow
         )(   style(normal)
         )(   1      )(   "琵琶湖"    )( nextRow
         )(   2      )(   "多来加湖"  )( nextRow
         )(   3      )(   "富内湖"    )( endTable   )
   }.write(new FileOutputStream("sample.xls"));
}

TableBuilder の中では、Apache POI を使っていて、実行すると 下のような Excel ファイルが sample.xls に書き出される。

カッコの開き方が気に入らないが、しょうがないらしい。下の A、B は等価だけど、C は別ものに解釈される。上例を C のようなカッコのに直すと、動作しない。

ABC
   ("A1")("B1")(
    "A2")("B2")
    ("A1")("B1"
   )("A2")("B2")
   ("A1")("B1")
   ("A2")("B2")

TableBuilder は下のコードのようになる。

class TableBuilder {
   def book
   TableBuilder() { book = new HSSFWorkbook() }
   def nextRow = { cell, style ->
      cursor.curry(cell.offset(1, -cell.columnIndex), style)
   }
   def endTable = { cell, style -> book }
   def cursor = { cell, style, arg ->
      if (arg instanceof Closure) { arg(cell, style) }
      else { 
         cell.setCellValue(arg)
         if (null != style) cell.cellStyle = style 
         cursor.curry(cell.next(), style) 
      }
   }
   def start(sheet){ cursor.curry(sheet.cellAt(0, 0), null) }
   def sheet(name) { start(book.createSheet(name)) }
   def style(styleClosure) {
      { cell, style-> cursor.curry(cell, styleClosure(book))}}
}

また、POI の API が Groovy コードの中で馴染むように、Mixin でメソッドを追加した。

class CellMix {
   def next() { offset(0, 1) }
   def offset(r, c) {
      sheet.cellAt(rowIndex + r, columnIndex + c) }
}
class RowMix {
   def cellAt(c) { getCell(c) ?: createCell(c) }
}
class SheetMix {
   def rowAt(r) { getRow(r) ?: createRow(r) }
   def cellAt(r, c) { rowAt(r).cellAt(c); }
}
class CellStyleMix {
   def borderMethodName = { s-> "setBorder" + s.capitalize() }
   def setBorder = { arg, edge -> 
      owner."${borderMethodName(edge)}"(arg."$edge" ?:BORDER_NONE); 
      setBorder.curry(arg) }
   def setBorder(Map arg) {
   setBorder.curry(arg)("top")("left")("bottom")("right") }
}

カッコを連鎖させるやり方にしたのは、単にクロージャとカリー化を試したかっただけで、実は、普通のクラスと leftShift() のオーバライドだけで、 下のような C++風の書き方もできて、こっちの方がむしろスッキリしたコードになる。

sheet("test") << 
   "A1" << "B1" << endRow <<
   "A2" << "B2" << endTable

0 件のコメント:

コメントを投稿