CEL テックブログ

株式会社CELの公式ブログです。サイバーセキュリティ・社内開発について発信します

Pythonでドキュメント自動生成 - odfpy

PythonOpenDocument ファイルを扱えるライブラリ「odfpy」を使用して、文書ファイル(odtファイル)を生成するやり方を紹介します。

Odfpyについて

odfpyは、OpenDocument フォーマットのファイルを作成・編集・パースすることができるオープンソースPythonライブラリです。リポジトリのREADME.mdにはGPL v2 以降または Apache License v.2 のデュアルライセンスと記述されていますが、各ソースファイルの注釈には異なる記載があるため、内部利用以外が目的の場合には注意が必要かもしれません。

github.com

OpenDocument ファイルは、複数のXMLファイルをzipアーカイブ化して文書ファイルとする構成になっています*1。odfpyは OpenDocument ファイルとして適切なXML要素・属性を作成・編集する機能と、添付する画像などのファイルを含めてzipアーカイブを作成・操作する機能を持っています。 使いこなすには ODF に関する比較的低レイヤーな知識が必要になりますが、ODFの仕様で定義されたものはほとんど何でも使うことができます。

主要な更新は2021年に停止していますが、比較的安定して動作します。

環境

  • Windows 10 Pro, バージョン 22H2
  • Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32
  • odfpy バージョン
> pip show odfpy
Name: odfpy
Version: 1.4.1
Summary: Python API and tools to manipulate OpenDocument files
Home-page: https://github.com/eea/odfpy
Author: Soren Roug
Author-email: soren.roug@eea.europa.eu
License:
Location: ***
Requires: defusedxml
Required-by:

準備

odfpy は 2023年2月現在で PyPI に登録されているため、 pip で簡単にインストールできます。

> pip install odfpy

コード例と解説

OdtファイルでのHello World!

まずはHello Worldです。段落を一つだけ出力するサンプルです。

from odf.opendocument import OpenDocumentText
import odf.text as odftext

# ドキュメント本体のオブジェクトを生成
doc = OpenDocumentText()

# 段落要素を生成
paragraph = odftext.P(text="Hello World!")
# 段落をドキュメントに追加
doc.text.addElement(paragraph)

# odt ファイルへ書き出す
doc.save("hello.odt")

この Python ファイルを実行すると、「hello.odt」 ファイルが作成されます。LibreOffice 等のオフィススイートアプリで開くと、下の画像のような内容になっているはずです。

hello.odt の内容

この例ではスタイルなどを何も設定していないため、フォントやページ設定は環境によって変わるかもしれません*2

解説

  • from odf.opendocument import * ドキュメント全体の管理オブジェクトを使用するためにインポートします。
  • import odf.text as odftext odfの段落要素を使用するためのモジュールをインポートします。段落要素は text モジュールで定義されています。なお、odfで定められた各名前空間と同名のモジュールがあるので、ほかの要素を使用したい場合は対応するモジュールのインポートが必要になります。
  • doc = OpenDocumentText() ドキュメント全体の管理オブジェクトを生成しています。基本的には、このオブジェクトに要素を追加していく方法でドキュメントを構成します。
  • paragraph = odftext.P(text="Hello World!") 段落要素を生成しています。text引数に内容を指定します*3
  • doc.text.addElement(paragraph) 生成した段落要素をドキュメントの text 要素に追加します。
  • doc.save("hello.odt") ドキュメントをファイルへ書き出します。

スタイルの使用

上の例を応用すれば文章をodfファイル化できますが、書式を変えることができず寂しい見た目になってしまいます。また、書類の体裁を作成するには全く不十分です。

通常オフィススイートでできるフォント名・サイズ・太字・斜体・下線・文字色の変更や、段落前後の行間指定・文字列の左右中央揃え・字下げの有無の変更などは、スタイルを段落(やスパン)要素に適用することで行うことができます。

サンプルコード

文書内に様々なスタイルを適用した例です*4

from odf.opendocument import OpenDocumentText
import odf.text as odftext
import odf.style as odfstyle

# ドキュメント本体のオブジェクトを生成
doc = OpenDocumentText()

# スタイル要素を生成して文書に追加
style_right = odfstyle.Style(name="RightAlign", family="paragraph")
style_right.addElement(odfstyle.ParagraphProperties(textalign="right"))
doc.styles.addElement(style_right)

style_indented = odfstyle.Style(name="Indented", family="paragraph")
style_indented.addElement(odfstyle.ParagraphProperties(textindent="0.4cm", margintop="0.275cm", marginbottom="0.275cm"))
doc.styles.addElement(style_indented)

style_title = odfstyle.Style(name="Title", family="paragraph")
style_title.addElement(odfstyle.ParagraphProperties(textalign="center"))
style_title.addElement(odfstyle.TextProperties(fontsizeasian="16pt", fontsizecomplex="16pt", fontfamily="MS Gothic", fontweightasian="bold"))
doc.styles.addElement(style_title)

style_underlined = odfstyle.Style(name="Underlined", family="paragraph")
style_underlined.addElement(odfstyle.TextProperties(textunderlinestyle="solid", textunderlinetype="single"))
doc.styles.addElement(style_underlined)

style_italic = odfstyle.Style(name="Italic", family="text")
style_italic.addElement(odfstyle.TextProperties(fontstyle="italic"))
doc.styles.addElement(style_italic)

style_red = odfstyle.Style(name="Red", family="text")
style_red.addElement(odfstyle.TextProperties(color="#BF0000"))
doc.styles.addElement(style_red)

# 段落要素を生成
paragraph_title = odftext.P(text="文書自動生成のご案内", stylename=style_title)
doc.text.addElement(paragraph_title)
paragraph_to = odftext.P(text="odfpy 御中", stylename=style_underlined)
doc.text.addElement(paragraph_to)
paragraph_from = odftext.P(text="株式会社CEL", stylename=style_right)
doc.text.addElement(paragraph_from)

# 段落をドキュメントに追加
paragraph_text = odftext.P(text="この方面から...[中略]...いけないです。", stylename=style_indented)
doc.text.addElement(paragraph_text)

# スパン要素を生成
span1 = odftext.Span(text="文書自動生成を...[中略]...こう言いました、「")
span2 = odftext.Span(stylename=style_italic, text="あまり多くを求めないことだ。とくに他人に対しては。")
span3 = odftext.Span(text="」諸君にもこの言葉の...[中略]...はこう言いました、「")
span4 = odftext.Span(stylename=style_red, text="貴方の心からくるものは、人の心を動かす。")
span5 = odftext.Span(text="」思い返せば。")
paragraph_text = odftext.P(text="", stylename=style_indented)
# スパン要素を段落要素に追加
paragraph_text.addElement(span1)
paragraph_text.addElement(span2)
paragraph_text.addElement(span3)
paragraph_text.addElement(span4)
paragraph_text.addElement(span5)
doc.text.addElement(paragraph_text) 

# odt ファイルへ書き出す
doc.save("styles.odt")

出力例

出力されたodtファイルは、Microsoft Word で次のように表示されます

スタイルの適用例

解説

  • odfstyle.Style(name="Title", family="paragraph") スタイル要素を生成しています。name属性は、ODFの内部でスタイルを識別するために使用されます。重複しない限りかなり自由に決められるようです。family属性は、このスタイルがどの種類であるかを決めるために使用されます。設定できる値はODFの仕様で定義されていますが、本記事のユースケースであれば段落要素に適用するスタイルには"paragraph"、スパン要素に適用するスタイルには"text"を設定すれば問題ありません。
  • odfstyle.ParagraphProperties(...) 段落のスタイルパラメータです。段落前後の改行・字下げ・囲み線・直前の改ページが設定できます*5
  • odfstyle.TextProperties(...) 文字のスタイルパラメータです。フォント名・文字色・太字・斜体・下線などが設定できます*6*7
  • odftext.P(text="...", stylename=***) 段落要素にスタイルを適用します。stylename引数にfamily='paragraph'として生成したスタイル要素を渡すと、そのスタイルを適用してくれます。Styleオブジェクトのname引数に指定した文字列(例えば"Title")をstylename引数に渡しても同様の結果になります。
  • odftext.Span(text="...", stylename=***) スパン要素にスタイルを適用します。stylename引数にfamily='text'として生成したスタイル要素を渡すと、そのスタイルを適用してくれます。Styleオブジェクトのname引数に指定した文字列(例えば"Red")をstylename引数に渡しても同様の結果になります。
  • paragraph_text.addElement(span1) スパン要素を段落要素に追加します。

まとめ

段落要素 P とスタイル要素 Style を組み合わせることで、送り状や挨拶文などの基本的な文書であればスクリプトで自動生成することができます。 図表・箇条書き・見出し・ハイパーリンクの追加や、ヘッダー・フッターの設定には、さらにほかのODF要素を組み合わせる必要があります。 追って記事を執筆したいと思います。

また、どの属性を指定するとどのように文書に反映されるかがわからない場合にはodtファイルを直接見てみるのもよいでしょう。次の手順を踏むとodtファイル内部のXMLファイルを閲覧できます。

  1. LibreOfficeGUI上で文書を編集し、odtファイルとして保存
  2. 7-zip などのアーカイブツールでodtファイルを開くか、拡張子を.zipに変更して展開する。
  3. 内部にある「style.xml」や「content.xml」をテキストエディタで開く。

特別な符号化や圧縮はかけられていないので、GUI上で打ち込んだ文章はほぼそのままXMLのテキストとして挿入されているはずです。設定されている属性やデータ構造を見てみてください。

*1:規格は

https://blog.documentfoundation.org/blog/2021/06/23/odf-1-3-is-an-oasis-standard/に公開されています。最新規格はバージョン1.3ですが、odfpyがサポートするのはバージョン1.2( http://docs.oasis-open.org/office/v1.2/OpenDocument-v1.2.html )です。

*2:例えば私の環境では、LibreOffice で開くとフォント名が Liberation Serif になりますが、 Microsoft 365 の Word で開くと游明朝になります

*3:改行や連続する空白などは自動的に削除されます。これはodfで定義された仕様のようです。文の途中で改行したい場合にはtext.LineBreak要素を段落中に追加します。

*4:本文はhttps://garily.github.io/BullshitGenerator-Japanese/を使用して生成しました。

*5:設定可能な属性については、Open Document Format for Office Applications (OpenDocument) Version 1.2, §17.6をご覧ください。

*6:設定可能な属性については、Open Document Format for Office Applications (OpenDocument) Version 1.2, §16.27.28をご覧ください。

*7:font-family属性でフォントを指定するのは非推奨のようです。より確実なフォントの指定方法は今後執筆します。