Word で好きなテンプレートの docx ファイルを自動生成する

やりたいこと

テキストファイルから読み込んだ文字列を、特定のフォーマットでdocxファイルに出力したいです。

具体的には、こんな感じで小説風の縦書き文章にしたいのです。

f:id:beikome:20191228170957p:plain

この記事では、docx ファイルを操作するライブラリ python-docx を使った方法を紹介していきます。

docx ファイルの中身をある程度理解できれば縦書きにする以外の応用もできると思うので、何らかの事情で docx ファイルと格闘している皆様はよかったら読んでみてください。

やり方

docx のテンプレートを用意する

wordでドキュメントを新規作成して、縦書きのテンプレートを作ってしまいます。ここでは本文は打ち込みません。

レイアウトタブ>テキストの方向>縦書き と選択すれば縦書きになります。

用紙サイズなどを指定したい場合もここで設定してしまいましょう。

f:id:beikome:20191228231711p:plain

template.docx など適当な名前で保存します。

テンプレートを読み込み、内容を追加する

python-docx を使います。

$ pip install python-docx

以下はテンプレートを読み込んで、本文を追加するコードの例です。

#!/usr/local/bin/python
# -*- coding:utf-8 -*-


from docx import Document


def main():
    # テンプレートを読み込む
    document = Document('template.docx')

    # 最初の段落を取得
    p = document.paragraphs[0]

    # 文章を追加
    p.add_run('吾輩は猫である。')
    p.add_run('名前はまだ無い。')

    # 新しい段落をつくる
    p = document.add_paragraph()

    # 新しい段落に文章を追加
    p.add_run('こんな感じで小説風な文章を書きたい。')

    # 保存
    document.save('out.docx')


if __name__ == '__main__':
    main()

これを実行すると冒頭のファイルができます。

何をしているのか

docx ファイルの中身

docx ファイルは zip アーカイブになっています。unzipで展開すると確認することができます。

試しに、縦書きのファイルの中身をみてみます。

$ unzip sample.docx -d sample_docx
$ tree sample_docx 
sample_docx/
├── [Content_Types].xml
├── _rels
├── customXml
│   ├── _rels
│   │   └── item1.xml.rels
│   ├── item1.xml
│   └── itemProps1.xml
├── docProps
│   ├── app.xml
│   └── core.xml
└── word
    ├── _rels
    │   └── document.xml.rels
    ├── document.xml
    ├── fontTable.xml
    ├── settings.xml
    ├── styles.xml
    ├── theme
    │   └── theme1.xml
    └── webSettings.xml

本文ファイルは sample_docx/document/words.xml です。

中身は以下のようになっています。

<?xml version="2.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid wp14">
  <w:body>
    <w:p w:rsidR="0033710F" w:rsidRPr="0011658F" w:rsidRDefault="0033710F">
      <w:pPr>
        <w:rPr>
          <w:rFonts w:ascii="Hiragino Mincho Pro W3" w:eastAsia="Hiragino Mincho Pro W3" w:hAnsi="Hiragino Mincho Pro W3"/>
          <w:sz w:val="36"/>
          <w:szCs w:val="36"/>
        </w:rPr>
      </w:pPr>
      <w:r w:rsidRPr="0011658F">
        <w:rPr>
          <w:rFonts w:ascii="Hiragino Mincho Pro W3" w:eastAsia="Hiragino Mincho Pro W3" w:hAnsi="Hiragino Mincho Pro W3" w:hint="eastAsia"/>
          <w:sz w:val="36"/>
          <w:szCs w:val="36"/>
        </w:rPr>
        <w:t>吾輩は猫である。</w:t>
      </w:r>
      <w:r w:rsidR="00BD0058">
        <w:rPr>
          <w:rFonts w:ascii="Hiragino Mincho Pro W3" w:eastAsia="Hiragino Mincho Pro W3" w:hAnsi="Hiragino Mincho Pro W3" w:hint="eastAsia"/>
          <w:sz w:val="36"/>
          <w:szCs w:val="36"/>
        </w:rPr>
        <w:t>名前はまだ無い。</w:t>
      </w:r>
      <w:bookmarkStart w:id="0" w:name="_GoBack"/>
      <w:bookmarkEnd w:id="0"/>
    </w:p>
    <w:p w:rsidR="004919A3" w:rsidRPr="0011658F" w:rsidRDefault="004919A3">
      <w:pPr>
        <w:rPr>
          <w:rFonts w:ascii="Hiragino Mincho Pro W3" w:eastAsia="Hiragino Mincho Pro W3" w:hAnsi="Hiragino Mincho Pro W3"/>
          <w:sz w:val="36"/>
          <w:szCs w:val="36"/>
        </w:rPr>
      </w:pPr>
      <w:r w:rsidRPr="0011658F">
        <w:rPr>
          <w:rFonts w:ascii="Hiragino Mincho Pro W3" w:eastAsia="Hiragino Mincho Pro W3" w:hAnsi="Hiragino Mincho Pro W3" w:hint="eastAsia"/>
          <w:sz w:val="36"/>
          <w:szCs w:val="36"/>
        </w:rPr>
        <w:t>こんな感じで小説風な文章を書きたい。</w:t>
      </w:r>
    </w:p>
    <w:sectPr w:rsidR="004919A3" w:rsidRPr="0011658F" w:rsidSect="004919A3">
      <w:pgSz w:w="16840" w:h="11900" w:orient="landscape"/>
      <w:pgMar w:top="1701" w:right="1701" w:bottom="1701" w:left="1985" w:header="851" w:footer="992" w:gutter="0"/>
      <w:cols w:space="425"/>
      <w:textDirection w:val="tbRl"/>
      <w:docGrid w:type="lines" w:linePitch="400"/>
    </w:sectPr>
  </w:body>
</w:document>

色々書いてありますが、おおまかには以下の構造で書かれています。

f:id:beikome:20191229184203p:plain:w600

本文(body)

ユーザが編集できる本文領域のデータです。この下に段落が配置されます。

段落(paragraph)

文章の塊はparagraph要素(p)で表されます。word 内で改行すると次の段落になります。 段落ごとのプロパティはpPr要素でまとめられています。pPrにはフォントの設定などが書かれています。

ラン(run)

段落より下位の要素です。今回の例では、「吾輩は猫である。」と「名前はまだ無い。」は同じ段落に属していますが別のランになっています。また、太字にしたり色をつけた文字などはその部分だけ別のランになります。 ランのプロパティはrPr要素にまとめられていますrPrにフォントの指定などがない場合は上位の段落のpPrに書かれている内容が引き継がれます。

テキスト(text)

テキスト要素はランの下に配置されます。word で開いたときに実際に表示される文字列がここにはいります。

セクションプロパティ(sectPr)

ドキュメント全体の設定が書かれています。書字方向(縦書きか横書きかの設定)はここで指定されます。セクションプロパティの位置はbodyの直下なこともあれば段落の下にあることもあるのですが、詳しくは以下を参照してください。

python-docxでは何ができるのか

python-docx.readthedocs.io

詳細は公式のドキュメントをみてもらいたいのですが、現在 python-docx の最新バージョン0.8.10では、

  • 段落の追加
  • ランの追加
  • 図の追加
  • 表の追加

がサポートされています。また、太字にする、イタリック体にする、などのプロパティは簡単に変更できるようになっています。

一方で、細かいプロパティはサポートされていないこともあります。 例えば、縦書きをするためには sectPr 要素の textDirection 要素を書き換える必要があるのですが、これは今はサポートされていません。また、ランプロパティの中には漢字にルビを振るときの設定等もあるのですが、そのあたりも現時点ではサポートされていません。

まとめ

簡単なのは、一旦手動で所望のフォーマットの docx ファイルを作成してみて、それを xml の状態で眺めてみることです。

python-docx のドキュメントと見比べて、サポートされている機能であれば python-docx で書けばよいです。縦書き設定のようなサポートされていない機能は、今回のようにテンプレートをつくってそれを読み込むことである程度対応できます。

テンプレートで対応しづらい機能(ルビをふるとか)は xml をゴリゴリ書いてどうにかする方法もあるのですが、、それはまたの機会に。

参考リンク