Skip to content

rpptx: setting Run.text invalidates every slide and shape handle, unlike every other setter and unlike rdocx #167

Description

@hadim

Thanks for the rpptx binding round (#141, #142): the deck can now be edited from Python end to end. One
behaviour makes it awkward: after run.text = ..., every handle to the presentation's slides, shapes,
paragraphs and runs raises StaleElementError on next use, including the shape the run belongs to. Setting a
font, a paragraph property, a shape's position or a text frame inset leaves them valid, and in rdocx
Run.text leaves paragraph and run handles valid too. A loop that rewrites the runs of one text box has to
re-navigate from Presentation after every run.

import rpptx
from pptx import Presentation
from pptx.util import Inches

p = Presentation(); s = p.slides.add_slide(p.slide_layouts[6])
tf = s.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1)).text_frame
tf.paragraphs[0].add_run().text = "alpha "; tf.paragraphs[0].add_run().text = "beta"
p.save("rs.pptx")


def fresh():
    prs = rpptx.Presentation("rs.pptx"); slide = prs.slides[0]; shape = slide.shapes[0]
    return prs, slide, shape


mutations = {
    "Run.text = ...": lambda prs, sl, sh: setattr(sh.text_frame.paragraphs[0].runs[0], "text", "ALPHA "),
    "Run.font.italic = True": lambda prs, sl, sh: setattr(sh.text_frame.paragraphs[0].runs[0].font, "italic", True),
    "Paragraph.space_after = ...": lambda prs, sl, sh: setattr(sh.text_frame.paragraphs[0], "space_after", 12700),
    "Shape.left = ...": lambda prs, sl, sh: setattr(sh, "left", sh.left + 12700),
    "TextFrame.margin_left = 0": lambda prs, sl, sh: setattr(sh.text_frame, "margin_left", 0),
}
for label, fn in mutations.items():
    prs, slide, shape = fresh()
    run = shape.text_frame.paragraphs[0].runs[1]
    fn(prs, slide, shape)
    out = []
    for name, probe in (("same shape", lambda: shape.left), ("same slide", lambda: len(slide.shapes)),
                        ("second run", lambda: run.text)):
        try:
            probe(); out.append(f"{name}: valid")
        except Exception as e:
            out.append(f"{name}: {type(e).__name__}")
    print(f"{label:<30} " + " | ".join(out))
Run.text = ...                 same shape: StaleElementError | same slide: StaleElementError | second run: StaleElementError
Run.font.italic = True         same shape: valid | same slide: valid | second run: valid
Paragraph.space_after = ...    same shape: valid | same slide: valid | second run: valid
Shape.left = ...               same shape: valid | same slide: valid | second run: valid
TextFrame.margin_left = 0      same shape: valid | same slide: valid | second run: valid

The README documents stale handles "after structural changes"; replacing the text of an existing run does
not change the structure: TextRunMut::set_text (rpptx/src/lib.rs:7189) only sets the run's text, and
the revision is bumped by the binding (rpptx-py/src/text.rs:886). PR #149 records it ("a later
run.text = ... also advances the revision") next to the rule that in-place setters do not advance it. Could the setter keep handles valid, as
the font setters do?

Environment: main at 9a7ed714 (S75), release build, linux x86_64, python-pptx 1.0.2 for the fixture.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions