Skip to content

Commit c14854d

Browse files
author
robertbrodie
committed
fix: add notesMasterIdLst element when creating notes master
When python-pptx creates a notes master on first access to slide.notes_slide, it correctly creates the NotesMasterPart and adds the relationship in the .rels file, but omits the corresponding <p:notesMasterIdLst> reference from presentation.xml. Without this element, OOXML consumers cannot discover the notes master from the presentation element, even though the relationship and part exist. This causes Apple Keynote (and potentially other consumers) to fail to recognize the notes master, breaking speaker notes import. This fix: - Adds CT_NotesMasterIdList and CT_NotesMasterIdListEntry element classes with proper ZeroOrOne/RequiredAttribute declarations - Registers both element classes in the oxml element class lookup - Adds a ZeroOrOne declaration for notesMasterIdLst on CT_Presentation with correct successor sequence - Updates PresentationPart.notes_master_part to populate the notesMasterIdLst element with the relationship ID after creating the notes master relationship Closes #1051
1 parent 278b47b commit c14854d

4 files changed

Lines changed: 51 additions & 2 deletions

File tree

src/pptx/oxml/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]):
273273

274274

275275
from pptx.oxml.presentation import ( # noqa: E402
276+
CT_NotesMasterIdList,
277+
CT_NotesMasterIdListEntry,
276278
CT_Presentation,
277279
CT_SlideId,
278280
CT_SlideIdList,
@@ -281,6 +283,8 @@ def register_element_cls(nsptagname: str, cls: Type[BaseOxmlElement]):
281283
CT_SlideSize,
282284
)
283285

286+
register_element_cls("p:notesMasterId", CT_NotesMasterIdListEntry)
287+
register_element_cls("p:notesMasterIdLst", CT_NotesMasterIdList)
284288
register_element_cls("p:presentation", CT_Presentation)
285289
register_element_cls("p:sldId", CT_SlideId)
286290
register_element_cls("p:sldIdLst", CT_SlideIdList)

src/pptx/oxml/presentation.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class CT_Presentation(BaseOxmlElement):
1717
get_or_add_sldSz: Callable[[], CT_SlideSize]
1818
get_or_add_sldIdLst: Callable[[], CT_SlideIdList]
1919
get_or_add_sldMasterIdLst: Callable[[], CT_SlideMasterIdList]
20+
get_or_add_notesMasterIdLst: Callable[[], CT_NotesMasterIdList]
2021

2122
sldMasterIdLst: CT_SlideMasterIdList | None = (
2223
ZeroOrOne( # pyright: ignore[reportAssignmentType]
@@ -30,6 +31,17 @@ class CT_Presentation(BaseOxmlElement):
3031
),
3132
)
3233
)
34+
notesMasterIdLst: CT_NotesMasterIdList | None = (
35+
ZeroOrOne( # pyright: ignore[reportAssignmentType]
36+
"p:notesMasterIdLst",
37+
successors=(
38+
"p:handoutMasterIdLst",
39+
"p:sldIdLst",
40+
"p:sldSz",
41+
"p:notesSz",
42+
),
43+
)
44+
)
3345
sldIdLst: CT_SlideIdList | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
3446
"p:sldIdLst", successors=("p:sldSz", "p:notesSz")
3547
)
@@ -115,6 +127,30 @@ class CT_SlideMasterIdListEntry(BaseOxmlElement):
115127
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
116128

117129

130+
class CT_NotesMasterIdList(BaseOxmlElement):
131+
"""`p:notesMasterIdLst` element.
132+
133+
Child of `p:presentation` containing a reference to the notes master that belongs to the
134+
presentation.
135+
"""
136+
137+
_add_notesMasterId: Callable[..., CT_NotesMasterIdListEntry]
138+
notesMasterId = ZeroOrOne("p:notesMasterId")
139+
140+
def add_notesMasterId(self, rId: str) -> CT_NotesMasterIdListEntry:
141+
"""Create and return a new `p:notesMasterId` child element with r:id set to `rId`."""
142+
return self._add_notesMasterId(rId=rId)
143+
144+
145+
class CT_NotesMasterIdListEntry(BaseOxmlElement):
146+
"""`p:notesMasterId` element.
147+
148+
Child of `p:notesMasterIdLst` containing an `rId` reference to the notes master part.
149+
"""
150+
151+
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
152+
153+
118154
class CT_SlideSize(BaseOxmlElement):
119155
"""`p:sldSz` element.
120156

src/pptx/parts/presentation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ def notes_master_part(self) -> NotesMasterPart:
7272
return self.part_related_by(RT.NOTES_MASTER)
7373
except KeyError:
7474
notes_master_part = NotesMasterPart.create_default(self.package)
75-
self.relate_to(notes_master_part, RT.NOTES_MASTER)
75+
rId = self.relate_to(notes_master_part, RT.NOTES_MASTER)
76+
self._element.get_or_add_notesMasterIdLst().add_notesMasterId(rId)
7677
return notes_master_part
7778

7879
@lazyproperty

tests/parts/test_presentation.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,21 @@ def but_it_adds_a_notes_master_part_when_needed(
6565
NotesMasterPart_ = class_mock(request, "pptx.parts.presentation.NotesMasterPart")
6666
NotesMasterPart_.create_default.return_value = notes_master_part_
6767
part_related_by_.side_effect = KeyError
68-
prs_part = PresentationPart(None, None, package_, None)
68+
relate_to_.return_value = "rId42"
69+
prs_elm = element("p:presentation/p:sldMasterIdLst")
70+
prs_part = PresentationPart(None, None, package_, prs_elm)
6971

7072
notes_master_part = prs_part.notes_master_part
7173

7274
NotesMasterPart_.create_default.assert_called_once_with(package_)
7375
relate_to_.assert_called_once_with(prs_part, notes_master_part_, RT.NOTES_MASTER)
7476
assert notes_master_part is notes_master_part_
77+
# --- notesMasterIdLst element was added to presentation.xml ---
78+
notesMasterIdLst = prs_elm.notesMasterIdLst
79+
assert notesMasterIdLst is not None
80+
notesMasterId = notesMasterIdLst.notesMasterId
81+
assert notesMasterId is not None
82+
assert notesMasterId.rId == "rId42"
7583

7684
def it_provides_access_to_its_notes_master(self, request, notes_master_part_):
7785
notes_master_ = instance_mock(request, NotesMaster)

0 commit comments

Comments
 (0)