python-pptx2 2.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pptx2/__init__.py +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
pptx2/oxml/slide.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
"""Slide-related custom element classes, including those for masters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Callable, cast
|
|
6
|
+
|
|
7
|
+
from pptx2.oxml import parse_from_template, parse_xml
|
|
8
|
+
from pptx2.oxml.dml.fill import CT_GradientFillProperties
|
|
9
|
+
from pptx2.oxml.ns import nsdecls
|
|
10
|
+
from pptx2.oxml.simpletypes import XsdBoolean, XsdString, XsdUnsignedInt
|
|
11
|
+
from pptx2.oxml.xmlchemy import (
|
|
12
|
+
BaseOxmlElement,
|
|
13
|
+
Choice,
|
|
14
|
+
OneAndOnlyOne,
|
|
15
|
+
OptionalAttribute,
|
|
16
|
+
RequiredAttribute,
|
|
17
|
+
ZeroOrMore,
|
|
18
|
+
ZeroOrOne,
|
|
19
|
+
ZeroOrOneChoice,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from pptx2.oxml.shapes.groupshape import CT_GroupShape
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _BaseSlideElement(BaseOxmlElement):
|
|
27
|
+
"""Base class for the six slide types, providing common methods."""
|
|
28
|
+
|
|
29
|
+
cSld: CT_CommonSlideData
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def spTree(self) -> CT_GroupShape:
|
|
33
|
+
"""Return required `p:cSld/p:spTree` grandchild."""
|
|
34
|
+
return self.cSld.spTree
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CT_Background(BaseOxmlElement):
|
|
38
|
+
"""`p:bg` element."""
|
|
39
|
+
|
|
40
|
+
_insert_bgPr: Callable[[CT_BackgroundProperties], None]
|
|
41
|
+
|
|
42
|
+
# ---these two are actually a choice, not a sequence, but simpler for
|
|
43
|
+
# ---present purposes this way.
|
|
44
|
+
_tag_seq = ("p:bgPr", "p:bgRef")
|
|
45
|
+
bgPr: CT_BackgroundProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
46
|
+
"p:bgPr", successors=()
|
|
47
|
+
)
|
|
48
|
+
bgRef = ZeroOrOne("p:bgRef", successors=())
|
|
49
|
+
del _tag_seq
|
|
50
|
+
|
|
51
|
+
def add_noFill_bgPr(self):
|
|
52
|
+
"""Return a new `p:bgPr` element with noFill properties."""
|
|
53
|
+
xml = "<p:bgPr %s>\n" " <a:noFill/>\n" " <a:effectLst/>\n" "</p:bgPr>" % nsdecls("a", "p")
|
|
54
|
+
bgPr = cast(CT_BackgroundProperties, parse_xml(xml))
|
|
55
|
+
self._insert_bgPr(bgPr)
|
|
56
|
+
return bgPr
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CT_BackgroundProperties(BaseOxmlElement):
|
|
60
|
+
"""`p:bgPr` element."""
|
|
61
|
+
|
|
62
|
+
_tag_seq = (
|
|
63
|
+
"a:noFill",
|
|
64
|
+
"a:solidFill",
|
|
65
|
+
"a:gradFill",
|
|
66
|
+
"a:blipFill",
|
|
67
|
+
"a:pattFill",
|
|
68
|
+
"a:grpFill",
|
|
69
|
+
"a:effectLst",
|
|
70
|
+
"a:effectDag",
|
|
71
|
+
"a:extLst",
|
|
72
|
+
)
|
|
73
|
+
eg_fillProperties = ZeroOrOneChoice(
|
|
74
|
+
(
|
|
75
|
+
Choice("a:noFill"),
|
|
76
|
+
Choice("a:solidFill"),
|
|
77
|
+
Choice("a:gradFill"),
|
|
78
|
+
Choice("a:blipFill"),
|
|
79
|
+
Choice("a:pattFill"),
|
|
80
|
+
Choice("a:grpFill"),
|
|
81
|
+
),
|
|
82
|
+
successors=_tag_seq[6:],
|
|
83
|
+
)
|
|
84
|
+
del _tag_seq
|
|
85
|
+
|
|
86
|
+
def _new_gradFill(self):
|
|
87
|
+
"""Override default to add default gradient subtree."""
|
|
88
|
+
return CT_GradientFillProperties.new_gradFill()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CT_CommonSlideData(BaseOxmlElement):
|
|
92
|
+
"""`p:cSld` element."""
|
|
93
|
+
|
|
94
|
+
_remove_bg: Callable[[], None]
|
|
95
|
+
get_or_add_bg: Callable[[], CT_Background]
|
|
96
|
+
|
|
97
|
+
_tag_seq = ("p:bg", "p:spTree", "p:custDataLst", "p:controls", "p:extLst")
|
|
98
|
+
bg: CT_Background | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
99
|
+
"p:bg", successors=_tag_seq[1:]
|
|
100
|
+
)
|
|
101
|
+
spTree: CT_GroupShape = OneAndOnlyOne("p:spTree") # pyright: ignore[reportAssignmentType]
|
|
102
|
+
del _tag_seq
|
|
103
|
+
name: str = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
104
|
+
"name", XsdString, default=""
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def get_or_add_bgPr(self) -> CT_BackgroundProperties:
|
|
108
|
+
"""Return `p:bg/p:bgPr` grandchild.
|
|
109
|
+
|
|
110
|
+
If no such grandchild is present, any existing `p:bg` child is first removed and a new
|
|
111
|
+
default `p:bg` with noFill settings is added.
|
|
112
|
+
"""
|
|
113
|
+
bg = self.bg
|
|
114
|
+
if bg is None or bg.bgPr is None:
|
|
115
|
+
bg = self._change_to_noFill_bg()
|
|
116
|
+
return cast(CT_BackgroundProperties, bg.bgPr)
|
|
117
|
+
|
|
118
|
+
def _change_to_noFill_bg(self) -> CT_Background:
|
|
119
|
+
"""Establish a `p:bg` child with no-fill settings.
|
|
120
|
+
|
|
121
|
+
Any existing `p:bg` child is first removed.
|
|
122
|
+
"""
|
|
123
|
+
self._remove_bg()
|
|
124
|
+
bg = self.get_or_add_bg()
|
|
125
|
+
bg.add_noFill_bgPr()
|
|
126
|
+
return bg
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class CT_NotesMaster(_BaseSlideElement):
|
|
130
|
+
"""`p:notesMaster` element, root of a notes master part."""
|
|
131
|
+
|
|
132
|
+
_tag_seq = ("p:cSld", "p:clrMap", "p:hf", "p:notesStyle", "p:extLst")
|
|
133
|
+
cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
|
|
134
|
+
del _tag_seq
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def new_default(cls) -> CT_NotesMaster:
|
|
138
|
+
"""Return a new `p:notesMaster` element based on the built-in default template."""
|
|
139
|
+
return cast(CT_NotesMaster, parse_from_template("notesMaster"))
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class CT_NotesSlide(_BaseSlideElement):
|
|
143
|
+
"""`p:notes` element, root of a notes slide part."""
|
|
144
|
+
|
|
145
|
+
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:extLst")
|
|
146
|
+
cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
|
|
147
|
+
del _tag_seq
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def new(cls) -> CT_NotesSlide:
|
|
151
|
+
"""Return a new ``<p:notes>`` element based on the default template.
|
|
152
|
+
|
|
153
|
+
Note that the template does not include placeholders, which must be subsequently cloned
|
|
154
|
+
from the notes master.
|
|
155
|
+
"""
|
|
156
|
+
return cast(CT_NotesSlide, parse_from_template("notes"))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class CT_Slide(_BaseSlideElement):
|
|
160
|
+
"""`p:sld` element, root element of a slide part (XML document)."""
|
|
161
|
+
|
|
162
|
+
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:transition", "p:timing", "p:extLst")
|
|
163
|
+
cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
|
|
164
|
+
clrMapOvr = ZeroOrOne("p:clrMapOvr", successors=_tag_seq[2:])
|
|
165
|
+
transition: CT_SlideTransition | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
166
|
+
"p:transition", successors=_tag_seq[3:]
|
|
167
|
+
)
|
|
168
|
+
timing = ZeroOrOne("p:timing", successors=_tag_seq[4:])
|
|
169
|
+
del _tag_seq
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def new(cls) -> CT_Slide:
|
|
173
|
+
"""Return new `p:sld` element configured as base slide shape."""
|
|
174
|
+
return cast(CT_Slide, parse_xml(cls._sld_xml()))
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def bg(self):
|
|
178
|
+
"""Return `p:bg` grandchild or None if not present."""
|
|
179
|
+
return self.cSld.bg
|
|
180
|
+
|
|
181
|
+
def get_or_add_childTnLst(self):
|
|
182
|
+
"""Return parent element for a new `p:video` child element.
|
|
183
|
+
|
|
184
|
+
The `p:video` element causes play controls to appear under a video
|
|
185
|
+
shape (pic shape containing video). There can be more than one video
|
|
186
|
+
shape on a slide, which causes the precondition to vary. It needs to
|
|
187
|
+
handle the case when there is no `p:sld/p:timing` element and when
|
|
188
|
+
that element already exists. If the case isn't simple, it just nukes
|
|
189
|
+
what's there and adds a fresh one. This could theoretically remove
|
|
190
|
+
desired existing timing information, but there isn't any evidence
|
|
191
|
+
available to me one way or the other, so I've taken the simple
|
|
192
|
+
approach.
|
|
193
|
+
"""
|
|
194
|
+
childTnLst = self._childTnLst
|
|
195
|
+
if childTnLst is None:
|
|
196
|
+
childTnLst = self._add_childTnLst()
|
|
197
|
+
return childTnLst
|
|
198
|
+
|
|
199
|
+
def _add_childTnLst(self):
|
|
200
|
+
"""Add `./p:timing/p:tnLst/p:par/p:cTn/p:childTnLst` descendant.
|
|
201
|
+
|
|
202
|
+
Any existing `p:timing` child element is ruthlessly removed and
|
|
203
|
+
replaced.
|
|
204
|
+
"""
|
|
205
|
+
self.remove(self.get_or_add_timing())
|
|
206
|
+
timing = parse_xml(self._childTnLst_timing_xml())
|
|
207
|
+
self._insert_timing(timing)
|
|
208
|
+
return timing.xpath("./p:tnLst/p:par/p:cTn/p:childTnLst")[0]
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def _childTnLst(self):
|
|
212
|
+
"""Return `./p:timing/p:tnLst/p:par/p:cTn/p:childTnLst` descendant.
|
|
213
|
+
|
|
214
|
+
Return None if that element is not present.
|
|
215
|
+
"""
|
|
216
|
+
childTnLsts = self.xpath("./p:timing/p:tnLst/p:par/p:cTn/p:childTnLst")
|
|
217
|
+
if not childTnLsts:
|
|
218
|
+
return None
|
|
219
|
+
return childTnLsts[0]
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def _childTnLst_timing_xml():
|
|
223
|
+
return (
|
|
224
|
+
"<p:timing %s>\n"
|
|
225
|
+
" <p:tnLst>\n"
|
|
226
|
+
" <p:par>\n"
|
|
227
|
+
' <p:cTn id="1" dur="indefinite" restart="never" nodeType="'
|
|
228
|
+
'tmRoot">\n'
|
|
229
|
+
" <p:childTnLst/>\n"
|
|
230
|
+
" </p:cTn>\n"
|
|
231
|
+
" </p:par>\n"
|
|
232
|
+
" </p:tnLst>\n"
|
|
233
|
+
"</p:timing>" % nsdecls("p")
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def _sld_xml():
|
|
238
|
+
return (
|
|
239
|
+
"<p:sld %s>\n"
|
|
240
|
+
" <p:cSld>\n"
|
|
241
|
+
" <p:spTree>\n"
|
|
242
|
+
" <p:nvGrpSpPr>\n"
|
|
243
|
+
' <p:cNvPr id="1" name=""/>\n'
|
|
244
|
+
" <p:cNvGrpSpPr/>\n"
|
|
245
|
+
" <p:nvPr/>\n"
|
|
246
|
+
" </p:nvGrpSpPr>\n"
|
|
247
|
+
" <p:grpSpPr/>\n"
|
|
248
|
+
" </p:spTree>\n"
|
|
249
|
+
" </p:cSld>\n"
|
|
250
|
+
" <p:clrMapOvr>\n"
|
|
251
|
+
" <a:masterClrMapping/>\n"
|
|
252
|
+
" </p:clrMapOvr>\n"
|
|
253
|
+
"</p:sld>" % nsdecls("a", "p", "r")
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class CT_SlideLayout(_BaseSlideElement):
|
|
258
|
+
"""`p:sldLayout` element, root of a slide layout part."""
|
|
259
|
+
|
|
260
|
+
_tag_seq = ("p:cSld", "p:clrMapOvr", "p:transition", "p:timing", "p:hf", "p:extLst")
|
|
261
|
+
cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
|
|
262
|
+
del _tag_seq
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class CT_SlideLayoutIdList(BaseOxmlElement):
|
|
266
|
+
"""`p:sldLayoutIdLst` element, child of `p:sldMaster`.
|
|
267
|
+
|
|
268
|
+
Contains references to the slide layouts that inherit from the slide master.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
sldLayoutId_lst: list[CT_SlideLayoutIdListEntry]
|
|
272
|
+
|
|
273
|
+
sldLayoutId = ZeroOrMore("p:sldLayoutId")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class CT_SlideLayoutIdListEntry(BaseOxmlElement):
|
|
277
|
+
"""`p:sldLayoutId` element, child of `p:sldLayoutIdLst`.
|
|
278
|
+
|
|
279
|
+
Contains a reference to a slide layout.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class CT_SlideMaster(_BaseSlideElement):
|
|
286
|
+
"""`p:sldMaster` element, root of a slide master part."""
|
|
287
|
+
|
|
288
|
+
get_or_add_sldLayoutIdLst: Callable[[], CT_SlideLayoutIdList]
|
|
289
|
+
|
|
290
|
+
_tag_seq = (
|
|
291
|
+
"p:cSld",
|
|
292
|
+
"p:clrMap",
|
|
293
|
+
"p:sldLayoutIdLst",
|
|
294
|
+
"p:transition",
|
|
295
|
+
"p:timing",
|
|
296
|
+
"p:hf",
|
|
297
|
+
"p:txStyles",
|
|
298
|
+
"p:extLst",
|
|
299
|
+
)
|
|
300
|
+
cSld: CT_CommonSlideData = OneAndOnlyOne("p:cSld") # pyright: ignore[reportAssignmentType]
|
|
301
|
+
sldLayoutIdLst: CT_SlideLayoutIdList = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
302
|
+
"p:sldLayoutIdLst", successors=_tag_seq[3:]
|
|
303
|
+
)
|
|
304
|
+
del _tag_seq
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class CT_SlideTransition(BaseOxmlElement):
|
|
308
|
+
"""`p:transition` element, specifying the transition into a slide.
|
|
309
|
+
|
|
310
|
+
The transition kind is encoded by which child element is present (e.g.
|
|
311
|
+
``<p:fade/>``, ``<p:wipe/>``, ``<p14:morph/>``); ``<p:transition>`` may
|
|
312
|
+
have at most one such child. Attributes control timing:
|
|
313
|
+
|
|
314
|
+
* ``spd`` – legacy speed bucket (``slow``, ``med``, ``fast``).
|
|
315
|
+
* ``advClick`` – whether the slide advances on mouse click. Default is
|
|
316
|
+
``True`` if the attribute is absent; setting it to ``False`` is the
|
|
317
|
+
common case for kiosk-style auto-advance.
|
|
318
|
+
* ``advTm`` – auto-advance time, in milliseconds.
|
|
319
|
+
"""
|
|
320
|
+
|
|
321
|
+
spd: str | None = OptionalAttribute("spd", XsdString) # pyright: ignore[reportAssignmentType]
|
|
322
|
+
advClick: bool | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
323
|
+
"advClick", XsdBoolean
|
|
324
|
+
)
|
|
325
|
+
advTm: int | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
|
|
326
|
+
"advTm", XsdUnsignedInt
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def kind_element(self):
|
|
331
|
+
"""The single transition-kind child element, or |None| if not set.
|
|
332
|
+
|
|
333
|
+
The transition-kind element is the first child whose tag is not a
|
|
334
|
+
sound-action or extension-list element; that gives us the right
|
|
335
|
+
answer for both standard ``p:`` transitions and PowerPoint-2010+
|
|
336
|
+
``p14:`` transitions without enumerating every known kind.
|
|
337
|
+
"""
|
|
338
|
+
for child in self:
|
|
339
|
+
local = child.tag.rsplit("}", 1)[-1]
|
|
340
|
+
if local in ("sndAc", "extLst"):
|
|
341
|
+
continue
|
|
342
|
+
return child
|
|
343
|
+
return None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class CT_SlideTiming(BaseOxmlElement):
|
|
347
|
+
"""`p:timing` element, specifying animations and timed behaviors."""
|
|
348
|
+
|
|
349
|
+
_tag_seq = ("p:tnLst", "p:bldLst", "p:extLst")
|
|
350
|
+
tnLst = ZeroOrOne("p:tnLst", successors=_tag_seq[1:])
|
|
351
|
+
del _tag_seq
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class CT_TimeNodeList(BaseOxmlElement):
|
|
355
|
+
"""`p:tnLst` or `p:childTnList` element."""
|
|
356
|
+
|
|
357
|
+
def add_video(self, shape_id):
|
|
358
|
+
"""Add a new `p:video` child element for movie having *shape_id*."""
|
|
359
|
+
video_xml = (
|
|
360
|
+
"<p:video %s>\n"
|
|
361
|
+
' <p:cMediaNode vol="80000">\n'
|
|
362
|
+
' <p:cTn id="%d" fill="hold" display="0">\n'
|
|
363
|
+
" <p:stCondLst>\n"
|
|
364
|
+
' <p:cond delay="indefinite"/>\n'
|
|
365
|
+
" </p:stCondLst>\n"
|
|
366
|
+
" </p:cTn>\n"
|
|
367
|
+
" <p:tgtEl>\n"
|
|
368
|
+
' <p:spTgt spid="%d"/>\n'
|
|
369
|
+
" </p:tgtEl>\n"
|
|
370
|
+
" </p:cMediaNode>\n"
|
|
371
|
+
"</p:video>\n" % (nsdecls("p"), self._next_cTn_id, shape_id)
|
|
372
|
+
)
|
|
373
|
+
video = parse_xml(video_xml)
|
|
374
|
+
self.append(video)
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def _next_cTn_id(self):
|
|
378
|
+
"""Return the next available unique ID (int) for p:cTn element."""
|
|
379
|
+
cTn_id_strs = self.xpath("/p:sld/p:timing//p:cTn/@id")
|
|
380
|
+
ids = [int(id_str) for id_str in cTn_id_strs]
|
|
381
|
+
return max(ids) + 1
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class CT_TLMediaNodeVideo(BaseOxmlElement):
|
|
385
|
+
"""`p:video` element, specifying video media details."""
|
|
386
|
+
|
|
387
|
+
_tag_seq = ("p:cMediaNode",)
|
|
388
|
+
cMediaNode = OneAndOnlyOne("p:cMediaNode")
|
|
389
|
+
del _tag_seq
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ---------------------------------------------------------------------------
|
|
393
|
+
# Markup-Compatibility (mc:AlternateContent) wrapping for p14 transitions
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
#
|
|
396
|
+
# PowerPoint-2010+ transitions (morph, vortex, switch, …) live in the ``p14``
|
|
397
|
+
# namespace. ``CT_SlideTransition`` (the schema for ``<p:transition>``) only
|
|
398
|
+
# admits the standard ``p:`` kind elements in its choice, so a bare
|
|
399
|
+
# ``<p14:morph/>`` child is schema-invalid and Microsoft PowerPoint reports the
|
|
400
|
+
# deck as needing repair. PowerPoint instead wraps the whole ``<p:transition>``
|
|
401
|
+
# in ``<mc:AlternateContent>``: an ``<mc:Choice Requires="p14">`` carrying the
|
|
402
|
+
# p14 transition, plus an ``<mc:Fallback>`` carrying a plain (kind-less)
|
|
403
|
+
# ``<p:transition>`` for pre-2010 viewers.
|
|
404
|
+
#
|
|
405
|
+
# We keep the in-memory model as a plain ``<p:transition>`` (so the
|
|
406
|
+
# ``slide.transition`` accessors stay simple) and translate to/from the
|
|
407
|
+
# AlternateContent form only at the serialization boundary — see
|
|
408
|
+
# ``pptx2.parts.slide.BaseSlidePart``.
|
|
409
|
+
|
|
410
|
+
_MC_URI = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
411
|
+
_P14_URI = "http://schemas.microsoft.com/office/powerpoint/2010/main"
|
|
412
|
+
_P159_URI = "http://schemas.microsoft.com/office/powerpoint/2015/09/main"
|
|
413
|
+
_P_URI = "http://schemas.openxmlformats.org/presentationml/2006/main"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _mc(tag: str) -> str:
|
|
417
|
+
return "{%s}%s" % (_MC_URI, tag)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _p_tag(tag: str) -> str:
|
|
421
|
+
return "{%s}%s" % (_P_URI, tag)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _transition_kind_child(transition):
|
|
425
|
+
"""Return the kind child of a ``<p:transition>`` (skipping sndAc/extLst)."""
|
|
426
|
+
for child in transition:
|
|
427
|
+
local = child.tag.rsplit("}", 1)[-1]
|
|
428
|
+
if local in ("sndAc", "extLst"):
|
|
429
|
+
continue
|
|
430
|
+
return child
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _extension_kind_uri(transition) -> "str | None":
|
|
435
|
+
"""The Microsoft-extension namespace URI of the transition kind, or |None|.
|
|
436
|
+
|
|
437
|
+
Returns ``_P159_URI`` for a 2016+ kind (morph), ``_P14_URI`` for a 2010+
|
|
438
|
+
kind (flythrough, vortex, …), and |None| for a classic ``p:`` kind or a
|
|
439
|
+
kind-less transition.
|
|
440
|
+
"""
|
|
441
|
+
kind = _transition_kind_child(transition)
|
|
442
|
+
if kind is None:
|
|
443
|
+
return None
|
|
444
|
+
for uri in (_P159_URI, _P14_URI):
|
|
445
|
+
if kind.tag.startswith("{%s}" % uri):
|
|
446
|
+
return uri
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _has_p14_attr(transition) -> bool:
|
|
451
|
+
"""True when *transition* carries any PowerPoint-2010 (p14) attribute.
|
|
452
|
+
|
|
453
|
+
The most common one is ``p14:dur`` (millisecond duration). Like the
|
|
454
|
+
extension *kind* elements, a p14 attribute is only schema-valid inside an
|
|
455
|
+
``<mc:Choice>`` — a bare ``<p:transition p14:dur="…">`` is rejected by
|
|
456
|
+
Microsoft PowerPoint (it reports the deck as needing repair).
|
|
457
|
+
"""
|
|
458
|
+
p14 = "{%s}" % _P14_URI
|
|
459
|
+
return any(name.startswith(p14) for name in transition.attrib)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _needs_mc_wrap(transition) -> bool:
|
|
463
|
+
"""True when *transition* must be wrapped in ``<mc:AlternateContent>``.
|
|
464
|
+
|
|
465
|
+
That is whenever it holds *any* extension content — a p14/p159 kind child
|
|
466
|
+
(flythrough, morph, …) or a p14 attribute (e.g. ``p14:dur``).
|
|
467
|
+
"""
|
|
468
|
+
return _extension_kind_uri(transition) is not None or _has_p14_attr(transition)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def slide_has_p14_transition(root) -> bool:
|
|
472
|
+
"""True when *root* (sld/sldLayout/sldMaster) holds a transition needing the
|
|
473
|
+
``mc:AlternateContent`` wrapper (a p14 kind child or a p14 attribute)."""
|
|
474
|
+
return any(_needs_mc_wrap(t) for t in root.findall(_p_tag("transition")))
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def wrap_p14_transitions(root) -> None:
|
|
478
|
+
"""In-place: wrap each bare p14 ``<p:transition>`` in ``<mc:AlternateContent>``.
|
|
479
|
+
|
|
480
|
+
Called on a *copy* of the element at serialization time so the live tree
|
|
481
|
+
keeps its plain ``<p:transition>`` for the high-level accessors.
|
|
482
|
+
"""
|
|
483
|
+
import copy
|
|
484
|
+
|
|
485
|
+
from lxml import etree
|
|
486
|
+
|
|
487
|
+
p14 = "{%s}" % _P14_URI
|
|
488
|
+
for transition in list(root.findall(_p_tag("transition"))):
|
|
489
|
+
if not _needs_mc_wrap(transition):
|
|
490
|
+
continue
|
|
491
|
+
idx = list(root).index(transition)
|
|
492
|
+
|
|
493
|
+
# Heal decks written by earlier python-pptx2 releases (and any other
|
|
494
|
+
# producer) that emitted morph in the p14 namespace: retag to p159 so
|
|
495
|
+
# a plain load → save round-trip repairs the file.
|
|
496
|
+
legacy_morph = _transition_kind_child(transition)
|
|
497
|
+
if legacy_morph is not None and legacy_morph.tag == "{%s}morph" % _P14_URI:
|
|
498
|
+
healed = etree.Element("{%s}morph" % _P159_URI, nsmap={"p159": _P159_URI})
|
|
499
|
+
for attr_name, attr_value in legacy_morph.attrib.items():
|
|
500
|
+
healed.set(attr_name, attr_value)
|
|
501
|
+
transition.replace(legacy_morph, healed)
|
|
502
|
+
|
|
503
|
+
ext_uri = _extension_kind_uri(transition)
|
|
504
|
+
|
|
505
|
+
# Morph is a 2015/09 (p159) element per MS-PPTX, so its Choice must
|
|
506
|
+
# require "p159" — every modern PowerPoint understands p14, and an
|
|
507
|
+
# undefined `p14:morph` inside the selected branch is exactly the
|
|
508
|
+
# repair-dialog class of failure. The 2010 kinds (and a p14
|
|
509
|
+
# attribute on a classic kind) require "p14".
|
|
510
|
+
if ext_uri == _P159_URI:
|
|
511
|
+
requires, choice_nsmap = "p159", {"p159": _P159_URI, "p14": _P14_URI}
|
|
512
|
+
else:
|
|
513
|
+
requires, choice_nsmap = "p14", {"p14": _P14_URI}
|
|
514
|
+
|
|
515
|
+
ac = etree.Element(_mc("AlternateContent"), nsmap={"mc": _MC_URI})
|
|
516
|
+
choice = etree.SubElement(ac, _mc("Choice"), nsmap=choice_nsmap)
|
|
517
|
+
choice.set("Requires", requires)
|
|
518
|
+
|
|
519
|
+
# Match PowerPoint: <p159:morph option="byObject"/> when option is unset.
|
|
520
|
+
kind = _transition_kind_child(transition)
|
|
521
|
+
if kind is not None and kind.tag == "{%s}morph" % _P159_URI and kind.get("option") is None:
|
|
522
|
+
kind.set("option", "byObject")
|
|
523
|
+
|
|
524
|
+
# Fallback transition keeps the non-p14 timing attributes. When the
|
|
525
|
+
# kind is a *classic* (p:) element — e.g. a fade carrying only a p14:dur
|
|
526
|
+
# — preserve it in the fallback so pre-2010 viewers still get the
|
|
527
|
+
# transition. Extension kinds (morph, vortex, …) have no classic
|
|
528
|
+
# equivalent; PowerPoint's own fallback for them is a plain fade, and
|
|
529
|
+
# an extension element must never leak into the ISO-pure fallback.
|
|
530
|
+
fallback = etree.SubElement(ac, _mc("Fallback"))
|
|
531
|
+
fb = etree.SubElement(fallback, _p_tag("transition"))
|
|
532
|
+
for name, value in transition.attrib.items():
|
|
533
|
+
if not name.startswith(p14):
|
|
534
|
+
fb.set(name, value)
|
|
535
|
+
if kind is not None:
|
|
536
|
+
if ext_uri is None:
|
|
537
|
+
fb.append(copy.deepcopy(kind))
|
|
538
|
+
else:
|
|
539
|
+
etree.SubElement(fb, _p_tag("fade"))
|
|
540
|
+
|
|
541
|
+
# Move the original extension transition under <mc:Choice> and drop
|
|
542
|
+
# it into the tree where it used to live.
|
|
543
|
+
choice.append(transition)
|
|
544
|
+
root.insert(idx, ac)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def unwrap_p14_transitions(root) -> None:
|
|
548
|
+
"""In-place inverse of :func:`wrap_p14_transitions` (called on load).
|
|
549
|
+
|
|
550
|
+
Replaces any ``<mc:AlternateContent>`` direct child of *root* that wraps a
|
|
551
|
+
transition with the plain ``<p:transition>`` from its ``<mc:Choice>`` so the
|
|
552
|
+
high-level accessors see a normal transition element.
|
|
553
|
+
"""
|
|
554
|
+
for ac in list(root.findall(_mc("AlternateContent"))):
|
|
555
|
+
choice = ac.find(_mc("Choice"))
|
|
556
|
+
if choice is None:
|
|
557
|
+
continue
|
|
558
|
+
transition = choice.find(_p_tag("transition"))
|
|
559
|
+
if transition is None:
|
|
560
|
+
continue
|
|
561
|
+
idx = list(root).index(ac)
|
|
562
|
+
root.remove(ac)
|
|
563
|
+
root.insert(idx, transition)
|