You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Request for method to delete an existing picture in a presentation. An example use is to allow an existing slide deck to be repurposed by simply changing the customer logo in the deck.
@jhludwig: This should get it done as a temporary workaround. I'll leave this case open as a feature request to get the general case shapes.remove(shape) capability built.
Let me know how you go :)
frompptximportPresentationfrompptx.oxmlimportqndefimage_part_rId(picture):
""" Return relationship id, e.g. 'rId9' corresponding to the image part referenced by the picture shape *picture*. """blip_elm=picture._element.blipFill[qn('a:blip')]
returnblip_elm.get(qn('r:embed'))
defremove_picture(slide, picture):
""" Remove *picture* shape from *slide*. """# remove relationship to image partrId=image_part_rId(picture)
remove_rel_with_rId(slide, rId)
# remove <pic> elementpic_elm=picture._elementspTree=pic_elm.getparent()
spTree.remove(pic_elm)
# remove picture from shapesslide.shapes._values.remove(picture)
defremove_rel_with_rId(slide, rId):
""" Remove the relationship identified by *rId* from *slide*. """# get relationships listrels=slide._relationships._values# delete relationship with matching rIdforrelinrels:
ifrel._rId==rId:
rels.remove(rel)
breakprs=Presentation('contains_picture.pptx')
slide=prs.slides[0]
picture=slide.shapes[0]
remove_picture(slide, picture)
prs.save('out.pptx')
This is awesome! Just tried it out and it was nearly perfect. One speed bump I hit -- for whatever reason, in my slide deck, the slide._relationships value was null, and so in the function remove_rel_with_rId that you specified, execution would blow up on the rels - slide._relationships._values line (pptxparse.py is my program):
File "pptxparse.py", line 119, in <module>
remove_picture(shapes, logopicture)
File "pptxparse.py", line 31, in remove_picture
remove_rel_with_rId(slide, rId)
File "pptxparse.py", line 44, in remove_rel_with_rId
rels = slide._relationships._values
AttributeError: '_ShapeCollection' object has no attribute '_relationships'
so i wrapped it with a "try" and all works ok for now:
I coupled this with your other code to query the existing picture and strung it all together: get existing measurements, delete old picture, add a new picture with the old measurements. it works amazingly well!
thanks much. if there is anything i can do to help (on this issue or any other) let me know...
Note to self: implement shape delete as a method on each _BaseShape subclass, allowing distinct methods for each shape type and incremental addition of capability, with NotImplementedError on _BaseShape.delete(). E.g. _Picture.delete() executes code roughly equivalent to above. So once implemented, deleting a picture shape would be coded:
prs=Presentation('contains_picture.pptx')
picture=prs.slides[0].shapes[0] # or suitable to specific casepicture.delete()
prs.save('out.pptx')
Still no news? Deleting shapes is a basic feature but slide.shapes._values.remove(picture)
cannot be performed (SlideShapes object has no attribute _values)
As far as I know, deleting a picture consists in this list of steps:
In slide XML file
find the shape in the slide (type = <p:pic>)
keep the reference to the image relationship (<p:pic> -> <p:blipFill> -> <a:blip>, attribute r:embed)
delete the <p:pic> item
in slide relationship XML file
find the item whose Id attribute matches the previous reference
keep the reference to the image (attribute Target)
delete the item
delete the image matching the previous target
HOWEVER if two shapes A and B have a reference to the same image, deleting it in A will result in invalid image for B.
So either do not delete the image file (that what Apache POI does), or remove it only if unused anywhere else (like, keeping a dict with image reference mapped to a counter??).
The details of relationships are abstracted out into the pptx.opc sub-package; so things like looking up an image part by the r:Id used in the pic element is accomplished with calls like:
image_part=shape.part.related_parts[rId]
Also in the opc subpackage is the OpcPackage class, which represents all the parts and all their relationships. Basically it's the .pptx file once opened.
A reference to the package can be gotten from a part:
package=shape.part.package
And all the relationships in the package can be accessed from there:
So I'd be inclined to have it just traverse all the rels when needed rather than try to maintain a synchronized dictionary or something that you have to remember to update.
Has their been any progress on this? I got distracted because of x-mas and haven't accomplished anything myself. I just want to make sure I don't recreate the wheel when I start working on this again.
Activity
scanny commentedon Oct 3, 2013
@jhludwig: This should get it done as a temporary workaround. I'll leave this case open as a feature request to get the general case shapes.remove(shape) capability built.
Let me know how you go :)
jhludwig commentedon Oct 3, 2013
This is awesome! Just tried it out and it was nearly perfect. One speed bump I hit -- for whatever reason, in my slide deck, the slide._relationships value was null, and so in the function remove_rel_with_rId that you specified, execution would blow up on the rels - slide._relationships._values line (pptxparse.py is my program):
so i wrapped it with a "try" and all works ok for now:
I coupled this with your other code to query the existing picture and strung it all together: get existing measurements, delete old picture, add a new picture with the old measurements. it works amazingly well!
thanks much. if there is anything i can do to help (on this issue or any other) let me know...
scanny commentedon Oct 3, 2013
Oops, should be
remove_picture(*slide*, picture), notremove_picture(shapes,picture):)I've updated above.
scanny commentedon Oct 4, 2013
Note to self: implement shape delete as a method on each _BaseShape subclass, allowing distinct methods for each shape type and incremental addition of capability, with NotImplementedError on _BaseShape.delete(). E.g. _Picture.delete() executes code roughly equivalent to above. So once implemented, deleting a picture shape would be coded:
[-]shape: Support for deleting a picture[/-][+]feature: delete picture[/+]osamak commentedon Jan 21, 2016
The workaround mentioned above is no longer functional. A new workaround would be appreciated!
nikayou commentedon Dec 21, 2016
Still no news? Deleting shapes is a basic feature but
slide.shapes._values.remove(picture)cannot be performed (SlideShapes object has no attribute _values)
rhofst commentedon Dec 29, 2016
I am also looking for a solution to this problem.
nikayou commentedon Dec 30, 2016
As far as I know, deleting a picture consists in this list of steps:
HOWEVER if two shapes A and B have a reference to the same image, deleting it in A will result in invalid image for B.
So either do not delete the image file (that what Apache POI does), or remove it only if unused anywhere else (like, keeping a dict with image reference mapped to a counter??).
scanny commentedon Dec 30, 2016
@Lasconik Yes, you see the complexity involved.
The details of relationships are abstracted out into the pptx.opc sub-package; so things like looking up an image part by the r:Id used in the pic element is accomplished with calls like:
Also in the
opcsubpackage is theOpcPackageclass, which represents all the parts and all their relationships. Basically it's the .pptx file once opened.A reference to the package can be gotten from a part:
And all the relationships in the package can be accessed from there:
So I'd be inclined to have it just traverse all the rels when needed rather than try to maintain a synchronized dictionary or something that you have to remember to update.
rhofst commentedon Jan 26, 2017
Has their been any progress on this? I got distracted because of x-mas and haven't accomplished anything myself. I just want to make sure I don't recreate the wheel when I start working on this again.
scanny commentedon Jan 27, 2017
I haven't seen anything come through on it. I expect you're clear to go :)
vivek1383 commentedon May 18, 2020
Hello! Has this feature been updated?
NataliaAssange commentedon Sep 8, 2022
Is that feature shapes.remove() available now?