ps-python-pptx 1.1.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 (117) hide show
  1. pptx/__init__.py +84 -0
  2. pptx/action.py +270 -0
  3. pptx/api.py +49 -0
  4. pptx/chart/__init__.py +0 -0
  5. pptx/chart/axis.py +523 -0
  6. pptx/chart/category.py +200 -0
  7. pptx/chart/chart.py +280 -0
  8. pptx/chart/data.py +864 -0
  9. pptx/chart/datalabel.py +288 -0
  10. pptx/chart/legend.py +79 -0
  11. pptx/chart/marker.py +70 -0
  12. pptx/chart/plot.py +412 -0
  13. pptx/chart/point.py +101 -0
  14. pptx/chart/series.py +258 -0
  15. pptx/chart/xlsx.py +272 -0
  16. pptx/chart/xmlwriter.py +1840 -0
  17. pptx/dml/__init__.py +0 -0
  18. pptx/dml/chtfmt.py +40 -0
  19. pptx/dml/color.py +301 -0
  20. pptx/dml/effect.py +41 -0
  21. pptx/dml/fill.py +468 -0
  22. pptx/dml/line.py +100 -0
  23. pptx/enum/__init__.py +0 -0
  24. pptx/enum/action.py +71 -0
  25. pptx/enum/base.py +175 -0
  26. pptx/enum/chart.py +492 -0
  27. pptx/enum/dml.py +405 -0
  28. pptx/enum/lang.py +685 -0
  29. pptx/enum/shapes.py +1029 -0
  30. pptx/enum/text.py +371 -0
  31. pptx/exc.py +23 -0
  32. pptx/media.py +197 -0
  33. pptx/opc/__init__.py +0 -0
  34. pptx/opc/constants.py +331 -0
  35. pptx/opc/oxml.py +188 -0
  36. pptx/opc/package.py +762 -0
  37. pptx/opc/packuri.py +109 -0
  38. pptx/opc/serialized.py +296 -0
  39. pptx/opc/shared.py +20 -0
  40. pptx/opc/spec.py +44 -0
  41. pptx/oxml/__init__.py +492 -0
  42. pptx/oxml/action.py +53 -0
  43. pptx/oxml/chart/__init__.py +0 -0
  44. pptx/oxml/chart/axis.py +297 -0
  45. pptx/oxml/chart/chart.py +282 -0
  46. pptx/oxml/chart/datalabel.py +252 -0
  47. pptx/oxml/chart/legend.py +72 -0
  48. pptx/oxml/chart/marker.py +61 -0
  49. pptx/oxml/chart/plot.py +345 -0
  50. pptx/oxml/chart/series.py +254 -0
  51. pptx/oxml/chart/shared.py +219 -0
  52. pptx/oxml/coreprops.py +288 -0
  53. pptx/oxml/dml/__init__.py +0 -0
  54. pptx/oxml/dml/color.py +111 -0
  55. pptx/oxml/dml/fill.py +197 -0
  56. pptx/oxml/dml/line.py +12 -0
  57. pptx/oxml/ns.py +129 -0
  58. pptx/oxml/presentation.py +130 -0
  59. pptx/oxml/shapes/__init__.py +19 -0
  60. pptx/oxml/shapes/autoshape.py +455 -0
  61. pptx/oxml/shapes/connector.py +107 -0
  62. pptx/oxml/shapes/graphfrm.py +342 -0
  63. pptx/oxml/shapes/groupshape.py +280 -0
  64. pptx/oxml/shapes/picture.py +270 -0
  65. pptx/oxml/shapes/shared.py +523 -0
  66. pptx/oxml/simpletypes.py +740 -0
  67. pptx/oxml/slide.py +347 -0
  68. pptx/oxml/table.py +588 -0
  69. pptx/oxml/text.py +748 -0
  70. pptx/oxml/theme.py +29 -0
  71. pptx/oxml/xmlchemy.py +717 -0
  72. pptx/package.py +222 -0
  73. pptx/parts/__init__.py +0 -0
  74. pptx/parts/chart.py +95 -0
  75. pptx/parts/coreprops.py +167 -0
  76. pptx/parts/embeddedpackage.py +93 -0
  77. pptx/parts/image.py +275 -0
  78. pptx/parts/media.py +37 -0
  79. pptx/parts/presentation.py +126 -0
  80. pptx/parts/slide.py +297 -0
  81. pptx/parts/theme.py +13 -0
  82. pptx/presentation.py +113 -0
  83. pptx/py.typed +0 -0
  84. pptx/shapes/__init__.py +26 -0
  85. pptx/shapes/autoshape.py +355 -0
  86. pptx/shapes/base.py +293 -0
  87. pptx/shapes/connector.py +297 -0
  88. pptx/shapes/freeform.py +337 -0
  89. pptx/shapes/graphfrm.py +166 -0
  90. pptx/shapes/group.py +69 -0
  91. pptx/shapes/picture.py +203 -0
  92. pptx/shapes/placeholder.py +407 -0
  93. pptx/shapes/shapetree.py +1190 -0
  94. pptx/shared.py +82 -0
  95. pptx/slide.py +577 -0
  96. pptx/spec.py +632 -0
  97. pptx/table.py +520 -0
  98. pptx/templates/default.pptx +0 -0
  99. pptx/templates/docx-icon.emf +0 -0
  100. pptx/templates/generic-icon.emf +0 -0
  101. pptx/templates/notes.xml +23 -0
  102. pptx/templates/notesMaster.xml +352 -0
  103. pptx/templates/pptx-icon.emf +0 -0
  104. pptx/templates/theme.xml +321 -0
  105. pptx/templates/xlsx-icon.emf +0 -0
  106. pptx/text/__init__.py +0 -0
  107. pptx/text/fonts.py +399 -0
  108. pptx/text/layout.py +325 -0
  109. pptx/text/text.py +770 -0
  110. pptx/theme.py +135 -0
  111. pptx/types.py +36 -0
  112. pptx/util.py +284 -0
  113. ps_python_pptx-1.1.0.dist-info/METADATA +75 -0
  114. ps_python_pptx-1.1.0.dist-info/RECORD +117 -0
  115. ps_python_pptx-1.1.0.dist-info/WHEEL +5 -0
  116. ps_python_pptx-1.1.0.dist-info/licenses/LICENSE +20 -0
  117. ps_python_pptx-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,342 @@
1
+ """lxml custom element class for CT_GraphicalObjectFrame XML element."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, cast
6
+
7
+ from pptx.oxml import parse_xml
8
+ from pptx.oxml.chart.chart import CT_Chart
9
+ from pptx.oxml.ns import nsdecls
10
+ from pptx.oxml.shapes.shared import BaseShapeElement
11
+ from pptx.oxml.simpletypes import XsdBoolean, XsdString
12
+ from pptx.oxml.table import CT_Table
13
+ from pptx.oxml.xmlchemy import (
14
+ BaseOxmlElement,
15
+ OneAndOnlyOne,
16
+ OptionalAttribute,
17
+ RequiredAttribute,
18
+ ZeroOrOne,
19
+ )
20
+ from pptx.spec import (
21
+ GRAPHIC_DATA_URI_CHART,
22
+ GRAPHIC_DATA_URI_OLEOBJ,
23
+ GRAPHIC_DATA_URI_TABLE,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from pptx.oxml.shapes.shared import (
28
+ CT_ApplicationNonVisualDrawingProps,
29
+ CT_NonVisualDrawingProps,
30
+ CT_Transform2D,
31
+ )
32
+
33
+
34
+ class CT_GraphicalObject(BaseOxmlElement):
35
+ """`a:graphic` element.
36
+
37
+ The container for the reference to or definition of the framed graphical object (table, chart,
38
+ etc.).
39
+ """
40
+
41
+ graphicData: CT_GraphicalObjectData = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
42
+ "a:graphicData"
43
+ )
44
+
45
+ @property
46
+ def chart(self) -> CT_Chart | None:
47
+ """The `c:chart` grandchild element, or |None| if not present."""
48
+ return self.graphicData.chart
49
+
50
+
51
+ class CT_GraphicalObjectData(BaseShapeElement):
52
+ """`p:graphicData` element.
53
+
54
+ The direct container for a table, a chart, or another graphical object.
55
+ """
56
+
57
+ chart: CT_Chart | None = ZeroOrOne("c:chart") # pyright: ignore[reportAssignmentType]
58
+ tbl: CT_Table | None = ZeroOrOne("a:tbl") # pyright: ignore[reportAssignmentType]
59
+ uri: str = RequiredAttribute("uri", XsdString) # pyright: ignore[reportAssignmentType]
60
+
61
+ @property
62
+ def blob_rId(self) -> str | None:
63
+ """Optional `r:id` attribute value of `p:oleObj` descendent element.
64
+
65
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object.
66
+ This value could also be `None` if an enclosed OLE object does not specify this attribute
67
+ (it is specified optional in the schema) but so far, all OLE objects we've encountered
68
+ specify this value.
69
+ """
70
+ return None if self._oleObj is None else self._oleObj.rId
71
+
72
+ @property
73
+ def is_embedded_ole_obj(self) -> bool | None:
74
+ """Optional boolean indicating an embedded OLE object.
75
+
76
+ Returns `None` when this `p:graphicData` element does not enclose an OLE object. `True`
77
+ indicates an embedded OLE object and `False` indicates a linked OLE object.
78
+ """
79
+ return None if self._oleObj is None else self._oleObj.is_embedded
80
+
81
+ @property
82
+ def progId(self) -> str | None:
83
+ """Optional str value of "progId" attribute of `p:oleObj` descendent.
84
+
85
+ This value identifies the "type" of the embedded object in terms of the application used
86
+ to open it.
87
+
88
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object.
89
+ This could also be `None` if an enclosed OLE object does not specify this attribute (it is
90
+ specified optional in the schema) but so far, all OLE objects we've encountered specify
91
+ this value.
92
+ """
93
+ return None if self._oleObj is None else self._oleObj.progId
94
+
95
+ @property
96
+ def showAsIcon(self) -> bool | None:
97
+ """Optional value of "showAsIcon" attribute value of `p:oleObj` descendent.
98
+
99
+ This value is `None` when this `p:graphicData` element does not enclose an OLE object. It
100
+ is False when the `showAsIcon` attribute is omitted on the `p:oleObj` element.
101
+ """
102
+ return None if self._oleObj is None else self._oleObj.showAsIcon
103
+
104
+ @property
105
+ def _oleObj(self) -> CT_OleObject | None:
106
+ """Optional `p:oleObj` element contained in this `p:graphicData' element.
107
+
108
+ Returns `None` when this graphic-data element does not enclose an OLE object. Note that
109
+ this returns the last `p:oleObj` element found. There can be more than one `p:oleObj`
110
+ element because an `mc.AlternateContent` element may appear as the child of
111
+ `p:graphicData` and that alternate-content subtree can contain multiple compatibility
112
+ choices. The last one should suit best for reading purposes because it contains the lowest
113
+ common denominator.
114
+ """
115
+ oleObjs = cast("list[CT_OleObject]", self.xpath(".//p:oleObj"))
116
+ return oleObjs[-1] if oleObjs else None
117
+
118
+
119
+ class CT_GraphicalObjectFrame(BaseShapeElement):
120
+ """`p:graphicFrame` element.
121
+
122
+ A container for a table, a chart, or another graphical object.
123
+ """
124
+
125
+ nvGraphicFramePr: CT_GraphicalObjectFrameNonVisual = ( # pyright: ignore[reportAssignmentType]
126
+ OneAndOnlyOne("p:nvGraphicFramePr")
127
+ )
128
+ xfrm: CT_Transform2D = OneAndOnlyOne("p:xfrm") # pyright: ignore
129
+ graphic: CT_GraphicalObject = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
130
+ "a:graphic"
131
+ )
132
+
133
+ @property
134
+ def chart(self) -> CT_Chart | None:
135
+ """The `c:chart` great-grandchild element, or |None| if not present."""
136
+ return self.graphic.chart
137
+
138
+ @property
139
+ def chart_rId(self) -> str | None:
140
+ """The `rId` attribute of the `c:chart` great-grandchild element.
141
+
142
+ |None| if not present.
143
+ """
144
+ chart = self.chart
145
+ if chart is None:
146
+ return None
147
+ return chart.rId
148
+
149
+ def get_or_add_xfrm(self) -> CT_Transform2D:
150
+ """Return the required `p:xfrm` child element.
151
+
152
+ Overrides version on BaseShapeElement.
153
+ """
154
+ return self.xfrm
155
+
156
+ @property
157
+ def graphicData(self) -> CT_GraphicalObjectData:
158
+ """`a:graphicData` grandchild of this graphic-frame element."""
159
+ return self.graphic.graphicData
160
+
161
+ @property
162
+ def graphicData_uri(self) -> str:
163
+ """str value of `uri` attribute of `a:graphicData` grandchild."""
164
+ return self.graphic.graphicData.uri
165
+
166
+ @property
167
+ def has_oleobj(self) -> bool:
168
+ """`True` for graphicFrame containing an OLE object, `False` otherwise."""
169
+ return self.graphicData.uri == GRAPHIC_DATA_URI_OLEOBJ
170
+
171
+ @property
172
+ def is_embedded_ole_obj(self) -> bool | None:
173
+ """Optional boolean indicating an embedded OLE object.
174
+
175
+ Returns `None` when this `p:graphicFrame` element does not enclose an OLE object. `True`
176
+ indicates an embedded OLE object and `False` indicates a linked OLE object.
177
+ """
178
+ return self.graphicData.is_embedded_ole_obj
179
+
180
+ @classmethod
181
+ def new_chart_graphicFrame(
182
+ cls, id_: int, name: str, rId: str, x: int, y: int, cx: int, cy: int
183
+ ) -> CT_GraphicalObjectFrame:
184
+ """Return a `p:graphicFrame` element tree populated with a chart element."""
185
+ graphicFrame = CT_GraphicalObjectFrame.new_graphicFrame(id_, name, x, y, cx, cy)
186
+ graphicData = graphicFrame.graphic.graphicData
187
+ graphicData.uri = GRAPHIC_DATA_URI_CHART
188
+ graphicData.append(CT_Chart.new_chart(rId))
189
+ return graphicFrame
190
+
191
+ @classmethod
192
+ def new_graphicFrame(
193
+ cls, id_: int, name: str, x: int, y: int, cx: int, cy: int
194
+ ) -> CT_GraphicalObjectFrame:
195
+ """Return a new `p:graphicFrame` element tree suitable for containing a table or chart.
196
+
197
+ Note that a graphicFrame element is not a valid shape until it contains a graphical object
198
+ such as a table.
199
+ """
200
+ return cast(
201
+ CT_GraphicalObjectFrame,
202
+ parse_xml(
203
+ f"<p:graphicFrame {nsdecls('a', 'p')}>\n"
204
+ f" <p:nvGraphicFramePr>\n"
205
+ f' <p:cNvPr id="{id_}" name="{name}"/>\n'
206
+ f" <p:cNvGraphicFramePr>\n"
207
+ f' <a:graphicFrameLocks noGrp="1"/>\n'
208
+ f" </p:cNvGraphicFramePr>\n"
209
+ f" <p:nvPr/>\n"
210
+ f" </p:nvGraphicFramePr>\n"
211
+ f" <p:xfrm>\n"
212
+ f' <a:off x="{x}" y="{y}"/>\n'
213
+ f' <a:ext cx="{cx}" cy="{cy}"/>\n'
214
+ f" </p:xfrm>\n"
215
+ f" <a:graphic>\n"
216
+ f" <a:graphicData/>\n"
217
+ f" </a:graphic>\n"
218
+ f"</p:graphicFrame>"
219
+ ),
220
+ )
221
+
222
+ @classmethod
223
+ def new_ole_object_graphicFrame(
224
+ cls,
225
+ id_: int,
226
+ name: str,
227
+ ole_object_rId: str,
228
+ progId: str,
229
+ icon_rId: str,
230
+ x: int,
231
+ y: int,
232
+ cx: int,
233
+ cy: int,
234
+ imgW: int,
235
+ imgH: int,
236
+ ) -> CT_GraphicalObjectFrame:
237
+ """Return newly-created `p:graphicFrame` for embedded OLE-object.
238
+
239
+ `ole_object_rId` identifies the relationship to the OLE-object part.
240
+
241
+ `progId` is a str identifying the object-type in terms of the application (program) used
242
+ to open it. This becomes an attribute of the same name in the `p:oleObj` element.
243
+
244
+ `icon_rId` identifies the relationship to an image part used to display the OLE-object as
245
+ an icon (vs. a preview).
246
+ """
247
+ return cast(
248
+ CT_GraphicalObjectFrame,
249
+ parse_xml(
250
+ f"<p:graphicFrame {nsdecls('a', 'p', 'r')}>\n"
251
+ f" <p:nvGraphicFramePr>\n"
252
+ f' <p:cNvPr id="{id_}" name="{name}"/>\n'
253
+ f" <p:cNvGraphicFramePr>\n"
254
+ f' <a:graphicFrameLocks noGrp="1"/>\n'
255
+ f" </p:cNvGraphicFramePr>\n"
256
+ f" <p:nvPr/>\n"
257
+ f" </p:nvGraphicFramePr>\n"
258
+ f" <p:xfrm>\n"
259
+ f' <a:off x="{x}" y="{y}"/>\n'
260
+ f' <a:ext cx="{cx}" cy="{cy}"/>\n'
261
+ f" </p:xfrm>\n"
262
+ f" <a:graphic>\n"
263
+ f" <a:graphicData"
264
+ f' uri="http://schemas.openxmlformats.org/presentationml/2006/ole">\n'
265
+ f' <p:oleObj showAsIcon="1"'
266
+ f' r:id="{ole_object_rId}"'
267
+ f' imgW="{imgW}"'
268
+ f' imgH="{imgH}"'
269
+ f' progId="{progId}">\n'
270
+ f" <p:embed/>\n"
271
+ f" <p:pic>\n"
272
+ f" <p:nvPicPr>\n"
273
+ f' <p:cNvPr id="0" name=""/>\n'
274
+ f" <p:cNvPicPr/>\n"
275
+ f" <p:nvPr/>\n"
276
+ f" </p:nvPicPr>\n"
277
+ f" <p:blipFill>\n"
278
+ f' <a:blip r:embed="{icon_rId}"/>\n'
279
+ f" <a:stretch>\n"
280
+ f" <a:fillRect/>\n"
281
+ f" </a:stretch>\n"
282
+ f" </p:blipFill>\n"
283
+ f" <p:spPr>\n"
284
+ f" <a:xfrm>\n"
285
+ f' <a:off x="{x}" y="{y}"/>\n'
286
+ f' <a:ext cx="{cx}" cy="{cy}"/>\n'
287
+ f" </a:xfrm>\n"
288
+ f' <a:prstGeom prst="rect">\n'
289
+ f" <a:avLst/>\n"
290
+ f" </a:prstGeom>\n"
291
+ f" </p:spPr>\n"
292
+ f" </p:pic>\n"
293
+ f" </p:oleObj>\n"
294
+ f" </a:graphicData>\n"
295
+ f" </a:graphic>\n"
296
+ f"</p:graphicFrame>"
297
+ ),
298
+ )
299
+
300
+ @classmethod
301
+ def new_table_graphicFrame(
302
+ cls, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int
303
+ ) -> CT_GraphicalObjectFrame:
304
+ """Return a `p:graphicFrame` element tree populated with a table element."""
305
+ graphicFrame = cls.new_graphicFrame(id_, name, x, y, cx, cy)
306
+ graphicFrame.graphic.graphicData.uri = GRAPHIC_DATA_URI_TABLE
307
+ graphicFrame.graphic.graphicData.append(CT_Table.new_tbl(rows, cols, cx, cy))
308
+ return graphicFrame
309
+
310
+
311
+ class CT_GraphicalObjectFrameNonVisual(BaseOxmlElement):
312
+ """`p:nvGraphicFramePr` element.
313
+
314
+ This contains the non-visual properties of a graphic frame, such as name, id, etc.
315
+ """
316
+
317
+ cNvPr: CT_NonVisualDrawingProps = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
318
+ "p:cNvPr"
319
+ )
320
+ nvPr: CT_ApplicationNonVisualDrawingProps = ( # pyright: ignore[reportAssignmentType]
321
+ OneAndOnlyOne("p:nvPr")
322
+ )
323
+
324
+
325
+ class CT_OleObject(BaseOxmlElement):
326
+ """`p:oleObj` element, container for an OLE object (e.g. Excel file).
327
+
328
+ An OLE object can be either linked or embedded (hence the name).
329
+ """
330
+
331
+ progId: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
332
+ "progId", XsdString
333
+ )
334
+ rId: str | None = OptionalAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
335
+ showAsIcon: bool = OptionalAttribute( # pyright: ignore[reportAssignmentType]
336
+ "showAsIcon", XsdBoolean, default=False
337
+ )
338
+
339
+ @property
340
+ def is_embedded(self) -> bool:
341
+ """True when this OLE object is embedded, False when it is linked."""
342
+ return len(self.xpath("./p:embed")) > 0
@@ -0,0 +1,280 @@
1
+ """lxml custom element classes for shape-tree-related XML elements."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Callable, Iterator
6
+
7
+ from pptx.enum.shapes import MSO_CONNECTOR_TYPE
8
+ from pptx.oxml import parse_xml
9
+ from pptx.oxml.ns import nsdecls, qn
10
+ from pptx.oxml.shapes.autoshape import CT_Shape
11
+ from pptx.oxml.shapes.connector import CT_Connector
12
+ from pptx.oxml.shapes.graphfrm import CT_GraphicalObjectFrame
13
+ from pptx.oxml.shapes.picture import CT_Picture
14
+ from pptx.oxml.shapes.shared import BaseShapeElement
15
+ from pptx.oxml.xmlchemy import BaseOxmlElement, OneAndOnlyOne, ZeroOrOne
16
+ from pptx.util import Emu
17
+
18
+ if TYPE_CHECKING:
19
+ from pptx.enum.shapes import PP_PLACEHOLDER
20
+ from pptx.oxml.shapes import ShapeElement
21
+ from pptx.oxml.shapes.shared import CT_Transform2D
22
+
23
+
24
+ class CT_GroupShape(BaseShapeElement):
25
+ """Used for shape tree (`p:spTree`) as well as the group shape (`p:grpSp`) elements."""
26
+
27
+ nvGrpSpPr: CT_GroupShapeNonVisual = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
28
+ "p:nvGrpSpPr"
29
+ )
30
+ grpSpPr: CT_GroupShapeProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
31
+ "p:grpSpPr"
32
+ )
33
+
34
+ _shape_tags = (
35
+ qn("p:sp"),
36
+ qn("p:grpSp"),
37
+ qn("p:graphicFrame"),
38
+ qn("p:cxnSp"),
39
+ qn("p:pic"),
40
+ qn("p:contentPart"),
41
+ )
42
+
43
+ def add_autoshape(
44
+ self, id_: int, name: str, prst: str, x: int, y: int, cx: int, cy: int
45
+ ) -> CT_Shape:
46
+ """Return new `p:sp` appended to the group/shapetree with specified attributes."""
47
+ sp = CT_Shape.new_autoshape_sp(id_, name, prst, x, y, cx, cy)
48
+ self.insert_element_before(sp, "p:extLst")
49
+ return sp
50
+
51
+ def add_cxnSp(
52
+ self,
53
+ id_: int,
54
+ name: str,
55
+ type_member: MSO_CONNECTOR_TYPE,
56
+ x: int,
57
+ y: int,
58
+ cx: int,
59
+ cy: int,
60
+ flipH: bool,
61
+ flipV: bool,
62
+ ) -> CT_Connector:
63
+ """Return new `p:cxnSp` appended to the group/shapetree with the specified attribues."""
64
+ prst = MSO_CONNECTOR_TYPE.to_xml(type_member)
65
+ cxnSp = CT_Connector.new_cxnSp(id_, name, prst, x, y, cx, cy, flipH, flipV)
66
+ self.insert_element_before(cxnSp, "p:extLst")
67
+ return cxnSp
68
+
69
+ def add_freeform_sp(self, x: int, y: int, cx: int, cy: int) -> CT_Shape:
70
+ """Append a new freeform `p:sp` with specified position and size."""
71
+ shape_id = self._next_shape_id
72
+ name = "Freeform %d" % (shape_id - 1,)
73
+ sp = CT_Shape.new_freeform_sp(shape_id, name, x, y, cx, cy)
74
+ self.insert_element_before(sp, "p:extLst")
75
+ return sp
76
+
77
+ def add_grpSp(self) -> CT_GroupShape:
78
+ """Return `p:grpSp` element newly appended to this shape tree.
79
+
80
+ The element contains no sub-shapes, is positioned at (0, 0), and has
81
+ width and height of zero.
82
+ """
83
+ shape_id = self._next_shape_id
84
+ name = "Group %d" % (shape_id - 1,)
85
+ grpSp = CT_GroupShape.new_grpSp(shape_id, name)
86
+ self.insert_element_before(grpSp, "p:extLst")
87
+ return grpSp
88
+
89
+ def add_pic(
90
+ self, id_: int, name: str, desc: str, rId: str, x: int, y: int, cx: int, cy: int
91
+ ) -> CT_Picture:
92
+ """Append a `p:pic` shape to the group/shapetree having properties as specified in call."""
93
+ pic = CT_Picture.new_pic(id_, name, desc, rId, x, y, cx, cy)
94
+ self.insert_element_before(pic, "p:extLst")
95
+ return pic
96
+
97
+ def add_placeholder(
98
+ self, id_: int, name: str, ph_type: PP_PLACEHOLDER, orient: str, sz: str, idx: int
99
+ ) -> CT_Shape:
100
+ """Append a newly-created placeholder `p:sp` shape having the specified properties."""
101
+ sp = CT_Shape.new_placeholder_sp(id_, name, ph_type, orient, sz, idx)
102
+ self.insert_element_before(sp, "p:extLst")
103
+ return sp
104
+
105
+ def add_table(
106
+ self, id_: int, name: str, rows: int, cols: int, x: int, y: int, cx: int, cy: int
107
+ ) -> CT_GraphicalObjectFrame:
108
+ """Append a `p:graphicFrame` shape containing a table as specified in call."""
109
+ graphicFrame = CT_GraphicalObjectFrame.new_table_graphicFrame(
110
+ id_, name, rows, cols, x, y, cx, cy
111
+ )
112
+ self.insert_element_before(graphicFrame, "p:extLst")
113
+ return graphicFrame
114
+
115
+ def add_textbox(self, id_: int, name: str, x: int, y: int, cx: int, cy: int) -> CT_Shape:
116
+ """Append a newly-created textbox `p:sp` shape having the specified position and size."""
117
+ sp = CT_Shape.new_textbox_sp(id_, name, x, y, cx, cy)
118
+ self.insert_element_before(sp, "p:extLst")
119
+ return sp
120
+
121
+ @property
122
+ def chExt(self):
123
+ """Descendent `p:grpSpPr/a:xfrm/a:chExt` element."""
124
+ return self.grpSpPr.get_or_add_xfrm().get_or_add_chExt()
125
+
126
+ @property
127
+ def chOff(self):
128
+ """Descendent `p:grpSpPr/a:xfrm/a:chOff` element."""
129
+ return self.grpSpPr.get_or_add_xfrm().get_or_add_chOff()
130
+
131
+ def get_or_add_xfrm(self) -> CT_Transform2D:
132
+ """Return the `a:xfrm` grandchild element, newly-added if not present."""
133
+ return self.grpSpPr.get_or_add_xfrm()
134
+
135
+ def iter_ph_elms(self):
136
+ """Generate each placeholder shape child element in document order."""
137
+ for e in self.iter_shape_elms():
138
+ if e.has_ph_elm:
139
+ yield e
140
+
141
+ def iter_shape_elms(self) -> Iterator[ShapeElement]:
142
+ """Generate each child of this `p:spTree` element that corresponds to a shape.
143
+
144
+ Items appear in XML document order.
145
+ """
146
+ for elm in self.iterchildren():
147
+ if elm.tag in self._shape_tags:
148
+ yield elm
149
+
150
+ @property
151
+ def max_shape_id(self) -> int:
152
+ """Maximum int value assigned as @id in this slide.
153
+
154
+ This is generally a shape-id, but ids can be assigned to other
155
+ objects so we just check all @id values anywhere in the document
156
+ (XML id-values have document scope).
157
+
158
+ In practice, its minimum value is 1 because the spTree element itself
159
+ is always assigned id="1".
160
+ """
161
+ id_str_lst = self.xpath("//@id")
162
+ used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
163
+ return max(used_ids) if used_ids else 0
164
+
165
+ @classmethod
166
+ def new_grpSp(cls, id_: int, name: str) -> CT_GroupShape:
167
+ """Return new "loose" `p:grpSp` element having `id_` and `name`."""
168
+ xml = (
169
+ "<p:grpSp %s>\n"
170
+ " <p:nvGrpSpPr>\n"
171
+ ' <p:cNvPr id="%%d" name="%%s"/>\n'
172
+ " <p:cNvGrpSpPr/>\n"
173
+ " <p:nvPr/>\n"
174
+ " </p:nvGrpSpPr>\n"
175
+ " <p:grpSpPr>\n"
176
+ " <a:xfrm>\n"
177
+ ' <a:off x="0" y="0"/>\n'
178
+ ' <a:ext cx="0" cy="0"/>\n'
179
+ ' <a:chOff x="0" y="0"/>\n'
180
+ ' <a:chExt cx="0" cy="0"/>\n'
181
+ " </a:xfrm>\n"
182
+ " </p:grpSpPr>\n"
183
+ "</p:grpSp>" % nsdecls("a", "p", "r")
184
+ ) % (id_, name)
185
+ grpSp = parse_xml(xml)
186
+ return grpSp
187
+
188
+ def recalculate_extents(self) -> None:
189
+ """Adjust x, y, cx, and cy to incorporate all contained shapes.
190
+
191
+ This would typically be called when a contained shape is added,
192
+ removed, or its position or size updated.
193
+
194
+ This method is recursive "upwards" since a change in a group shape
195
+ can change the position and size of its containing group.
196
+ """
197
+ if not self.tag == qn("p:grpSp"):
198
+ return
199
+
200
+ x, y, cx, cy = self._child_extents
201
+
202
+ self.chOff.x = self.x = x
203
+ self.chOff.y = self.y = y
204
+ self.chExt.cx = self.cx = cx
205
+ self.chExt.cy = self.cy = cy
206
+ self.getparent().recalculate_extents()
207
+
208
+ @property
209
+ def xfrm(self) -> CT_Transform2D | None:
210
+ """The `a:xfrm` grandchild element or |None| if not found."""
211
+ return self.grpSpPr.xfrm
212
+
213
+ @property
214
+ def _child_extents(self) -> tuple[int, int, int, int]:
215
+ """(x, y, cx, cy) tuple representing net position and size.
216
+
217
+ The values are formed as a composite of the contained child shapes.
218
+ """
219
+ child_shape_elms = list(self.iter_shape_elms())
220
+
221
+ if not child_shape_elms:
222
+ return Emu(0), Emu(0), Emu(0), Emu(0)
223
+
224
+ min_x = min([xSp.x for xSp in child_shape_elms])
225
+ min_y = min([xSp.y for xSp in child_shape_elms])
226
+ max_x = max([(xSp.x + xSp.cx) for xSp in child_shape_elms])
227
+ max_y = max([(xSp.y + xSp.cy) for xSp in child_shape_elms])
228
+
229
+ x = min_x
230
+ y = min_y
231
+ cx = max_x - min_x
232
+ cy = max_y - min_y
233
+
234
+ return x, y, cx, cy
235
+
236
+ @property
237
+ def _next_shape_id(self) -> int:
238
+ """Return unique shape id suitable for use with a new shape element.
239
+
240
+ The returned id is the next available positive integer drawing object
241
+ id in shape tree, starting from 1 and making use of any gaps in
242
+ numbering. In practice, the minimum id is 2 because the spTree
243
+ element itself is always assigned id="1".
244
+ """
245
+ id_str_lst = self.xpath("//@id")
246
+ used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
247
+ for n in range(1, len(used_ids) + 2):
248
+ if n not in used_ids:
249
+ return n
250
+
251
+
252
+ class CT_GroupShapeNonVisual(BaseShapeElement):
253
+ """`p:nvGrpSpPr` element."""
254
+
255
+ cNvPr = OneAndOnlyOne("p:cNvPr")
256
+
257
+
258
+ class CT_GroupShapeProperties(BaseOxmlElement):
259
+ """p:grpSpPr element"""
260
+
261
+ get_or_add_xfrm: Callable[[], CT_Transform2D]
262
+
263
+ _tag_seq = (
264
+ "a:xfrm",
265
+ "a:noFill",
266
+ "a:solidFill",
267
+ "a:gradFill",
268
+ "a:blipFill",
269
+ "a:pattFill",
270
+ "a:grpFill",
271
+ "a:effectLst",
272
+ "a:effectDag",
273
+ "a:scene3d",
274
+ "a:extLst",
275
+ )
276
+ xfrm: CT_Transform2D | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
277
+ "a:xfrm", successors=_tag_seq[1:]
278
+ )
279
+ effectLst = ZeroOrOne("a:effectLst", successors=_tag_seq[8:])
280
+ del _tag_seq