1
votes

Playing around with Python PPTX and it seems that it is not reading in the slide master name properly.

You can see here I rename the slide master

enter image description here

and it even shows up when trying to add a slide

enter image description here

However when I load the presentation through pptx the name is ''.

In [14]: import pptx
In [15]: pres = pptx.Presentation("adsf.pptx")
In [16]: pres.slide_master.name
Out[17]: ''

Maybe I am doing something wrong from the powerpoint side. I'd love to know. I am using Office 2016. As a side note, I was digging around the xml and it appears the slidemaster xml isn't holding any attributes related to the name attributes in the ooxml. However the theme xml does. Color me confused.

Thanks for your time and efforts

EDIT:

enter image description here

enter image description here

After all this I have found my solution and would like to thank @Scanny

import pptx
import re
from lxml import etree

# This causes pres.slide_master.layout.placeholder.name to be passed to pres.slide.placeholder
@property
def placeholder_name(self):
    """Name of the placeholder inherited from slide master"""
    return self._inherited_value("name")

def Presentation(powerpoint=None):
    """
    Return a |Presentation| object loaded from *pptx*, where *pptx* can be
    either a path to a ``.pptx`` file (a string) or a file-like object. If
    *pptx* is missing or ``None``, the built-in default presentation
    "template" is loaded.
    """
    if powerpoint is None:
        powerpoint = pptx.api._default_pptx_path()

    # get the package and not just the presentation package
    package = pptx.package.Package.open(powerpoint)

    # now extract the document
    presentation_part = package.main_document_part

    if not pptx.api._is_pptx_package(presentation_part):
        tmpl = "file '%s' is not a PowerPoint file, content type is '%s'"
        raise ValueError(tmpl % (powerpoint, presentation_part.content_type))

    # the theme names are the slide master names
    themes = (part for part in package.parts if re.search("^/ppt/theme/theme\d+\.xml$",part.partname))
    theme_names = [etree.fromstring(theme.blob).get("name") for theme in themes]


    # now get the presentation
    presentation = presentation_part.presentation

    # change the slide master names
    for idx,sld_mstr in enumerate(presentation.slide_masters):
        sld_mstr.name = theme_names[idx]


    return presentation

pptx.Presentation = Presentation
pptx.shapes.placeholder._InheritsDimensions.placeholder_name = placeholder_name
In [2]: pres = pptx.Presentation("adsf.pptx")
In [3]: for sm in pres.slide_masters: print(sm.name)
my master
number 3
my second
In [4]: layout = pres.slide_masters[0].slide_layouts[0]
In [5]: layout.name
Out[5]: 'my master title slide layout'
In [6]: new_slide = pres.slides.add_slide(layout)
In [7]: new_slide.placeholders[0].name
Out[7]: 'Title 1'
In [8]: new_slide.placeholders[0].placeholder_name
Out[8]: 'Main Title'
In [9]: pres.save("test.pptx")                             
1

1 Answers

1
votes

Yeah, that's how I would proceed, look for the string "my master" in the XML and see where it shows up.

A PowerPoint theme includes a master and layouts, so the UI could figure this rename operation for a rename of the theme, which could make sense from a UI perspective, like you want to save this theme and then use it later by picking this name from a list.

The reverse operation might make sense, meaning use python-pptx to set the master name, read it back out to confirm, and then see where that shows up in the UI afterward, if anywhere.


You can get to the presentation-part directly from the presentation:

prs = Presentation(...)
presentation_part = prs._part

All parts have a reference to the package, you don't have to load it yourself:

package = presentation_part._package

But you probably don't need that because you can get to the presentation theme(s) (as opposed to Notes-pages themes etc.) directly from the presentation part:

from pptx.opc.constants import RELATIONSHIP_TYPE as RT

theme_rels = [rel for rel in presentation_part.rels if rel.reltype == RT.THEME]
theme_parts = [presentation_part.related_parts[rel.rId] for rel in theme_rels]

Then just load each theme in as an XmlPart with something like:

theme_parts = [
    XmlPart.load(
        part._partname,
        part._content_type,
        part._blob,
        part._package,
    )
    for part in theme_parts
]

And then you can get the root of the theme XML document on theme_part._element and you can use lxml.etree._Element methods on that to traverse that tree, or just get the root element name with theme_part._element.attribs["name"].

This is all just aircode from memory, but hopefully gives you enough to go on and you can post working code once you get there.

If what you have does the job then by all means use it, but this is perhaps more direct and uses the implementations already there so you may have less to worry about things going unexpectedly wrong :)

Pretty much all the code this is exercising is in pptx/opc/package.py.