python-pptx を使ってノート欄に追記する

プログラミング

python-pptx を使って既存のノート欄の装飾(太字や箇条書きなど)を保存したまま編集する方法を紹介します。

pptx-python とは

Python を使用してパワーポイントファイル (pptx) を編集できるライブラリとして python-pptx があります。ファイル全体の Presentation オブジェクトであったり、各スライドの Slide オブジェクト、図形や画像やプレースホルダーといった Shape オブジェクト、そしてノート欄の Note オブジェクトなどが定義されていて、そんなに凝ったことでなければ対応可能です。

例えば、既存の pptx ファイルを読み込んで、ページ数を出力するには以下のようなコードを記述します。

import pptx

p = pptx.Presentation("./sample.pptx")

print(len(p.slides))

また、全てのスライドのノート欄を抽出してテキストファイルとして保存するには、以下のようなコードを記述します。

import pptx

p = pptx.Presentation("./sample.pptx")

with open("./sample.txt", 'w') as out:
  for slide in p.slides:
    out.write(slide.notes_slide.notes_text_frame.text)
    out.write("\n\n")

装飾(太字や箇条書きなど)を保存したままノート欄を編集する

問題点の確認

ノート欄を抽出するだけなら上述した slide.notes_slide.notes_text_frame.text で十分なのですが、これを上書きしようとすると、少し残念なことになります。

例えば以下のようなコードを準備して、

import pptx

p = pptx.Presentation("./sample.pptx")

slide1 = p.slides[0]
slide1.notes_slide.notes_text_frame.text += "追記テキスト"

p.save("./sample2.pptx")

プログラムを実行すると以下のようになります。

元ファイル (sample.pptx) にあった太字や箇条書きといった装飾がなくなっています。

解決策

ノート欄のプレースホルダーを編集することで対応します。以下は追記の例です。

import pptx
from pptx.enum.shapes import PP_PLACEHOLDER

p = pptx.Presentation("./sample.pptx")

slide1 = p.slides[0]

def get_body_ph(placeholders):
  return list(filter(lambda ph: ph.element.ph_type == PP_PLACEHOLDER.BODY, placeholders))[0]

note_body = get_body_ph(slide1.notes_slide.placeholders).element.txBody

new_p = note_body.add_p()
new_p.append_text("追記テキスト")

p.save("./sample2.pptx")

解説

ノート欄は複数のプレースホルダーで構成されています。まずはどのようなプレースホルダーがあるのか、先程の sample.pptx で確認してみます。

import pptx

p = pptx.Presentation("./sample.pptx")

slide1 = p.slides[0]

for ph in slide1.notes_slide.placeholders:
  print("--------------")
  print(ph.element.ph_type)
  print(ph.text)

# --------------
# SLIDE_IMAGE (101)

# 
# --------------
# BODY (2)
# これは サンプル です。
# サンプル1
# サンプル2
# サンプル3
# --------------
# SLIDE_NUMBER (13)
# 1

少し補足をすると、そもそも pptx は内部的には xml で構造化されていて、python-pptx においては .element でその要素を取得できます。そして、xml 中にそのプレースホルダーの種類が定義されている場合は、ph_type にてそれを取得できます。結果を見てみると、どうやら BODY タイプが、追記すべきプレースホルダーのようです。

ということで、BODY プレースホルダーを取得する関数を定義します。

from pptx.enum.shapes import PP_PLACEHOLDER

def get_body_ph(placeholders):
  return list(filter(lambda ph: ph.element.ph_type == PP_PLACEHOLDER.BODY, placeholders))[0]

そして、この関数で得られたオブジェクトの element を string として出力してみると、実際のテキストは <p:txBody> 要素以下に配置されていることがわかるので、この部分だけ取得します。

note_body = get_body_ph(slide1.notes_slide.placeholders).element.txBody

あとは、この要素に対していろいろ操作をすることで、やりたいことが実現できます。既に存在する段落要素 <a:p> を削除すれば消えますし、新たな段落要素を追加してあげて、その段落要素に追記したいテキストをセットすれば、既存の段落には一切触れずに追記ができます。以下は、追記の例です。

new_p = note_body.add_p()
new_p.append_text("追記テキスト")

補足

そのオブジェクトの属性を出力:

def _print_attributes(obj):
  print(type(obj))
  for m in dir(obj):
      print(m)

xml を文字列として出力:

from lxml import etree

def _print_xml(obj):
  print(etree.tostring(obj).decode('utf-8'))
タイトルとURLをコピーしました