サンプル

ByteArrayOutputStream baos = null;
try {
    // 郵便局から提供されるテンプレートファイルを読み込む
    XWPFDocument templateDoc =
            new XWPFDocument(new FileInputStream("C:\\japan_post\\template01.docx"));

    // 雛形の内容をすべて削除する(改行がある)
    int elementsSize = templateDoc.getBodyElements().size();
    for (int i = 0; i < elementsSize; i++) {
        templateDoc.removeBodyElement(0);
    }

    // jasperReport で生成した DOCX ファイルを読み込む
    XWPFDocument doc = new XWPFDocument(new ByteArrayInputStream(os.toByteArray()));

    // 上記 DOCX ファイルは、表でレイアウトを調整しているので、表の書式を整えつつ
    // 雛形ファイルに表をコピーする。
    for (XWPFTable t : doc.getTables()) {
        // 表自体の VALIGN を中心 (CENTER) にする。
        CTTblPr tblPr = t.getCTTbl().getTblPr();
        CTJc jc = null;
        if (tblPr.isSetJc()) {
            jc = tblPr.getJc();
        } else {
            jc = tblPr.addNewJc();
        }
        jc.setVal(STJc.CENTER);

        // 各セルの行間が標準だと大きすぎるので、設定しなおす
        for (XWPFTableRow row : t.getRows()) {
            for (XWPFTableCell cell : row.getTableCells()) {
                for (XWPFParagraph p : cell.getParagraphs()) {
                    p.setSpacingAfter(0);
                    p.setSpacingAfterLines(0);
                    p.setSpacingBefore(0);
                    p.setSpacingBeforeLines(0);
                    p.setSpacingLineRule(LineSpacingRule.EXACT);

                    // XWPFParagraph に CTSpacing#setLine
                    // を呼ぶメソッドが無いので、以下のような実装になる。
                    CTP ctp = p.getCTP();
                    CTPPr ppr = null;
                    if (ctp.isSetPPr()) {
                        ppr = ctp.getPPr();
                    } else {
                        ppr = ctp.addNewPPr();
                    }
                    CTSpacing spacing = null;
                    if (ppr.isSetSpacing()) {
                        spacing = ppr.getSpacing();
                    } else {
                        spacing = ppr.addNewSpacing();
                    }
                    spacing.setLine(BigInteger.valueOf(280));
                }
            }
        }
        // 雛形ファイルに表を1つ作成する。
        templateDoc.createTable();
        // 書式を設定した表を雛形ファイルに作成した表にセットする。
        templateDoc.setTable(templateDoc.getTables().size() - 1, t);
    }

    baos = new ByteArrayOutputStream();
    templateDoc.write(baos);
} catch (IOException e) {
    throw new HogeException("帳票生成のIOに失敗", e);
} finally {
    IOUtils.closeQuietly(baos);
}

return baos.toByteArray();

工夫したところ

  • JasperReport で生成したものは表としてレイアウトされているので、その表自体を左寄せではなく中心に配置したこと
    • →Word の表は、デフォルトの左寄せだと少し余白に線がはみ出すようなので
    • →直感的な API は無かった
  • 上記の表の各セルは、行間が広すぎるので、行間の設定を行ったこと
    • →行間を狭くするだけなら XWPFParagraph#setSpacing* メソッドでできる
    • →行の間隔を最小にしても、まだ広すぎる場合、行の間隔を固定値にして指定したいが、XWPFParagraph に CTSpacing#setLine を呼ぶメソッドが無いので、private メソッドを真似した実装になる
    • →行の間隔に指定したい pt の値に 20 をかけた値を設定する(経験則)
  • 書式を設定した表を雛形ファイルにコピーしたこと
    • 表をコピーするのに、表を生成してインデックスを指定してセットする
  • 雛形のファイルにある改行を消したこと
    • →改行が残るとレイアウトがずれる

注意点

  • JasperReport で設定していた余白をなくすこと
  • JasperReport で生成する部分のサイズを、雛形の余白以外のスペースより若干小さくすること
  • poi で docx を扱う情報が少ないこと
  • poi で docx を細かく扱う場合、org.apache.poi.ooml-schemas の 1.3 (1.1) が必要なこと

こまったこと

郵便局の e 内容証明サービスに変更があって、ホームページからダウンロードした雛形の docx ファイルしか対応しないということになったので、JasperReport を使って生成していた docx ファイルではサービスに対応できなくなってしまった。