yoraba build

備忘録を兼ねた技術ブログ

python-docxでWord文書を作成する

実用的なケース

Wordをプログラムで操作する上で実用的なケースを考えました。やはり、テンプレートが指定されている文書をcsvなどからデータを取り込んで自動編集する、という局面で役に立つのではないでしょうか。今回はネットで拾ってきたサンプルの請求書ファイルを編集するというところまでやっていこうと思います。

financial.mook.to

テンプレートファイルの中身を解析する

from docx import Document
import os


print(f'{os.path.abspath(os.curdir)=}')
document = Document('iv01.docx')
print(f'{len(document.paragraphs)=}')
for paragraph in document.paragraphs:
    print(f'{paragraph.text=}')
print(f'{len(document.tables)=}')
for table in document.tables:
    print(f'{len(table.rows)=}')
    for row in table.rows:
        print(f'{len(row.cells)=}')
        for cell in row.cells:
            paragraphs = cell.paragraphs
            for paragraph in paragraphs:
                print(f'table {paragraph.text=}')

以上のコードを実行すると、全てのバラグラフとテーブル値を出力できます。 paragraphsやtablesといったオブジェクトはコレクションなので、インデックスで要素にアクセス出来ます。 Wordのパラグラフやテーブル値がどのインデックスに位置するかを、上記コードで出力した文字列から解析します。

CSV

複数の請求先のデータを取り込みます。 請求先ごとに請求書を作る必要があります。

請求先,品名,数量,単価
会社A,商品A,1,1000
会社A,商品B,2,2000
会社A,商品C,3,3000
会社B,商品D,4,4000
会社B,商品E,5,5000
会社B,商品F,6,6000
会社B,商品G,7,7000

Word編集

 請求先名称や日付、商品のテーブル行列がどのインデックスに位置するかを変数に保持します。 csvからデータを取り込んで、請求先名称の集合を取得します。それをメインのループで回して、請求先毎の請求書を作ります。 paragraphsやtablesに先程保持したインデックスを指定して、text値を編集します。

from docx import Document
import pandas as pd
from datetime import date
from japanera import Japanera, EraDate

jaera = Japanera()
c_era = jaera.era(date.today())


template_name = 'iv01.docx'
csv_path = './iv01.csv'

pr_date = 0
pr_billing_address = 1
trc_billing_amount = (2, 3)
trc_subtotal = (15, 7)
trc_tax = (16, 7)
trc_total = (17, 7)
tax_rate = 0.08
items_row_start_idx = 6
c_item_name = 1
c_amount = 5
c_unit_price = 6
c_total = 7


df = pd.read_csv(csv_path)
print(df)
billing_address_set=set(df["請求先"])
print(f'{billing_address_set=}')

for billing_address in billing_address_set:
    doc_name = f'請求書_{billing_address}.docx'
    document = Document(template_name)
    paragraphs = document.paragraphs
    table = document.tables[0]
    paragraphs[pr_date].text = c_era.strftime(date.today(), '%-E%-O年%m月%d日')
    paragraphs[pr_billing_address].text = f'{billing_address} 御中'
    billing_data = df[df['請求先'] == billing_address]
    print(billing_data)
    current_row_idx = items_row_start_idx
    subtotal = 0
    for data in billing_data.values:
        print(data)
        table.rows[current_row_idx].cells[c_item_name].paragraphs[0].text = data[1]
        table.rows[current_row_idx].cells[c_amount].paragraphs[0].text = str(data[2])
        table.rows[current_row_idx].cells[c_unit_price].paragraphs[0].text = str(data[3])
        row_total = data[2] * data[3]
        table.rows[current_row_idx].cells[c_total].paragraphs[0].text = str(row_total)
        subtotal += row_total
        current_row_idx +=1

    table.rows[trc_subtotal[0]].cells[trc_subtotal[1]].paragraphs[0].text = str(subtotal)
    tax = subtotal * tax_rate
    table.rows[trc_tax[0]].cells[trc_tax[1]].paragraphs[0].text = str(tax)
    table.rows[trc_total[0]].cells[trc_total[1]].paragraphs[0].text = str(subtotal + tax)
    table.rows[trc_billing_amount[0]].cells[trc_billing_amount[1]].paragraphs[0].text = str(subtotal + tax)
    document.save(doc_name)

結果

f:id:yoraba:20200704163540p:plain f:id:yoraba:20200704163609p:plain