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.
Files changed (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. 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)