rch850 の上澄み

技術的な話題とか、雑談とか。タイトルを上澄みに変えました @ 2020/09/02

コワーキングスペースの申請のため Groovy x POI で Word ファイルを操作する

ふくもく会では、福井産業情報センターのコワーキングスペースにお世話になることが多いのだけど、ここを使うには利用申請書を提出する必要がある。この利用申請書が .doc ファイルで、これをなんとかしてサクッと記入できないか考えてみた。

最初は勉強のために Rust で書こうとしたけど、Rust には docx を編集するライブラリ、その名も docx しかなさそうだった。じゃぁ Python でいいかと調べてみても、こちらも docx を編集するライブラリしかなさそう。例えば python-docx

諦めて手書きするかなーと @macoshita にぼやいてたら「この分野は POI が最強っすね」と言われて Apache POI のことを思い出した。Java かー。もうちょっと軽い気持ちで書きたいんだよなー。ということで Groovy から使ってみることにした。

Groovy 書くのが5億年ぶりなので、イチから調べてみたところ、Grape という仕組みがあって、依存をすっと書けるらしい。サンプルとして次のコードが書かれていた。

@Grab('org.springframework:spring-orm:3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate

build.gradle とかが無くても、こんな感じで groovy ファイルに @Grab で書いとくだけで、勝手に依存する jar を取ってきてくれるんだって。夢か?Denoか?

そんなわけで、これを活用して POI でコワーキングスペースの利用申請書の doc ファイルを開いて、適当にテキスト置換して、書き出すコードを書いてみた。

// TextRangeReplatement test as an HWPFDocument example
// http://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java?revision=1872041&view=markup

@Grab('org.apache.poi:poi:4.1.2')
@Grab('org.apache.poi:poi-scratchpad:4.1.2')
import org.apache.poi.hwpf.HWPFDocument
import org.apache.poi.hwpf.usermodel.Range
import org.apache.poi.hwpf.usermodel.Section

FILENAME = 'coworking_format1.doc'

try (HWPFDocument daDoc = openSampleFile(FILENAME)) {
    Range range = daDoc.getRange()

    def 申請日 = '令和2年 11月 5日'
    def 住所 = '福井県福井市'
    def 利用日 = '令和2年 11月 23日(月)'
    def 氏名 = '福井太郎'

    // dump(range)
    range.replaceText('平成   年   月   日(  )', 利用日)
    range.replaceText('年   月   日', 申請日)
    range.replaceText('住所   ', '住所 ' + 住所)
    range.replaceText('氏名   ', '氏名 ' + 氏名)

    dump(range)
    daDoc.write(new File('coworking.doc'))
}

void dump(Range range) {
    println("numParagraphs: " + range.numParagraphs())
    for (int j = 0; j < range.numParagraphs(); j++) {
        println(j + '[' + range.getParagraph(j).text() + ']')
    }
}

HWPFDocument openSampleFile(String filename) {
    try {
        InputStream is = new FileInputStream(filename)
        try {
            return new HWPFDocument(is)
        } catch (Throwable e) {
            is.close()
            throw e
        }
    } catch (IOException e) {
        throw new RuntimeException(e)
    }
}

これを groovy main.groovy とかで実行するだけ。

f:id:rch850:20201105010826p:plain

おー、できたできた。便利すぎるな。この方向で進めてみようと思う。

細かいメモ:

  • POI の数ある機能のなかで Word ファイル (.doc) を操作する機能は HWPF という部分。
  • Word ファイルを読むと HWPFDocument という形になる。
  • コメントに書いた通り TextRangeReplacement というテストコードが HWPFDocument でテキスト置換するときのいい例になっている。実際、このコードからすべてを学んだと言ってもいいぐらい。
  • 「住所」を「住所 福井県」に Range#replaceText すると処理が終わらない。たぶん無限ループにハマってる。仕方なく「住所   」を「住所 福井県」に置換して無限ループを回避した。他にも Section を特定して Range#insertAfter を駆使するなど、細かい工夫が要りそう。