feature: merge presentation file
I'm trying to use this lib to develop some customize slide layouts when I found there are only 9 default layouts. I just wonder if I could make a presentation template manually, save it as a.pptx, make another presentation template b,save it as b.pptx. So that I could filling some stuffs into a.pptx and b.pptx by coding then a merge function that combines the two filled file into a single c.pptx is what I really want.
The short answer is no, your usecase is not possible ATM with the current state of the art python-pptx. There is currently no way to copy slides. A slide is not portable, and cannot be easily duplicated or moved to another presentation.
Thanks for your answer, I understand your explanation of the current state of art python-pptx, some libs are usually limited by its design motivations. But I disagree with you about
a slide is not portable.
You know, in Windows operating system, I can easily copy slides of one PPT file into another.
Slides (in the context of pptx python package) are not portable.
Slides (as part of Interop/COM) are portable. So, if you want to deal with the overhead of that, you can do this sort of merge (relatively) easily. Instead of using python-pptx, just use win32com :) or whip something up in VBA.
Note: When working with the Application object that way, all of the normal events are exposed, etc., and there's a lot of shenanigans that (inevitably must) go on under the hood in order to accomplish the stuff that we take for granted from UX, which become apparent when we're working abstractly with the XML. Buuuuuut, there's also a lot of things that are really fussy about that object model, things you literally cannot do without making the application window Visible=True, or keeping certain slides active/in view, in some cases shapes/etcs must be "selected", etc. and inserting Chart Data requires instantiating an Excel instance which takes focus and intercepts keystrokes, which can easily lead to runtime errors because Excel.Application is unresponsive in that state, etc...
I'm not sure I fully understand your use-case, but couldn't that (probably) be accomplished by creating multiple Masters within a single template PPTX file? And then, rather than creating two separate presentations and merging them together, you would create the "a" slides from layouts defined within pres.slide_masters[0] and the "b" slides from layouts defined within pres.slide_masters[1], etc.
I'm pretty new to the actual process of updating via PRs etc, merges, but I wrote these functions using the existing functions in the slide duplicate issue. I am still debugging some of the xml issues that are coming out of the merge function below. Still pretty new to this library, but thought putting this out there as a starting point, might help someone or many someones. I'm open to working to help develop this feature. The current code below overwrites some images, and goes on the fritz if you have different sized slides, (ppt doesn't like that either though), but still debugging, it works for the simple processing that I'm doing with repeating logos and txt.
DZemens solution is great and I have done this for desktop solutions in past, but I am trying to build this out for a web-app for server side processing on a linux server, (for a non-profit) so it's out of the question for me to use the excel application. unfortunately.
from pptx.parts.chart import ChartPart
from pptx.parts.embeddedpackage import EmbeddedXlsxPart
from pptx import Presentation
import copy
def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def move_slide(src, pres, index):
"""Src: source Presentation, pres: Target Presentstion, index: slide number in the presentation. Duplicate the slide with the given index in pres.
Adds slide to the end of the presentation"""
source = src.slides[index]
blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres.slides.add_slide(blank_slide_layout)
for shape in source.shapes:
newel = copy.deepcopy(shape.element)
dest.shapes._spTree.insert_element_before(newel, 'p:extLst')
for key, value in source.part.rels.items():
# Make sure we don't copy a notesSlide relation as that won't exist
if "notesSlide" not in value.reltype:
target = value._target
# if the relationship was a chart, we need to duplicate the embedded chart part and xlsx
if "chart" in value.reltype:
partname = target.package.next_partname(
ChartPart.partname_template)
xlsx_blob = target.chart_workbook.xlsx_part.blob
target = ChartPart(partname, target.content_type,
copy.deepcopy(target._element), package=target.package)
target.chart_workbook.xlsx_part = EmbeddedXlsxPart.new(
xlsx_blob, target.package)
dest.part.rels.add_relationship(value.reltype,
target,
value.rId)
return dest
def pptxmerge(merge_files,out_file):
"""merge_files is a list of files to be merged, out_file is the name of the file to be saved as at the outset"""
destination = Presentation(merge_files[0])
for file in merge_files[1:]:
source = Presentation(file)
for i in range(len(source.slides)):
#print(i)
move_slide(source, destination, i)
destination.save(out_file)`
I'm pretty new to the actual process of updating via PRs etc, merges, but I wrote these functions using the existing functions in the slide duplicate issue. I am still debugging some of the xml issues that are coming out of the merge function below. Still pretty new to this library, but thought putting this out there as a starting point, might help someone or many someones. I'm open to working to help develop this feature. The current code below overwrites some images, and goes on the fritz if you have different sized slides, (ppt doesn't like that either though), but still debugging, it works for the simple processing that I'm doing with repeating logos and txt.
DZemens solution is great and I have done this for desktop solutions in past, but I am trying to build this out for a web-app for server side processing on a linux server, (for a non-profit) so it's out of the question for me to use the excel application. unfortunately.
from pptx.parts.chart import ChartPart from pptx.parts.embeddedpackage import EmbeddedXlsxPart from pptx import Presentation import copy def _get_blank_slide_layout(pres): layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts] min_items = min(layout_items_count) blank_layout_id = layout_items_count.index(min_items) return pres.slide_layouts[blank_layout_id] def move_slide(src, pres, index): """Duplicate the slide with the given index in pres. Adds slide to the end of the presentation""" source = src.slides[index] blank_slide_layout = _get_blank_slide_layout(pres) dest = pres.slides.add_slide(blank_slide_layout) for shape in source.shapes: newel = copy.deepcopy(shape.element) dest.shapes._spTree.insert_element_before(newel, 'p:extLst') for key, value in source.part.rels.items(): # Make sure we don't copy a notesSlide relation as that won't exist if "notesSlide" not in value.reltype: target = value._target # if the relationship was a chart, we need to duplicate the embedded chart part and xlsx if "chart" in value.reltype: partname = target.package.next_partname( ChartPart.partname_template) xlsx_blob = target.chart_workbook.xlsx_part.blob target = ChartPart(partname, target.content_type, copy.deepcopy(target._element), package=target.package) target.chart_workbook.xlsx_part = EmbeddedXlsxPart.new( xlsx_blob, target.package) dest.part.rels.add_relationship(value.reltype, target, value.rId) return dest def pptxmerge(merge_files,out_file): """merge_files is a list of files to be merged, out_file is the name of the file to be saved as at the outset""" destination = Presentation(merge_files[0]) for file in merge_files[1:]: source = Presentation(file) for i in range(len(source.slides)): #print(i) move_slide(source, destination, i) destination.save(out_file)`
Ben - Thanks for this! Can you please explain what the parameters mean in your functions? like what is src and pres and Index?
I updated my comments above, pres is the presentation you are moving files to, src is the presentation it’s coming from, and index is the slide number in the presentation. Also, the files need to be Class Presentation, not just the file as a string. @vivek1383
I'm pretty new to the actual process of updating via PRs etc, merges, but I wrote these functions using the existing functions in the slide duplicate issue. I am still debugging some of the xml issues that are coming out of the merge function below. Still pretty new to this library, but thought putting this out there as a starting point, might help someone or many someones. I'm open to working to help develop this feature. The current code below overwrites some images, and goes on the fritz if you have different sized slides, (ppt doesn't like that either though), but still debugging, it works for the simple processing that I'm doing with repeating logos and txt.
DZemens solution is great and I have done this for desktop solutions in past, but I am trying to build this out for a web-app for server side processing on a linux server, (for a non-profit) so it's out of the question for me to use the excel application. unfortunately.
from pptx.parts.chart import ChartPart from pptx.parts.embeddedpackage import EmbeddedXlsxPart from pptx import Presentation import copy def _get_blank_slide_layout(pres): layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts] min_items = min(layout_items_count) blank_layout_id = layout_items_count.index(min_items) return pres.slide_layouts[blank_layout_id] def move_slide(src, pres, index): """Src: source Presentation, pres: Target Presentstion, index: slide number in the presentation. Duplicate the slide with the given index in pres. Adds slide to the end of the presentation""" source = src.slides[index] blank_slide_layout = _get_blank_slide_layout(pres) dest = pres.slides.add_slide(blank_slide_layout) for shape in source.shapes: newel = copy.deepcopy(shape.element) dest.shapes._spTree.insert_element_before(newel, 'p:extLst') for key, value in source.part.rels.items(): # Make sure we don't copy a notesSlide relation as that won't exist if "notesSlide" not in value.reltype: target = value._target # if the relationship was a chart, we need to duplicate the embedded chart part and xlsx if "chart" in value.reltype: partname = target.package.next_partname( ChartPart.partname_template) xlsx_blob = target.chart_workbook.xlsx_part.blob target = ChartPart(partname, target.content_type, copy.deepcopy(target._element), package=target.package) target.chart_workbook.xlsx_part = EmbeddedXlsxPart.new( xlsx_blob, target.package) dest.part.rels.add_relationship(value.reltype, target, value.rId) return dest def pptxmerge(merge_files,out_file): """merge_files is a list of files to be merged, out_file is the name of the file to be saved as at the outset""" destination = Presentation(merge_files[0]) for file in merge_files[1:]: source = Presentation(file) for i in range(len(source.slides)): #print(i) move_slide(source, destination, i) destination.save(out_file)`
@benjaminwheel3r
I'm getting the below error, while trying to run the code.
The error generates at this line:
destination.save(out_file)
Error:
for rel in source.rels.values():
AttributeError: 'str' object has no attribute 'rels'
@NarenZen Could you make that code work or are you still facing the same error?
@ApurvaDani Facing the error
@NarenZen can you provide an example of what your code looks like? what are your data types in your inputs? what operating system are you on? What python version are you using? (The below works without failing in 3.8) What version of python-pptx are you using? (I tested against 0.6.18) feel free to add to your above comment to keep the chain short.
some test data
pr0 = "C:/Users/foo/Desktop/genra_bay_area.pptx"
pr1 = "C:/Users/foo/Desktop/genra_joe_pesc.pptx"
pr2 = "C:/Users/foo/Desktop/genra_zoe.pptx"
pr3 = "C:/Users/foo/Desktop/genra_artist.pptx"
downloads = [pr0, pr1, pr2, pr3]
pptxmerge(downloads,out_file='all.pptx')
#pptxmerge(downloads,out_file='C:/Users/foo/Desktop/all.pptx')
Thank you @benjaminwheel3r . The version 0.6.18 is necessary. Previous I am using python-pptx==0.6.21, and it throws the error:
Traceback (most recent call last):
File "test1.py", line 55, in <module>
pptxmerge(['data/template.pptx', 'data/template.pptx'], 'data/merge.pptx')
File "test1.py", line 51, in pptxmerge
move_slide(source, destination, i)
File "test1.py", line 24, in move_slide
for key, value in source.part.rels.items():
File "D:\Programs\miniconda3\envs\dev\lib\_collections_abc.py", line 744, in __iter__
yield (key, self._mapping[key])
File "D:\Programs\miniconda3\envs\dev\lib\site-packages\pptx\opc\package.py", line 507, in __getitem__
raise KeyError("no relationship with key '%s'" % rId)
KeyError: "no relationship with key '<pptx.opc.package._Relationship object at 0x00000107B5121700>'"
When I change to 0.6.18, only Warnings left and it works