LoginSignup
3
13

More than 3 years have passed since last update.

Pythonを使ってJSONからWord(docx)を作成する

Last updated at Posted at 2019-06-07

Word(docx)の自動作成

はじめに

Mac環境の記事ですが、Windows環境も同じ手順になります。環境依存の部分は読み替えてお試しください。

目的

この記事を最後まで読むと、次のことができるようになります。

  • デスクトップアプリを実装する
  • Word(docx)の自動作成を実装する

アプリ

JSONデータとWord(docx)テンプレートをレンダリングして出力する。複数件のデータはページに分けて出力する。

スクリーンショット 2019-06-07 14.16.32.png

JSONデータ

sample_data.json
[
    {
        "create_date": "令和元年 5月 1日",
        "to_company_name": "カレンダー株式会社",
        "to_company_department": "イヤホン本部",
        "relocation_date": "令和元年 7月 28日",
        "post_code": "123-4567",
        "new_address": "東京都港区サンプル1-2-3 ビルディング 45F",
        "new_phone_number": "1234-56-7890",
        "new_fax_number": "1234-56-7890"
    },
    {
        "create_date": "2019年 5月 1日",
        "to_company_name": "冷暖房リモコン株式会社",
        "to_company_department": "ティッシュケース本部",
        "relocation_date": "2019年 7月 28日",
        "post_code": "987-6543",
        "new_address": "東京都港区サンプル9-8-7 ビルディング 65F",
        "new_phone_number": "0987-65-4321",
        "new_fax_number": "0987-65-4321"
    }
]

Word(docx)テンプレート

スクリーンショット 2019-06-07 17.12.37.png

実行結果

page 1/2

スクリーンショット 2019-06-07 17.26.42.png

page 2/2

スクリーンショット 2019-06-07 17.26.53.png

関連する記事

実行環境

環境 Ver.
macOS Mojave 10.14.5
Python 3.7.3
tkinter 8.5
docxtpl 0.6.1

ソースコード

実際に実装内容やソースコードを追いながら読むとより理解が深まるかと思います。是非ご活用ください。

GitHub

UIの実装

UIはTkinterで実装します。

Tkinterとは、Windows、MacOS、Linuxに対応するクロスプラットフォームなGUIライブラリです。

app.py
import os
import tkinter as tk
from tkinter import filedialog as fdialog
from tkinter import messagebox as mdialog

from model import Docx


class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.create_widgets()

    def set_title(self):
        self.master.title('Create Docx')

    def set_menu_bar(self):
        self.menu_bar = tk.Menu(self.master)
        self.master.config(menu=self.menu_bar)
        file_menu = tk.Menu(self.menu_bar)
        file_menu.add_command(label='Exit', command=self.master.quit)
        self.menu_bar.add_cascade(label='File', menu=file_menu)

    def select_file(self, entry):
        entry.delete(0, tk.END)
        entry.insert(0, fdialog.askopenfilename(initialdir=os.getcwd()))

    def create_docx(self, json_url, template_url):
        if not os.path.exists(json_url) or not os.path.exists(template_url):
            mdialog.showerror('Error', 'Please select JSON and Template.')
            return

        docx = Docx(json_url=json_url, template_url=template_url)
        docx.render()

    def set_body(self):
        tk.Label(self.master, text='JSON:').grid(row=0, column=0)
        entry_json = tk.Entry(self.master)
        entry_json.grid(row=0, column=1, pady=5)
        tk.Button(self.master, text='Select...',
                  command=lambda: self.select_file(entry_json)).grid(row=0, column=2)

        tk.Label(self.master, text='Template:').grid(row=1, column=0)
        entry_template = tk.Entry(self.master)
        entry_template.grid(row=1, column=1, pady=5)
        tk.Button(self.master, text='Select...',
                  command=lambda: self.select_file(entry_template)).grid(row=1, column=2)

        tk.Button(self.master, text='Create', width=30,
                  command=lambda: self.create_docx(entry_json.get(), entry_template.get())).grid(row=2, column=0, columnspan=3)

    def create_widgets(self):
        self.master.geometry()
        self.entry = tk.Entry(self.master)

        self.set_title()
        self.set_menu_bar()
        self.set_body()


# fix tkinter bug start
def fix_bug():
    width_height = root.winfo_geometry().split('+')[0].split('x')
    width = int(width_height[0])
    height = int(width_height[1])
    root.geometry('{}x{}'.format(width+1, height+1))
# fix tkinter bug end


if __name__ == '__main__':
    root = tk.Tk()
    app = Application(master=root)
    # fix tkinter bug start
    root.update()
    root.after(0, fix_bug)
    # fix tkinter bug end
    app.mainloop()

※ tkinterで白画面になる不具合がありサイズを1プラスすることで改修

docx作成の実装

docxの作成はdocxtplで実装します。

docxtplとは、JSONデータとWord(docx)テンプレートをレンダリングするライブラリです。

model.py
import cgi
import json
import os.path
import re
import sys
import uuid
from tkinter import messagebox as mdialog

from docxtpl import DocxTemplate


_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x1F),
                    (0x7F, 0x84), (0x86, 0x9F),
                    (0xFDD0, 0xFDDF), (0xFFFE, 0xFFFF)]
if sys.maxunicode >= 0x10000:  # not narrow build
    _illegal_unichrs.extend([(0x1FFFE, 0x1FFFF), (0x2FFFE, 0x2FFFF),
                             (0x3FFFE, 0x3FFFF), (0x4FFFE, 0x4FFFF),
                             (0x5FFFE, 0x5FFFF), (0x6FFFE, 0x6FFFF),
                             (0x7FFFE, 0x7FFFF), (0x8FFFE, 0x8FFFF),
                             (0x9FFFE, 0x9FFFF), (0xAFFFE, 0xAFFFF),
                             (0xBFFFE, 0xBFFFF), (0xCFFFE, 0xCFFFF),
                             (0xDFFFE, 0xDFFFF), (0xEFFFE, 0xEFFFF),
                             (0xFFFFE, 0xFFFFF), (0x10FFFE, 0x10FFFF)])
_illegal_ranges = ['%s-%s' % (chr(low), chr(high))
                   for (low, high) in _illegal_unichrs]
_illegal_xml_chars_RE = re.compile(u'[%s]' % u''.join(_illegal_ranges))


class Docx(object):
    def __init__(self, json_url, template_url):
        self.json_url = json_url
        self.template_url = template_url

    def read_data(self):
        with open(self.json_url, 'r') as f:
            load_data = json.load(f)

        json_data = json.dumps(load_data)
        json_data = cgi.escape(json_data)
        json_data = json_data.replace('\n', '\\n')
        dict_data = json.loads(json_data)

        for d in dict_data:
            for k in d.keys():
                try:
                    d[k] = _illegal_xml_chars_RE.sub('', d[k])
                except TypeError:
                    pass

        return dict_data

    def render(self):
        dict_data = self.read_data()

        docx = DocxTemplate(self.template_url)
        docx.render({'applications': dict_data})

        file_name = '{}.{}'.format(str(uuid.uuid4()), 'docx')

        save_dir = os.path.join(os.path.curdir, 'output')
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)

        docx.save(os.path.join(save_dir, file_name))

        mdialog.showinfo('Successful', 'Please check the output folder.')
3
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
13