🫵

【python】python-pptxで図形内のテキストを置き換える

2024/09/29に公開1

はじめに

pythonでパワーポイントを操作する際、python-pptxというライブラリがあります。python-pptxでどのようなことが可能なのかを調べる機会があり、意外といろんなことができると思った反面、細かいところまでは制御できなかったり、そもそもネット上に情報があまりなくて困りました。今回の記事では、図形内のテキストを置き換えるということに焦点を当てて記事を書いていきます。簡単そうに見えて意外と苦労するので、python-pptxを使用したい人の参考になる記事にしていこうと思います。

参考情報

先に参考情報を記載しておきます。
python-pptxの基本から応用的な操作まで以下の連載記事が非常にわかりやすいです。python-pptxを使い始める前に一通り読むことをお勧めします(一部説明のため、以下のサイトから画像も拝借させていただきます)。
https://www.shibutan-bloomers.com/python-libraly-pptx/988/

以下のサイトがpython-pptx公式の説明ページです。わかりやすくはないですが、参考にはなります。
https://python-pptx.readthedocs.io/en/latest/

バージョン

pythonとpython-pptxライブラリのバージョンは以下を使用しています。

  • python:3.11.0
  • python-pptx:1.0.2

なお、私はMac用のMicrosoft PowerPointを使用しているので、windowsの人とはパワポ画面が多少異なる部分があるかもですが、ある程度は同じと思いますので、適宜自分の環境に置き換えて見てください。

今回やっていくこと

以下のようなパワポの図の中のテキストを別のテキストに置き換えることを考えます。

図1
この時「図」というのは、パワポの図形で下図のように挿入できるもののうち、テキストを入力できる図形とざっくり捉えてもらえれば大丈夫です(一部違うものもありそうですが)。

図2
というのも、今回のやり方ではうまくテキストを置き換えられないものも存在するからです。例えば、SmartArtなどはおそらく上手くいきません。

python-pptxの前提知識

参考記事に記載されているように、Presentationオブジェクトの子としてSlideオブジェクトが存在し、その子としてShapeオブジェクトが存在します。このShapeオブジェクトにはテキストボックスやプレースホルダーなど色々な種類が存在し、今回の図形はその中でも図形(AutoShape)という種類になります。詳細は是非参考記事をご確認ください。

図3:https://www.shibutan-bloomers.com/python-libraly-pptx-5/1188/

パワポの解析・修正

まず、テキストを置き換えたいときに必要なことは、テキスト変更前のパワポファイルの解析および修正を行うことです。修正はしなくてもいい場合もありますが、修正した方がテキスト変更しやすかったり、修正しないと変更できないという場合もあります。

グループ化について

まず、図形がグループ化されていることがあります。テキスト置き換えしたい図形がグループ化されている場合はpython-pptxでのテキスト変更が(おそらく)困難になります。なので、パワポ上でグループ化を解除します。python-pptxのコード実行でグループ化したり、グループ化を解除する方法もわからなかったので、ここは非常に不便なところです(良い方法あれば教えて欲しいです。。)

Shapeオブジェクトの名前について

「整列」→「選択ウィンドウ」をクリックすると下画像の右にあるように「オブジェクトの選択と表示」という部分が表示されます。

図4
ここには各Shapeの名前が表示されています。名前は図形を挿入した時に勝手に命名されて、この名前になっています(命名に関して、図形の形は分かりますが、数字は謎です)。
このままでも問題はないのですが、今回はテキスト置き換えする際にわかりやすくするために、各Shapeの名前を下図のように変更しました(←名前部分をダブルクリックすれば編集できます)。

図5

その上で、以下のコードを実行すると全てのShapeの名前を確認することができます。以下のコードはパワポをPresentionで読み込んで、パワポのスライドの1枚目の中にあるShapeを繰り返し処理している感じになります。

from pptx import Presentation

prs = Presentation("python-pptx-test.pptx")

for shape in prs.slides[0].shapes:
    print(shape.name)

出力は以下のようになります。ちゃんとパワポ上で変更した名前になっていることが確認できます。

ShapeText1
Allow1
ShapeText2
Allow2
ShapeText3

Shapeオブジェクトの配置順番について

Shapeオブジェクトは作成した順に配置され、後から作成したものが手前側(前面)にくるように配置されます。今回のパワポでいえば上の図形から順番に作ったので、ShapeText1(STEP1と記載の図形)が一番奥にあり、ShapeText3(STEP3と記載の図形)が一番手前にあります。また、python-pptxでスライドのShapeを取り出す時、上のShape名を出力したコードのように、for文を使うと奥のShapeから手前のShapeにかけて順番に取り出されることがわかります。図形の配置順番がわかれば、何番目の図形はどの図形かが判明し、特定の図形に対しての処理を行いやすくなります。また、パワポ上で図形がどの順番で並んでいるかは下図のように「オブジェクトの選択と表示」部分を見ればわかります。下から上にかけて、奥から手前にくるようにShpae名が並ぶようになっています。

図6
この図形の順番はある図形を前面・背面に移動したりすると当然変更されます。
これは余談なのですが、下図のように「整列」→「オブジェクトの並び替え」をクリックすると、

図7
下図のように3次元的に図形が配置された層を確認することができ、並び替えもできるようで少し面白かったです。

図8

テキスト置き換え

part1:TextFrameオブジェクトでのテキスト変更

これまでの内容を活かして、以下コードで特定の図形(ShapeText1)のテキストを変更することが可能になります。if shape.name == "ShapeText1"のように、設定したShape名に一致している場合に動く処理にすることで、特定のShapeに対する処理を定義しやすくなります。

from pptx import Presentation

prs = Presentation("python-pptx-test.pptx")

for shape in prs.slides[0].shapes:
    if shape.name == "ShapeText1":
        shape.text_frame.text = "change1"

prs.save("output.pptx")

下図が完成したパワポの画像となっており、テキストの変更はできていますが、文字が中央寄せではなく、左寄せに変わってしまっています。このようにテキスト置き換えしようとすると思ったようにいかないことはよくあることで、これを頑張って解決できることもあれば、python-pptxに用意された機能では解決できないこともあります。

図9
上のコードでは、TextFrameオブジェクトのtextプロパティでテキストを置き換えようとしましたが、そうすると中央寄せだったという情報も消えてしまうようです。また、TextFrameオブジェクトでは横方向の中央寄せなどの位置調整ができないようでした。

そして、テキスト変更に関して少しでも理解を深めるためには、まず参考情報で紹介した以下サイトをご確認ください。
https://www.shibutan-bloomers.com/python-libraly-pptx-2/1024/
Shapeオブジェクトの中には、TextFrameオブジェクトが存在し、さらにその中にParagraphオブジェクト、さらにその中にRunオブジェクトというものが存在します。

図10:https://www.shibutan-bloomers.com/python-libraly-pptx-2/1024/

実はTextFrameオブジェクト以外に、ParagraphオブジェクトやRunオブジェクトを使用しても、テキストを変更することが可能です。ややこしいですが、親のオブジェクトでテキストを書き換える方が、元々設定されていた情報も上書きされる可能性が高まるものだと私は解釈しています。

part2:Paragraphオブジェクトでのテキスト変更

Paragraphオブジェクトを用いてテキスト変更をするために以下のコードを作成し、実行してみました。

from pptx import Presentation
from pptx.text.text import TextFrame

prs = Presentation("python-pptx-test.pptx")

for shape in prs.slides[0].shapes:
    if shape.name == "ShapeText1":
        text_frame: TextFrame = shape.text_frame
        text_frame.paragraphs[0].text = "change1"

prs.save("output.pptx")

Paragraphでテキスト変更すれば、下図のようにテキストの中央寄せは変わらず、変更することが可能になりました。

図11

part3:Runオブジェクトでのテキスト変更

Runオブジェクトを用いて、テキスト変更するコードは以下のように書けます。

from pptx import Presentation
from pptx.text.text import TextFrame

prs = Presentation("python-pptx-test.pptx")

for shape in prs.slides[0].shapes:
    if shape.name == "ShapeText1":
        text_frame: TextFrame = shape.text_frame
        text_frame.paragraphs[0].runs[0].text = "change1"

prs.save("output.pptx")

出力されるパワポは図11と同じものになりました。なので、今回の場合は、Paragraph・Runのどちらを用いてテキスト変更しても同じ結果が得られました。
図10ではRunオブジェクトは1文字に該当するかのように書かれていますが、runs[0]でParagraphと同じ段落部分を変更できてますし、runs[1]を確認しようとしたら存在しないというエラーになったので、このあたりは謎な部分です。

Shapeオブジェクトの補足

ShapeオブジェクトはTextFrameを持つ場合と持たない場合があります。今回は指定した図形がTextFrameを持つことがわかっていましたが、場合によっては以下コードのようにhas_text_frameメソッドでTextFrameを持つかどうかを判別してから、次の処理を行う方が良いかもしれません。

if shape.has_text_frame:  

各オブジェクトでできること

テキストを操作するために利用できる以下のオブジェクトについて、どんなパラメータを持っていて変更できそうかを参考記事から確認していきましょう。

  • TextFrameオブジェクト
  • Paragraphオブジェクト
  • Runオブジェクト

TextFrameオブジェクト


図12:https://www.shibutan-bloomers.com/python-libraly-pptx-2/1024/

Paragraphオブジェクト


図13:https://www.shibutan-bloomers.com/python-libraly-pptx-2/1024/
→TextFrameオブジェクトでは横方向(水平方向)の位置調整ができませんでしたが、Paragraphではできるようです。これを見るに横方向は各段落ごとに横方向の位置調整が変更が可能ということがわかりますね。
→フォントの色や太文字にするかどうかも調整することができます

Runオブジェクト


図14:https://www.shibutan-bloomers.com/python-libraly-pptx-2/1024/
→Paragraphオブジェクトより設定できるパラメータが少ないように思います
→もしかしたらハイパーリンクをつける以外にあまり使い所がないのかもしれません(ここら辺はしっかり調査できてないです)

最後に

python-pptxを用いて図形内のテキストを置き換える方法について、理解できたでしょうか?
PowerPointとpython-pptxについて理解を深めないと、コードでパワポ操作する際にうまくいかないことは多いでしょうし、理解したところでライブラリができることに限界があると凄く感じました。
皆さんの参考になれば幸いです!

Discussion

anthory79anthory79

フォントの変更に関しては、私はこちらでうまくいきました。
参考になれば。

# フォントの種類を指定
font_name = 'Meiryo UI'

pg = text_frame.paragraphs[0]
rn = pg.runs[0]
#----- ここが大事 ------
rpr = rn._r.get_or_add_rPr()
ea = etree.SubElement(rpr, qn('a:ea'))
ea.set('typeface', font_name)
#----- ここが大事 ------
rn.font.name = font_name