"""Slide and related objects.""" from __future__ import annotations from typing import IO, TYPE_CHECKING, cast from pptx.enum.shapes import PROG_ID from pptx.opc.constants import CONTENT_TYPE as CT from pptx.opc.constants import RELATIONSHIP_TYPE as RT from pptx.opc.package import XmlPart from pptx.opc.packuri import PackURI from pptx.oxml.slide import CT_NotesMaster, CT_NotesSlide, CT_Slide from pptx.oxml.theme import CT_OfficeStyleSheet from pptx.parts.chart import ChartPart from pptx.parts.embeddedpackage import EmbeddedPackagePart from pptx.slide import NotesMaster, NotesSlide, Slide, SlideLayout, SlideMaster from pptx.util import lazyproperty if TYPE_CHECKING: from pptx.chart.data import ChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.media import Video from pptx.parts.image import Image, ImagePart class BaseSlidePart(XmlPart): """Base class for slide parts. This includes slide, slide-layout, and slide-master parts, but also notes-slide, notes-master, and handout-master parts. """ _element: CT_Slide def get_image(self, rId: str) -> Image: """Return an |Image| object containing the image related to this slide by *rId*. Raises |KeyError| if no image is related by that id, which would generally indicate a corrupted .pptx file. """ return cast("ImagePart", self.related_part(rId)).image def get_or_add_image_part(self, image_file: str | IO[bytes]): """Return `(image_part, rId)` pair corresponding to `image_file`. The returned |ImagePart| object contains the image in `image_file` and is related to this slide with the key `rId`. If either the image part or relationship already exists, they are reused, otherwise they are newly created. """ image_part = self._package.get_or_add_image_part(image_file) rId = self.relate_to(image_part, RT.IMAGE) return image_part, rId @property def name(self) -> str: """Internal name of this slide.""" return self._element.cSld.name class NotesMasterPart(BaseSlidePart): """Notes master part. Corresponds to package file `ppt/notesMasters/notesMaster1.xml`. """ @classmethod def create_default(cls, package): """ Create and return a default notes master part, including creating the new theme it requires. """ notes_master_part = cls._new(package) theme_part = cls._new_theme_part(package) notes_master_part.relate_to(theme_part, RT.THEME) return notes_master_part @lazyproperty def notes_master(self): """ Return the |NotesMaster| object that proxies this notes master part. """ return NotesMaster(self._element, self) @classmethod def _new(cls, package): """ Create and return a standalone, default notes master part based on the built-in template (without any related parts, such as theme). """ return NotesMasterPart( PackURI("/ppt/notesMasters/notesMaster1.xml"), CT.PML_NOTES_MASTER, package, CT_NotesMaster.new_default(), ) @classmethod def _new_theme_part(cls, package): """Return new default theme-part suitable for use with a notes master.""" return XmlPart( package.next_partname("/ppt/theme/theme%d.xml"), CT.OFC_THEME, package, CT_OfficeStyleSheet.new_default(), ) class NotesSlidePart(BaseSlidePart): """Notes slide part. Contains the slide notes content and the layout for the slide handout page. Corresponds to package file `ppt/notesSlides/notesSlide[1-9][0-9]*.xml`. """ @classmethod def new(cls, package, slide_part): """Return new |NotesSlidePart| for the slide in `slide_part`. The new notes-slide part is based on the (singleton) notes master and related to both the notes-master part and `slide_part`. If no notes-master is present, one is created based on the default template. """ notes_master_part = package.presentation_part.notes_master_part notes_slide_part = cls._add_notes_slide_part(package, slide_part, notes_master_part) notes_slide = notes_slide_part.notes_slide notes_slide.clone_master_placeholders(notes_master_part.notes_master) return notes_slide_part @lazyproperty def notes_master(self): """Return the |NotesMaster| object this notes slide inherits from.""" notes_master_part = self.part_related_by(RT.NOTES_MASTER) return notes_master_part.notes_master @lazyproperty def notes_slide(self): """Return the |NotesSlide| object that proxies this notes slide part.""" return NotesSlide(self._element, self) @classmethod def _add_notes_slide_part(cls, package, slide_part, notes_master_part): """Create and return a new notes-slide part. The return part is fully related, but has no shape content (i.e. placeholders not cloned). """ notes_slide_part = NotesSlidePart( package.next_partname("/ppt/notesSlides/notesSlide%d.xml"), CT.PML_NOTES_SLIDE, package, CT_NotesSlide.new(), ) notes_slide_part.relate_to(notes_master_part, RT.NOTES_MASTER) notes_slide_part.relate_to(slide_part, RT.SLIDE) return notes_slide_part class SlidePart(BaseSlidePart): """Slide part. Corresponds to package files ppt/slides/slide[1-9][0-9]*.xml.""" @classmethod def new(cls, partname, package, slide_layout_part): """Return newly-created blank slide part. The new slide-part has `partname` and a relationship to `slide_layout_part`. """ slide_part = cls(partname, CT.PML_SLIDE, package, CT_Slide.new()) slide_part.relate_to(slide_layout_part, RT.SLIDE_LAYOUT) return slide_part def add_chart_part(self, chart_type: XL_CHART_TYPE, chart_data: ChartData): """Return str rId of new |ChartPart| object containing chart of `chart_type`. The chart depicts `chart_data` and is related to the slide contained in this part by `rId`. """ return self.relate_to(ChartPart.new(chart_type, chart_data, self._package), RT.CHART) def add_embedded_ole_object_part( self, prog_id: PROG_ID | str, ole_object_file: str | IO[bytes] ): """Return rId of newly-added OLE-object part formed from `ole_object_file`.""" relationship_type = RT.PACKAGE if isinstance(prog_id, PROG_ID) else RT.OLE_OBJECT return self.relate_to( EmbeddedPackagePart.factory( prog_id, self._blob_from_file(ole_object_file), self._package ), relationship_type, ) def get_or_add_video_media_part(self, video: Video) -> tuple[str, str]: """Return rIds for media and video relationships to media part. A new |MediaPart| object is created if it does not already exist (such as would occur if the same video appeared more than once in a presentation). Two relationships to the media part are created, one each with MEDIA and VIDEO relationship types. The need for two appears to be for legacy support for an earlier (pre-Office 2010) PowerPoint media embedding strategy. """ media_part = self._package.get_or_add_media_part(video) media_rId = self.relate_to(media_part, RT.MEDIA) video_rId = self.relate_to(media_part, RT.VIDEO) return media_rId, video_rId @property def has_notes_slide(self): """ Return True if this slide has a notes slide, False otherwise. A notes slide is created by the :attr:`notes_slide` property when one doesn't exist; use this property to test for a notes slide without the possible side-effect of creating one. """ try: self.part_related_by(RT.NOTES_SLIDE) except KeyError: return False return True @lazyproperty def notes_slide(self) -> NotesSlide: """The |NotesSlide| instance associated with this slide. If the slide does not have a notes slide, a new one is created. The same single instance is returned on each call. """ try: notes_slide_part = self.part_related_by(RT.NOTES_SLIDE) except KeyError: notes_slide_part = self._add_notes_slide_part() return notes_slide_part.notes_slide @lazyproperty def slide(self): """ The |Slide| object representing this slide part. """ return Slide(self._element, self) @property def slide_id(self) -> int: """Return the slide identifier stored in the presentation part for this slide part.""" presentation_part = self.package.presentation_part return presentation_part.slide_id(self) @property def slide_layout(self) -> SlideLayout: """|SlideLayout| object the slide in this part inherits appearance from.""" slide_layout_part = self.part_related_by(RT.SLIDE_LAYOUT) return slide_layout_part.slide_layout def _add_notes_slide_part(self): """ Return a newly created |NotesSlidePart| object related to this slide part. Caller is responsible for ensuring this slide doesn't already have a notes slide part. """ notes_slide_part = NotesSlidePart.new(self.package, self) self.relate_to(notes_slide_part, RT.NOTES_SLIDE) return notes_slide_part class SlideLayoutPart(BaseSlidePart): """Slide layout part. Corresponds to package files ``ppt/slideLayouts/slideLayout[1-9][0-9]*.xml``. """ @lazyproperty def slide_layout(self): """ The |SlideLayout| object representing this part. """ return SlideLayout(self._element, self) @property def slide_master(self) -> SlideMaster: """Slide master from which this slide layout inherits properties.""" return self.part_related_by(RT.SLIDE_MASTER).slide_master class SlideMasterPart(BaseSlidePart): """Slide master part. Corresponds to package files ppt/slideMasters/slideMaster[1-9][0-9]*.xml. """ def related_slide_layout(self, rId: str) -> SlideLayout: """Return |SlideLayout| related to this slide-master by key `rId`.""" return self.related_part(rId).slide_layout @lazyproperty def slide_master(self): """ The |SlideMaster| object representing this part. """ return SlideMaster(self._element, self)
Memory