【Python】【Word】【python-docx】python-docxを使ってPythonでワード文章のひな形を作成してみる


　diffの情報をどう可視化したものかなーというのがキッカケでした。色付きの差分情報を最終的にはpdfでそれなりの形にしたいなーと。
 
 　でいろいろ調べてみますと[python-docx](https://python-docx.readthedocs.io)が使えるのかなーと思って評価し、一応の結果が出ましたので備忘の意味も込めてここにまとめておきます。MarkDownとかも考えたのですが、docxを生成する方法であれば細かいレイアウトとか見栄えはWordのGUIで修正できるしなーというダメエンジニアの気質がこの方法を選ばせてしまったのです（苦笑）。
  　WordができればPDFはもう出来たも同然ですし。

　Pythonで諸々解析した結果を元に、Wordのひな形を作りたいという方にとって何らかの参考になれば幸いです。
 
# 追記

* コメント欄にて[shiracamus様](http://qiita.com/shiracamus)が改善してくださってます必見です。
* 今回はparagraphの複数対応は考慮していません。ちなみに対応すると、データを読み出してこっちは1章、こっちは2章とかも可能になると思われます。

 
 
# どんなdocsを作るか

今回は以下の機能を確認しました。
 
 * 表題を作成
 * 見出し１を作成（２以降もこの応用でいけるはず）
 * WindowsのShif-JISで書かれたテキストファイルの埋め込み
 * Pythonで直接テキストを埋め込み
 * 図の挿入

# 動作環境など

　本来はWindowsのPythonでやれば諸々スムーズなのでしょうが、今回は諸事情あって以下の環境で動作を確認しました。いや、diffもそうなのですがcygwin上でゴニョゴニョしたデータを処理させたくてまぁこの環境となりました。
 
 * Cygwin(32bit)／Windows10 上
 * python2.7
 * python_docx-0.8.6-py2.7

  Cygwinでやる場合は、pythonのファイルは全て文字コードをUTF-8、改行は\nのみのフォーマットで確認して下さい。

　また、**MS-DOSプロンプトでやる場合 **は、改行は\r\nで、文字コードはSJISにします。また本分章で

```py
# -*- coding: utf-8 -*-
```

　とある記述を

```py
# -*- coding: shift-jis -*-
```

　とすればイケるはずです。

# python-docxのインストール

Pythonは既に入っている前提です。

インストールについては[ここ](https://python-docx.readthedocs.io/en/latest/user/install.html#install)に書いてあります。条件は以下。
 
* Python 2.6, 2.7, 3.3, or 3.4
* lxml >= 2.3.2

通常は pip install python-docx で 行けるハズです多分。私の環境でもMS-DOSプロンプト版はスムーズに入りました。

### Cygwin上での注意点

まず、上の条件でもありますが以下のライブラリをCygwinにインストールしている必要があります。これらが入ってないと、ヘッダがないみたいなエラーが出ますので、入れておきます。**標準では入っていないかも。**
 
 * libxml2
 * libxslt
 
 更に 私のようにWindowsとCygwin両方にpythonが入っている場合は注意が必要でpip installするとパスの設定とかにもよりますがWindowsの方に入ってしまいます。なので、以下のような方法でインストールしました。
 
 Cygwinのセットアップは[こちら](http://qiita.com/GDaigo/items/a80003684fc6ab7505fd#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97)も参考になるかも
 

```bash
easy_install-2.7 python-docx
```
# python-docxの使い方

以下に本家の説明があります。

https://python-docx.readthedocs.io/en/latest/index.html

チュートリアルは分かりやすくて良いです。が、やりたい事をさがすのはなかなか大変です。私の場合は文字の修飾系で結構悩みましたが、それ関係は以下にまとまっていました。

http://python-docx.readthedocs.io/en/latest/user/text.html

ライブラリの仕様をくどくど説明するのは面倒なので、以下にコードで表現してみました。

```py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# SimpleDocService
#  python-doxに関する簡単なサービスを提供します。
#  まぁ docxライブラリを理解するためのコードですね。
#

from docx import Document
from docx.shared import RGBColor
from docx.shared import Inches
from docx.shared import Pt

class SimpleDocxService:

    def __init__(self):
        self.document = Document()
        self.latest_run = None

    def set_normal_font(self, name, size):
        # フォントの設定
        font = self.document.styles['Normal'].font
        font.name = name
        font.size = Pt(size)

    def add_head(self, text, lv):
        # 見出しの設定
        self.document.add_heading(text, level=lv)

    def open_text(self):
        # テキスト追加開始
        self.paragraph = self.document.add_paragraph()

    def close_text(self):
        # テキスト追加終了
        return #現状では特に処理はなし

    def get_unicode_text(self, text, src_code):
        # python-docxで扱えるように unicodeに変換
        return unicode(text, src_code)

    def adjust_return_code(self, text):
        # テキストファイルのデータをそのままaddすると改行が
        # 面倒なことになるので、それを削除
        text = text.replace("\n", "")
        text = text.replace("\r", "")
        return text

    def add_text(self, text):
        # テキスト追加
        self.latest_run = self.paragraph.add_run(text)

    def add_text_italic(self, text):
        # テキスト追加（イタリックに）
        self.paragraph.add_run(text).italic = True

    def add_text_bold(self, text):
        # テキスト追加（強調）
        self.paragraph.add_run(text).bold = True

    def add_text_color(self, text, r, g, b):
        # 文字に色をつける
        self.paragraph.add_run(text).font.color.rgb = RGBColor(r, g, b)

    def add_picture(self, filename, inch):
        # 図を挿入する
        self.document.add_picture(filename, width=Inches(inch))

    def save(self, name):
        # docxファイルとして出力。
        self.document.save(name)
```

SimpleDocxServiceは今回評価した諸々の機能のAPIを集めたクラスです。以下の機能を提供しています。

| API | 動作 |
|:-----------|:------------|
|set_normal_font(name, size)|標準テキストのフォントを設定。nameは名前でsizeにサイズ|
|add_head(text, lv)|見出しの作成。textは見出し名。lvはレベル(0=表題、1=見出し1、...）|
|open_text()|テキストエリアのオープン（※）|
|close_text()|テキストエリアのクローズ（※）|
|get_unicode_text(text, src_code)|src_codeで指定した文字コードからunicode文字列を生成して戻す|
|adjust_return_code(text)|改行関係を消去したテキストを生成して戻す|
|add_text(text)|textデータをワードドキュメントに書き込む|
|add_text_italic(text)|textデータをワードドキュメントに書き込む、書体をイタリックに|
|add_text_bold(text)|textデータをワードドキュメントに書き込む、書体をボールドに|
|add_text_color(text, r, g, b)|textデータをワードドキュメントに書き込む。色指定をrgbで。例：r=255, g=0, b=0 で赤|
|add_picture(filename, inch)|filenameで指定した画像データを差し込む。inchは横のインチサイズ|
|save(name)|nameで指定したファイル名でワードのファイルとして保存|

いくつか補足を。

* ※テキストエリアについて

これはpython-docxの挙動とも絡みますのでコードで補足します。実際にテキストを書き込むコードは以下の通りです。これは[本家](https://python-docx.readthedocs.io/en/latest/index.html)から引用したコードです。

```py
p = document.add_paragraph('A plain paragraph having some ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True
```

どのように表示されるかも[本家](https://python-docx.readthedocs.io/en/latest/index.html)にあります。このように、paragraphを取得して、そこにテキストを追加していく事が出来ます。テキストの修飾も、どうやらこのadd_runの時に行えるようなのです。恐らくですがこれはdocxファイルの構造とも絡むのでしょう。

なので、paragraphを新規に取るという動作をopen_textで行います。python-docxでは必要ないのですが、その一連の記載が終わった事をclose_textで行うという思想にしてあります。そのため、
SimpleDoxServiceクラスを利用した文章は以下のように記載します。

```py
docx  = SimpleDoxService()
docx.open_text()
docx.add_text("This is a my best book.\n")
docx.add_text("Do you know this?")
docx.close_text()
```

なんでこういう事をしているかというと図との関係を考慮しています。図を入れる場合は、上のコードにもありますようにadd_pictureを利用します。この時に以下のように書いたとします（SimpleDocxServiceクラスを使わないでpython-docxを直接利用したコードです）。

```py
p = document.add_paragraph('A plain paragraph having some ')
p.add_run("text1\n")
document.add_picture("sample.png", width=Inches(1.25))
p.add_run("text2\n")
```

この場合、まぁある意味当然なのですが、

```text
text1
<<sample.pngの図>>
text2
```

ではなく

```text
text1
text2
<<sample.pngの図>>
```

となるのです。なので、アプリコードでそこらを明示化したくてopenとcloseの概念を入れてみました。これは後ほどサンプルアプリのコードでも示すので、そちらも参考にして戴ければと思います。

* get_unicode_text関数

Pythonの場合、文字コードは割と面倒です。ライブラリがどの文字コードを扱っているかに留意が必要です。python-docxの場合はunicodeで処理しているようですので、日本語の場合はunicodeに変換が必要になります。ここらの変換の仕方はいろいろあるのですが、unicodeにするにはこのget_unicode_text関数のやり方にする必要があるみたいです（WordなのでSJISというのでもないみたいですね…）。

* adjust_return_code関数

これはカット＆トライで入れたコードです申し訳ありません。改行込みのテキストをそのまま使うと不要な改行がどうも入ってしまうみたいです。それを防ぐやり方として私がやった中ではこのadjust_return_code関数のやり方でした。

次節では、このSimpleDocxServiceクラスのコードを使って実際にワードファイルを作成してみます。

# 実際に作ってみる。

今回はサンプルとして以下のような構成のWORDファイルを作成します。

1. 表題
2. 画を挿入
2. 見出し１（１個目）
3. テキストファイルの文字列
4. 別の画を挿入
5. Pythonで修飾した文字列
6. 見出し１（２個目）
7. Pythonで指定した文字列

### 素材

素材としてサンプルで使ったものを以下に。またプロ生ちゃんですスミマセン。。

まずは表題の下の画は**report_top.png**というファイルでこんな感じ。
![report_top.png](https://qiita-image-store.s3.amazonaws.com/0/129334/ac87b54d-6789-90dc-3796-02d997f1c938.png)

次にテキストファイルは**sample.txt**というファイルでこんな感じ。ま、[私のブログ](http://gdaigo.blog.jp/archives/5165488.html)からの抜粋ですが・・

```txt
マネージャというポジションは複数の人を動かして成果を上げるのが基本的な役割と思っています。
そのため、やはり人の感情とか心的な問題を無視しては厳しいです。これは相手に迎合するのとは少し違うと思っています。そのような問題をある程度考慮した上であえて無視するという事もやってましたので。
```

別の画ってのは**sample_pic.png**というファイルでこんな感じ。
![sample_pic.png](https://qiita-image-store.s3.amazonaws.com/0/129334/e08ca4a5-d12f-88fb-7b21-05a14590cf8c.png)

以下に掲示するサンプルはこれを使って作成しました。もちろん画像やテキストはこれでなくても構いません。ただテキストは日本語はSJISで、改行はWindoｗsの\r\nである事を想定していますので、そこは注意願います。

ちなみにプロ生ちゃんの素材は以下から取得し、サイズや文字入れの加工をしています。
http://pronama.azurewebsites.net/pronama/

### サンプルコード

以下がSimpleDocxServiceクラスを利用してワードを生成するサンプルコードです。

```py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from docx_simple_service import SimpleDocxService

if __name__ == "__main__":

    docx = SimpleDocxService()

    #フォント設定
    docx.set_normal_font("Courier New", 9)

    # タイトル表示
    docx.add_head(u"メインタイトル", 0)

    # 挿絵挿入
    docx.add_picture("report_top.png", 3.0)

    # 文節タイトル表示
    docx.add_head(u"一個目の話題", 1)

    # shift-jisのテキストファイルをdocxの文章に入れます
    f = open("sample.txt")
    text = f.read()
    f.close()
    docx.open_text()
    docx.add_text("\n")
    text = docx.get_unicode_text(text, 'shift-jis')
    text = docx.adjust_return_code(text)
    docx.add_text(text)
    docx.close_text()

    # 挿絵挿入
    docx.add_picture("sample_pic.png", 5.0)

    # コードでテキストを生成、docxに入れ込みます。
    # 修飾の例もここで。
    docx.open_text()
    docx.add_text("\nThis is a my best book.")
    docx.add_text("\nThis is ")
    docx.add_text_bold("a my best")
    docx.add_text(" book.")
    docx.add_text("\nThis is ")
    docx.add_text_italic("a my best")
    docx.add_text(" book.")
    docx.add_text_color("\nThis is a my best book.", 0xff, 0x00, 0x00)
    docx.close_text()

    # 次の文節
    docx.add_head(u"二個目の話題", 1)

    # コードでテキストを生成、docxに入れ込みます。
    docx.open_text()
    docx.add_text(u"\nはい、おしまい。")
    docx.close_text()

    # セーブです。
    docx.save("test.docx")

    print "complete."
```

* SimpleDocxServiceクラスはこのプログラムと同じフォルダにあるdocx_simple_service.pyに実装されている想定です。
* report_top.pngとsample_pic.pngという画像ファイルがこのプログラムと同じフォルダにある事を想定しています。
* sample.txtというSJISで改行が\r\nのテキストファイルがこのプログラムと同じフォルダにある事を想定しています。
* 本プログラムを実行すると、このプログラムと同じフォルダにtest.docxという名前で保存されます。

### 実行結果

こんな感じのdocxが出来上がります。
![python_docx_sample.jpg](https://qiita-image-store.s3.amazonaws.com/0/129334/5e479a64-ca05-391f-ca36-f9f5c1cc51bc.jpeg)

上に書いた構成になっているかと思います。

python-docxの場合、書き方が分かってしまえば、コード自体はそう難しくはありません。上のサンプルコードと SimpleDocxServiceクラスのコードをつきあわせてみれば理解はできるかと思います。なので、この範囲であればここで掲示したコードを元に変えていろいろ出来るかと思います。
*問題は書き方を見つけるまでですね・・*

# ライセンス

以下使わせて戴きました。素晴らしいソフトウェアを提供して下さり、ありがとうございます。

* （一応書きます…）上記のコードはパブリックドメインとします。著作権を主張するほどのコードではないっつーことで。ただ当然ですが使用した際の損害は誰も請け負ってくれません。そこだけ注意で。
* Python自体は[PSF (Python Software Foundation)ライセンス](https://docs.python.org/3/license.html)です。
* ↑の情報は[WikipediaのPython](https://ja.wikipedia.org/wiki/Python#.E3.83.A9.E3.82.A4.E3.82.BB.E3.83.B3.E3.82.B9)がソースです。
* python-docxのライセンスは以下に記載があります。MITなようですね。
 https://github.com/python-openxml/python-docx/blob/master/LICENSE
* プロ生ちゃんの画像は[「プロ生ちゃん利用ガイドライン」](http://pronama.azurewebsites.net/pronama/guideline/)に従った利用が必要ですので、ご注意ください。
 

以上です。