2424 from pptx .oxml .presentation import CT_SlideIdList , CT_SlideMasterIdList
2525 from pptx .oxml .slide import (
2626 CT_CommonSlideData ,
27+ CT_NotesMaster ,
2728 CT_NotesSlide ,
2829 CT_Slide ,
30+ CT_SlideLayout ,
2931 CT_SlideLayoutIdList ,
3032 CT_SlideMaster ,
3133 )
3234 from pptx .parts .presentation import PresentationPart
3335 from pptx .parts .slide import SlideLayoutPart , SlideMasterPart , SlidePart
3436 from pptx .presentation import Presentation
35- from pptx .shapes .placeholder import LayoutPlaceholder , MasterPlaceholder
37+ from pptx .shapes .placeholder import LayoutPlaceholder , MasterPlaceholder , SlidePlaceholder
3638 from pptx .shapes .shapetree import NotesSlidePlaceholder
3739 from pptx .text .text import TextFrame
3840
@@ -92,7 +94,84 @@ def shapes(self):
9294 return MasterShapes (self ._element .spTree , self )
9395
9496
95- class NotesMaster (_BaseMaster ):
97+ class _HeaderFooterVisibility :
98+ """Provides access to header/footer visibility settings on a slide template."""
99+
100+ _element : CT_SlideLayout | CT_SlideMaster | CT_NotesMaster
101+
102+ def _get_hf_visibility (self , attr_name : str ) -> bool :
103+ """Return effective `attr_name` value, defaulting to |True| when `<p:hf>` is absent."""
104+ hf = self ._element .hf
105+ return True if hf is None else getattr (hf , attr_name )
106+
107+ def _set_hf_visibility (self , attr_name : str , value : bool ) -> None :
108+ """Set `attr_name` on `<p:hf>`, creating the element only when needed.
109+
110+ Assigning |True| when `<p:hf>` is absent is a no-op because the effective default is
111+ already |True|. An existing `<p:hf>` element is retained even when all values become
112+ |True|, avoiding low-value XML churn.
113+ """
114+ hf = self ._element .hf
115+ if hf is None and value :
116+ return
117+ if hf is None :
118+ hf = self ._element .get_or_add_hf ()
119+ setattr (hf , attr_name , value )
120+
121+ @property
122+ def show_slide_number (self ) -> bool :
123+ """`True` when slide numbers are shown for this template, `False` otherwise.
124+
125+ Assigning |False| creates a `<p:hf>` element when needed and writes `sldNum="0"`.
126+ Assigning |True| preserves any existing `<p:hf>` element rather than removing it.
127+ """
128+ return self ._get_hf_visibility ("sldNum" )
129+
130+ @show_slide_number .setter
131+ def show_slide_number (self , value : bool ) -> None :
132+ self ._set_hf_visibility ("sldNum" , value )
133+
134+ @property
135+ def show_footer (self ) -> bool :
136+ """`True` when footer placeholders are shown for this template, `False` otherwise.
137+
138+ Assigning |False| creates a `<p:hf>` element when needed and writes `ftr="0"`.
139+ Assigning |True| preserves any existing `<p:hf>` element rather than removing it.
140+ """
141+ return self ._get_hf_visibility ("ftr" )
142+
143+ @show_footer .setter
144+ def show_footer (self , value : bool ) -> None :
145+ self ._set_hf_visibility ("ftr" , value )
146+
147+ @property
148+ def show_date (self ) -> bool :
149+ """`True` when date placeholders are shown for this template, `False` otherwise.
150+
151+ Assigning |False| creates a `<p:hf>` element when needed and writes `dt="0"`.
152+ Assigning |True| preserves any existing `<p:hf>` element rather than removing it.
153+ """
154+ return self ._get_hf_visibility ("dt" )
155+
156+ @show_date .setter
157+ def show_date (self , value : bool ) -> None :
158+ self ._set_hf_visibility ("dt" , value )
159+
160+ @property
161+ def show_header (self ) -> bool :
162+ """`True` when header placeholders are shown for this template, `False` otherwise.
163+
164+ Assigning |False| creates a `<p:hf>` element when needed and writes `hdr="0"`.
165+ Assigning |True| preserves any existing `<p:hf>` element rather than removing it.
166+ """
167+ return self ._get_hf_visibility ("hdr" )
168+
169+ @show_header .setter
170+ def show_header (self , value : bool ) -> None :
171+ self ._set_hf_visibility ("hdr" , value )
172+
173+
174+ class NotesMaster (_HeaderFooterVisibility , _BaseMaster ):
96175 """Proxy for the notes master XML document.
97176
98177 Provides access to shapes, the most commonly used of which are placeholders.
@@ -214,6 +293,33 @@ def has_notes_slide(self) -> bool:
214293 """
215294 return self .part .has_notes_slide
216295
296+ @property
297+ def has_date (self ) -> bool :
298+ """`True` if this slide has a date placeholder, `False` otherwise.
299+
300+ This property is non-mutating; it reports only whether a DATE placeholder is already
301+ present on the slide.
302+ """
303+ return self ._first_ph_of_type (PP_PLACEHOLDER .DATE ) is not None
304+
305+ @property
306+ def has_footer (self ) -> bool :
307+ """`True` if this slide has a footer placeholder, `False` otherwise.
308+
309+ This property is non-mutating; it reports only whether a FOOTER placeholder is already
310+ present on the slide.
311+ """
312+ return self ._first_ph_of_type (PP_PLACEHOLDER .FOOTER ) is not None
313+
314+ @property
315+ def has_slide_number (self ) -> bool :
316+ """`True` if this slide has a slide-number placeholder, `False` otherwise.
317+
318+ This property is non-mutating; it reports only whether a SLIDE_NUMBER placeholder is
319+ already present on the slide.
320+ """
321+ return self ._first_ph_of_type (PP_PLACEHOLDER .SLIDE_NUMBER ) is not None
322+
217323 @property
218324 def notes_slide (self ) -> NotesSlide :
219325 """The |NotesSlide| instance for this slide.
@@ -223,6 +329,60 @@ def notes_slide(self) -> NotesSlide:
223329 """
224330 return self .part .notes_slide
225331
332+ @property
333+ def date_text (self ) -> str | None :
334+ """Text of this slide's date placeholder, or |None| when no date placeholder is present.
335+
336+ Reading this property does not create a placeholder. An existing empty DATE placeholder
337+ returns an empty string.
338+ """
339+ placeholder = self ._first_ph_of_type (PP_PLACEHOLDER .DATE )
340+ return None if placeholder is None else placeholder .text_frame .text
341+
342+ @date_text .setter
343+ def date_text (self , value : str | None ) -> None :
344+ placeholder = self ._first_ph_of_type (PP_PLACEHOLDER .DATE )
345+ if value in (None , "" ):
346+ if placeholder is not None :
347+ placeholder .text_frame .text = ""
348+ return
349+
350+ if placeholder is None :
351+ layout_ph = self ._layout_ph_of_type (PP_PLACEHOLDER .DATE )
352+ if layout_ph is None :
353+ raise ValueError ("slide layout has no DATE placeholder to clone from" )
354+ self .shapes .clone_placeholder (layout_ph )
355+ placeholder = cast ("SlidePlaceholder" , self ._first_ph_of_type (PP_PLACEHOLDER .DATE ))
356+
357+ placeholder .text_frame .text = value
358+
359+ @property
360+ def footer (self ) -> str | None :
361+ """Text of this slide's footer placeholder, or |None| when no footer placeholder is present.
362+
363+ Reading this property does not create a placeholder. An existing empty FOOTER placeholder
364+ returns an empty string.
365+ """
366+ placeholder = self ._first_ph_of_type (PP_PLACEHOLDER .FOOTER )
367+ return None if placeholder is None else placeholder .text_frame .text
368+
369+ @footer .setter
370+ def footer (self , value : str | None ) -> None :
371+ placeholder = self ._first_ph_of_type (PP_PLACEHOLDER .FOOTER )
372+ if value in (None , "" ):
373+ if placeholder is not None :
374+ placeholder .text_frame .text = ""
375+ return
376+
377+ if placeholder is None :
378+ layout_ph = self ._layout_ph_of_type (PP_PLACEHOLDER .FOOTER )
379+ if layout_ph is None :
380+ raise ValueError ("slide layout has no FOOTER placeholder to clone from" )
381+ self .shapes .clone_placeholder (layout_ph )
382+ placeholder = cast ("SlidePlaceholder" , self ._first_ph_of_type (PP_PLACEHOLDER .FOOTER ))
383+
384+ placeholder .text_frame .text = value
385+
226386 @lazyproperty
227387 def placeholders (self ) -> SlidePlaceholders :
228388 """Sequence of placeholder shapes in this slide."""
@@ -247,6 +407,28 @@ def slide_layout(self) -> SlideLayout:
247407 """|SlideLayout| object this slide inherits appearance from."""
248408 return self .part .slide_layout
249409
410+ def _first_ph_of_type (self , ph_type : PP_PLACEHOLDER ) -> SlidePlaceholder | None :
411+ """Return the first SlidePlaceholder of `ph_type` in document order, or |None|.
412+
413+ This helper is non-mutating and returns the first matching slide placeholder when multiple
414+ placeholders of the same type are present.
415+ """
416+ for placeholder in self .placeholders :
417+ if placeholder .placeholder_format .type == ph_type :
418+ return placeholder
419+ return None
420+
421+ def _layout_ph_of_type (self , ph_type : PP_PLACEHOLDER ) -> LayoutPlaceholder | None :
422+ """Return the first LayoutPlaceholder of `ph_type` on this slide's layout, or |None|.
423+
424+ The layout placeholder is used as the source when promoting a latent placeholder to a
425+ slide-level placeholder on first write.
426+ """
427+ for placeholder in self .slide_layout .placeholders :
428+ if placeholder .placeholder_format .type == ph_type :
429+ return placeholder
430+ return None
431+
250432 def delete (self ) -> None :
251433 """Remove this slide from its presentation.
252434
@@ -426,7 +608,7 @@ def duplicate(self, slide: Slide, index: int | None = None) -> Slide:
426608 return new_slide_part .slide
427609
428610
429- class SlideLayout (_BaseSlide ):
611+ class SlideLayout (_HeaderFooterVisibility , _BaseSlide ):
430612 """Slide layout object.
431613
432614 Provides access to placeholders, regular shapes, and slide layout-level properties.
@@ -544,7 +726,7 @@ def remove(self, slide_layout: SlideLayout) -> None:
544726 slide_layout .slide_master .part .drop_rel (target_sldLayoutId .rId )
545727
546728
547- class SlideMaster (_BaseMaster ):
729+ class SlideMaster (_HeaderFooterVisibility , _BaseMaster ):
548730 """Slide master object.
549731
550732 Provides access to slide layouts. Access to placeholders, regular shapes, and slide master-level
0 commit comments