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
pptx/oxml/text.py ADDED
@@ -0,0 +1,748 @@
1
+ """Custom element classes for text-related XML elements"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import TYPE_CHECKING, Callable, cast
7
+
8
+ from pptx.enum.lang import MSO_LANGUAGE_ID
9
+ from pptx.enum.text import (
10
+ MSO_AUTO_SIZE,
11
+ MSO_NUMBERED_BULLET_STYLE,
12
+ MSO_TEXT_STRIKE_TYPE,
13
+ MSO_TEXT_UNDERLINE_TYPE,
14
+ MSO_VERTICAL_ANCHOR,
15
+ PP_PARAGRAPH_ALIGNMENT,
16
+ BulletStyleType,
17
+ )
18
+ from pptx.exc import InvalidXmlError
19
+ from pptx.oxml import parse_xml
20
+ from pptx.oxml.dml.fill import CT_GradientFillProperties
21
+ from pptx.oxml.ns import nsdecls, qn
22
+ from pptx.oxml.simpletypes import (
23
+ ST_Coordinate32,
24
+ ST_Percentage,
25
+ ST_TextFontScalePercentOrPercentString,
26
+ ST_TextFontSize,
27
+ ST_TextIndentLevelType,
28
+ ST_TextSpacingPercentOrPercentString,
29
+ ST_TextSpacingPoint,
30
+ ST_TextTypeface,
31
+ ST_TextWrappingType,
32
+ XsdBoolean,
33
+ XsdString,
34
+ )
35
+ from pptx.oxml.xmlchemy import (
36
+ BaseOxmlElement,
37
+ Choice,
38
+ OneAndOnlyOne,
39
+ OneOrMore,
40
+ OptionalAttribute,
41
+ OxmlElement,
42
+ RequiredAttribute,
43
+ ZeroOrMore,
44
+ ZeroOrOne,
45
+ ZeroOrOneChoice,
46
+ )
47
+ from pptx.util import BulletStyle, Emu, Length
48
+
49
+ if TYPE_CHECKING:
50
+ from pptx.oxml.action import CT_Hyperlink
51
+
52
+
53
+ class CT_RegularTextRun(BaseOxmlElement):
54
+ """`a:r` custom element class"""
55
+
56
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
57
+
58
+ rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
59
+ "a:rPr", successors=("a:t",)
60
+ )
61
+ t: BaseOxmlElement = OneAndOnlyOne("a:t") # pyright: ignore[reportAssignmentType]
62
+
63
+ @property
64
+ def text(self) -> str:
65
+ """All text of (required) `a:t` child."""
66
+ text = self.t.text
67
+ # -- t.text is None when t element is empty, e.g. '<a:t/>' --
68
+ return text or ""
69
+
70
+ @text.setter
71
+ def text(self, value: str): # pyright: ignore[reportIncompatibleMethodOverride]
72
+ self.t.text = self._escape_ctrl_chars(value)
73
+
74
+ @staticmethod
75
+ def _escape_ctrl_chars(s: str) -> str:
76
+ """Return str after replacing each control character with a plain-text escape.
77
+
78
+ For example, a BEL character (x07) would appear as "_x0007_". Horizontal-tab
79
+ (x09) and line-feed (x0A) are not escaped. All other characters in the range
80
+ x00-x1F are escaped.
81
+ """
82
+ return re.sub(r"([\x00-\x08\x0B-\x1F])", lambda match: "_x%04X_" % ord(match.group(1)), s)
83
+
84
+
85
+ class CT_TextBody(BaseOxmlElement):
86
+ """`p:txBody` custom element class.
87
+
88
+ Also used for `c:txPr` in charts and perhaps other elements.
89
+ """
90
+
91
+ add_p: Callable[[], CT_TextParagraph]
92
+ p_lst: list[CT_TextParagraph]
93
+
94
+ bodyPr: CT_TextBodyProperties = OneAndOnlyOne( # pyright: ignore[reportAssignmentType]
95
+ "a:bodyPr"
96
+ )
97
+ p: CT_TextParagraph = OneOrMore("a:p") # pyright: ignore[reportAssignmentType]
98
+
99
+ def clear_content(self):
100
+ """Remove all `a:p` children, but leave any others.
101
+
102
+ cf. lxml `_Element.clear()` method which removes all children.
103
+ """
104
+ for p in self.p_lst:
105
+ self.remove(p)
106
+
107
+ @property
108
+ def defRPr(self) -> CT_TextCharacterProperties:
109
+ """`a:defRPr` element of required first `p` child, added with its ancestors if not present.
110
+
111
+ Used when element is a ``c:txPr`` in a chart and the `p` element is used only to specify
112
+ formatting, not content.
113
+ """
114
+ p = self.p_lst[0]
115
+ pPr = p.get_or_add_pPr()
116
+ defRPr = pPr.get_or_add_defRPr()
117
+ return defRPr
118
+
119
+ @property
120
+ def is_empty(self) -> bool:
121
+ """True if only a single empty `a:p` element is present."""
122
+ ps = self.p_lst
123
+ if len(ps) > 1:
124
+ return False
125
+
126
+ if not ps:
127
+ raise InvalidXmlError("p:txBody must have at least one a:p")
128
+
129
+ if ps[0].text != "":
130
+ return False
131
+ return True
132
+
133
+ @classmethod
134
+ def new(cls):
135
+ """Return a new `p:txBody` element tree."""
136
+ xml = cls._txBody_tmpl()
137
+ txBody = parse_xml(xml)
138
+ return txBody
139
+
140
+ @classmethod
141
+ def new_a_txBody(cls) -> CT_TextBody:
142
+ """Return a new `a:txBody` element tree.
143
+
144
+ Suitable for use in a table cell and possibly other situations.
145
+ """
146
+ xml = cls._a_txBody_tmpl()
147
+ txBody = cast(CT_TextBody, parse_xml(xml))
148
+ return txBody
149
+
150
+ @classmethod
151
+ def new_p_txBody(cls):
152
+ """Return a new `p:txBody` element tree, suitable for use in an `p:sp` element."""
153
+ xml = cls._p_txBody_tmpl()
154
+ return parse_xml(xml)
155
+
156
+ @classmethod
157
+ def new_txPr(cls):
158
+ """Return a `c:txPr` element tree.
159
+
160
+ Suitable for use in a chart object like data labels or tick labels.
161
+ """
162
+ xml = (
163
+ "<c:txPr %s>\n"
164
+ " <a:bodyPr/>\n"
165
+ " <a:lstStyle/>\n"
166
+ " <a:p>\n"
167
+ " <a:pPr>\n"
168
+ " <a:defRPr/>\n"
169
+ " </a:pPr>\n"
170
+ " </a:p>\n"
171
+ "</c:txPr>\n"
172
+ ) % nsdecls("c", "a")
173
+ txPr = parse_xml(xml)
174
+ return txPr
175
+
176
+ def unclear_content(self):
177
+ """Ensure p:txBody has at least one a:p child.
178
+
179
+ Intuitively, reverse a ".clear_content()" operation to minimum conformance with spec
180
+ (single empty paragraph).
181
+ """
182
+ if len(self.p_lst) > 0:
183
+ return
184
+ self.add_p()
185
+
186
+ @classmethod
187
+ def _a_txBody_tmpl(cls):
188
+ return "<a:txBody %s>\n <a:bodyPr/>\n <a:p/>\n</a:txBody>\n" % (nsdecls("a"))
189
+
190
+ @classmethod
191
+ def _p_txBody_tmpl(cls):
192
+ return "<p:txBody %s>\n <a:bodyPr/>\n <a:p/>\n</p:txBody>\n" % (nsdecls("p", "a"))
193
+
194
+ @classmethod
195
+ def _txBody_tmpl(cls):
196
+ return "<p:txBody %s>\n <a:bodyPr/>\n <a:lstStyle/>\n <a:p/>\n</p:txBody>\n" % (
197
+ nsdecls("a", "p")
198
+ )
199
+
200
+
201
+ class CT_TextBodyProperties(BaseOxmlElement):
202
+ """`a:bodyPr` custom element class."""
203
+
204
+ _add_noAutofit: Callable[[], BaseOxmlElement]
205
+ _add_normAutofit: Callable[[], CT_TextNormalAutofit]
206
+ _add_spAutoFit: Callable[[], BaseOxmlElement]
207
+ _remove_eg_textAutoFit: Callable[[], None]
208
+
209
+ noAutofit: BaseOxmlElement | None
210
+ normAutofit: CT_TextNormalAutofit | None
211
+ spAutoFit: BaseOxmlElement | None
212
+
213
+ eg_textAutoFit = ZeroOrOneChoice(
214
+ (Choice("a:noAutofit"), Choice("a:normAutofit"), Choice("a:spAutoFit")),
215
+ successors=("a:scene3d", "a:sp3d", "a:flatTx", "a:extLst"),
216
+ )
217
+ lIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
218
+ "lIns", ST_Coordinate32, default=Emu(91440)
219
+ )
220
+ tIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
221
+ "tIns", ST_Coordinate32, default=Emu(45720)
222
+ )
223
+ rIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
224
+ "rIns", ST_Coordinate32, default=Emu(91440)
225
+ )
226
+ bIns: Length = OptionalAttribute( # pyright: ignore[reportAssignmentType]
227
+ "bIns", ST_Coordinate32, default=Emu(45720)
228
+ )
229
+ anchor: MSO_VERTICAL_ANCHOR | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
230
+ "anchor", MSO_VERTICAL_ANCHOR
231
+ )
232
+ wrap: str | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
233
+ "wrap", ST_TextWrappingType
234
+ )
235
+
236
+ @property
237
+ def autofit(self):
238
+ """The autofit setting for the text frame, a member of the `MSO_AUTO_SIZE` enumeration."""
239
+ if self.noAutofit is not None:
240
+ return MSO_AUTO_SIZE.NONE
241
+ if self.normAutofit is not None:
242
+ return MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
243
+ if self.spAutoFit is not None:
244
+ return MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT
245
+ return None
246
+
247
+ @autofit.setter
248
+ def autofit(self, value: MSO_AUTO_SIZE | None):
249
+ if value is not None and value not in MSO_AUTO_SIZE:
250
+ raise ValueError(
251
+ f"only None or a member of the MSO_AUTO_SIZE enumeration can be assigned to"
252
+ f" CT_TextBodyProperties.autofit, got {value}"
253
+ )
254
+ self._remove_eg_textAutoFit()
255
+ if value == MSO_AUTO_SIZE.NONE:
256
+ self._add_noAutofit()
257
+ elif value == MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE:
258
+ self._add_normAutofit()
259
+ elif value == MSO_AUTO_SIZE.SHAPE_TO_FIT_TEXT:
260
+ self._add_spAutoFit()
261
+
262
+
263
+ class CT_TextCharacterProperties(BaseOxmlElement):
264
+ """Custom element class for `a:rPr`, `a:defRPr`, and `a:endParaRPr`.
265
+
266
+ 'rPr' is short for 'run properties', and it corresponds to the |Font| proxy class.
267
+ """
268
+
269
+ get_or_add_hlinkClick: Callable[[], CT_Hyperlink]
270
+ get_or_add_latin: Callable[[], CT_TextFont]
271
+ _remove_latin: Callable[[], None]
272
+ _remove_hlinkClick: Callable[[], None]
273
+
274
+ eg_fillProperties = ZeroOrOneChoice(
275
+ (
276
+ Choice("a:noFill"),
277
+ Choice("a:solidFill"),
278
+ Choice("a:gradFill"),
279
+ Choice("a:blipFill"),
280
+ Choice("a:pattFill"),
281
+ Choice("a:grpFill"),
282
+ ),
283
+ successors=(
284
+ "a:effectLst",
285
+ "a:effectDag",
286
+ "a:highlight",
287
+ "a:uLnTx",
288
+ "a:uLn",
289
+ "a:uFillTx",
290
+ "a:uFill",
291
+ "a:latin",
292
+ "a:ea",
293
+ "a:cs",
294
+ "a:sym",
295
+ "a:hlinkClick",
296
+ "a:hlinkMouseOver",
297
+ "a:rtl",
298
+ "a:extLst",
299
+ ),
300
+ )
301
+ latin: CT_TextFont | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
302
+ "a:latin",
303
+ successors=(
304
+ "a:ea",
305
+ "a:cs",
306
+ "a:sym",
307
+ "a:hlinkClick",
308
+ "a:hlinkMouseOver",
309
+ "a:rtl",
310
+ "a:extLst",
311
+ ),
312
+ )
313
+ hlinkClick: CT_Hyperlink | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
314
+ "a:hlinkClick", successors=("a:hlinkMouseOver", "a:rtl", "a:extLst")
315
+ )
316
+
317
+ baseline = OptionalAttribute("baseline", ST_Percentage)
318
+ strike = OptionalAttribute("strike", MSO_TEXT_STRIKE_TYPE)
319
+
320
+ lang: MSO_LANGUAGE_ID | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
321
+ "lang", MSO_LANGUAGE_ID
322
+ )
323
+ sz: int | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
324
+ "sz", ST_TextFontSize
325
+ )
326
+ b: bool | None = OptionalAttribute("b", XsdBoolean) # pyright: ignore[reportAssignmentType]
327
+ i: bool | None = OptionalAttribute("i", XsdBoolean) # pyright: ignore[reportAssignmentType]
328
+ u: MSO_TEXT_UNDERLINE_TYPE | None = OptionalAttribute( # pyright: ignore[reportAssignmentType]
329
+ "u", MSO_TEXT_UNDERLINE_TYPE
330
+ )
331
+
332
+ @property
333
+ def theme_font(self):
334
+ names = [self.find(qn("a:" + script)) for script in ("latin", "ea", "cs")]
335
+ for kind, prefix in (("major", "+mj-"), ("minor", "+mn-")):
336
+ if all(
337
+ node is not None and node.get("typeface") == prefix + suffix
338
+ for node, suffix in zip(names, ("lt", "ea", "cs"))
339
+ ):
340
+ return kind
341
+ return None
342
+
343
+ @theme_font.setter
344
+ def theme_font(self, value):
345
+ if value not in (None, "major", "minor"):
346
+ raise ValueError("theme_font must be major, minor, or None")
347
+ for script in ("latin", "ea", "cs"):
348
+ for node in list(self.findall(qn("a:" + script))):
349
+ self.remove(node)
350
+ if value is None:
351
+ return
352
+ prefix = "+mj-" if value == "major" else "+mn-"
353
+ for script, suffix in (("latin", "lt"), ("ea", "ea"), ("cs", "cs")):
354
+ node = OxmlElement("a:" + script)
355
+ node.set("typeface", prefix + suffix)
356
+ self.insert_element_before(
357
+ node, "a:sym", "a:hlinkClick", "a:hlinkMouseOver", "a:rtl", "a:extLst"
358
+ )
359
+
360
+ def _new_gradFill(self):
361
+ return CT_GradientFillProperties.new_gradFill()
362
+
363
+ def add_hlinkClick(self, rId: str) -> CT_Hyperlink:
364
+ """Add an `a:hlinkClick` child element with r:id attribute set to `rId`."""
365
+ hlinkClick = self.get_or_add_hlinkClick()
366
+ hlinkClick.rId = rId
367
+ return hlinkClick
368
+
369
+
370
+ class CT_TextField(BaseOxmlElement):
371
+ """`a:fld` field element, for either a slide number or date field."""
372
+
373
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
374
+
375
+ rPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
376
+ "a:rPr", successors=("a:pPr", "a:t")
377
+ )
378
+ t: BaseOxmlElement | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
379
+ "a:t", successors=()
380
+ )
381
+
382
+ @property
383
+ def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
384
+ """The text of the `a:t` child element."""
385
+ t = self.t
386
+ if t is None:
387
+ return ""
388
+ return t.text or ""
389
+
390
+
391
+ class CT_TextFont(BaseOxmlElement):
392
+ """Custom element class for `a:latin`, `a:ea`, `a:cs`, and `a:sym`.
393
+
394
+ These occur as child elements of CT_TextCharacterProperties, e.g. `a:rPr`.
395
+ """
396
+
397
+ typeface: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
398
+ "typeface", ST_TextTypeface
399
+ )
400
+
401
+
402
+ class CT_TextLineBreak(BaseOxmlElement):
403
+ """`a:br` line break element"""
404
+
405
+ get_or_add_rPr: Callable[[], CT_TextCharacterProperties]
406
+
407
+ rPr = ZeroOrOne("a:rPr", successors=())
408
+
409
+ @property
410
+ def text(self): # pyright: ignore[reportIncompatibleMethodOverride]
411
+ """Unconditionally a single vertical-tab character.
412
+
413
+ A line break element can contain no text other than the implicit line feed it
414
+ represents.
415
+ """
416
+ return "\v"
417
+
418
+
419
+ class CT_TextNormalAutofit(BaseOxmlElement):
420
+ """`a:normAutofit` element specifying fit text to shape font reduction, etc."""
421
+
422
+ fontScale = OptionalAttribute(
423
+ "fontScale", ST_TextFontScalePercentOrPercentString, default=100.0
424
+ )
425
+
426
+
427
+ class CT_TextParagraph(BaseOxmlElement):
428
+ """`a:p` custom element class"""
429
+
430
+ get_or_add_endParaRPr: Callable[[], CT_TextCharacterProperties]
431
+ get_or_add_pPr: Callable[[], CT_TextParagraphProperties]
432
+ r_lst: list[CT_RegularTextRun]
433
+ _add_br: Callable[[], CT_TextLineBreak]
434
+ _add_r: Callable[[], CT_RegularTextRun]
435
+
436
+ pPr: CT_TextParagraphProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
437
+ "a:pPr", successors=("a:r", "a:br", "a:fld", "a:endParaRPr")
438
+ )
439
+ r = ZeroOrMore("a:r", successors=("a:endParaRPr",))
440
+ br = ZeroOrMore("a:br", successors=("a:endParaRPr",))
441
+ endParaRPr: CT_TextCharacterProperties | None = ZeroOrOne("a:endParaRPr", successors=()) # pyright: ignore[reportAssignmentType]
442
+
443
+ def add_br(self) -> CT_TextLineBreak:
444
+ """Return a newly appended `a:br` element."""
445
+ return self._add_br()
446
+
447
+ def add_r(self, text: str | None = None) -> CT_RegularTextRun:
448
+ """Return a newly appended `a:r` element."""
449
+ r = self._add_r()
450
+ if text:
451
+ r.text = text
452
+ return r
453
+
454
+ def append_text(self, text: str):
455
+ """Append `a:r` and `a:br` elements to `p` based on `text`.
456
+
457
+ Any `\n` or `\v` (vertical-tab) characters in `text` delimit `a:r` (run) elements and
458
+ themselves are translated to `a:br` (line-break) elements. The vertical-tab character
459
+ appears in clipboard text from PowerPoint at "soft" line-breaks (new-line, but not new
460
+ paragraph).
461
+ """
462
+ for idx, r_str in enumerate(re.split("\n|\v", text)):
463
+ # ---breaks are only added _between_ items, not at start---
464
+ if idx > 0:
465
+ self.add_br()
466
+ # ---runs that would be empty are not added---
467
+ if r_str:
468
+ self.add_r(r_str)
469
+
470
+ @property
471
+ def content_children(self) -> tuple[CT_RegularTextRun | CT_TextLineBreak | CT_TextField, ...]:
472
+ """Sequence containing text-container child elements of this `a:p` element.
473
+
474
+ These include `a:r`, `a:br`, and `a:fld`.
475
+ """
476
+ return tuple(
477
+ e for e in self if isinstance(e, (CT_RegularTextRun, CT_TextLineBreak, CT_TextField))
478
+ )
479
+
480
+ @property
481
+ def text(self) -> str: # pyright: ignore[reportIncompatibleMethodOverride]
482
+ """str text contained in this paragraph."""
483
+ # ---note this shadows the lxml _Element.text---
484
+ return "".join([child.text for child in self.content_children])
485
+
486
+ def _new_r(self):
487
+ r_xml = "<a:r %s><a:t/></a:r>" % nsdecls("a")
488
+ return parse_xml(r_xml)
489
+
490
+
491
+ class CT_TextParagraphProperties(BaseOxmlElement):
492
+ """`a:pPr` custom element class."""
493
+
494
+ get_or_add_defRPr: Callable[[], CT_TextCharacterProperties]
495
+ _add_lnSpc: Callable[[], CT_TextSpacing]
496
+ _add_spcAft: Callable[[], CT_TextSpacing]
497
+ _add_spcBef: Callable[[], CT_TextSpacing]
498
+ _add_buNone: Callable[[], CT_TextNoBullet]
499
+ _add_buAutoNum: Callable[[], CT_TextAutoNumberBullet]
500
+ _add_buChar: Callable[[], CT_TextCharBullet]
501
+ _remove_lnSpc: Callable[[], None]
502
+ _remove_spcAft: Callable[[], None]
503
+ _remove_spcBef: Callable[[], None]
504
+ _remove_buNone: Callable[[], None]
505
+ _remove_buAutoNum: Callable[[], CT_TextAutoNumberBullet]
506
+ _remove_buChar: Callable[[], None]
507
+
508
+ _tag_seq = (
509
+ "a:lnSpc",
510
+ "a:spcBef",
511
+ "a:spcAft",
512
+ "a:buClrTx",
513
+ "a:buClr",
514
+ "a:buSzTx",
515
+ "a:buSzPct",
516
+ "a:buSzPts",
517
+ "a:buFontTx",
518
+ "a:buFont",
519
+ "a:buNone",
520
+ "a:buAutoNum",
521
+ "a:buChar",
522
+ "a:buBlip",
523
+ "a:tabLst",
524
+ "a:defRPr",
525
+ "a:extLst",
526
+ )
527
+ lnSpc: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
528
+ "a:lnSpc", successors=_tag_seq[1:]
529
+ )
530
+ spcBef: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
531
+ "a:spcBef", successors=_tag_seq[2:]
532
+ )
533
+ spcAft: CT_TextSpacing | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
534
+ "a:spcAft", successors=_tag_seq[3:]
535
+ )
536
+ defRPr: CT_TextCharacterProperties | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
537
+ "a:defRPr", successors=_tag_seq[16:]
538
+ )
539
+ buNone: CT_TextNoBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
540
+ "a:buNone", successors=_tag_seq[11:]
541
+ )
542
+ buAutoNum: CT_TextAutoNumberBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
543
+ "a:buAutoNum", successors=_tag_seq[12:]
544
+ )
545
+ buChar: CT_TextCharBullet | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
546
+ "a:buChar", successors=_tag_seq[13:]
547
+ )
548
+ marL = OptionalAttribute("marL", ST_Coordinate32)
549
+ indent = OptionalAttribute("indent", ST_Coordinate32)
550
+
551
+ lvl: int = OptionalAttribute( # pyright: ignore[reportAssignmentType]
552
+ "lvl", ST_TextIndentLevelType, default=0
553
+ )
554
+ algn: PP_PARAGRAPH_ALIGNMENT | None = OptionalAttribute("algn", PP_PARAGRAPH_ALIGNMENT) # pyright: ignore[reportAssignmentType]
555
+ del _tag_seq
556
+
557
+ @property
558
+ def line_spacing(self) -> float | Length | None:
559
+ """The spacing between baselines of successive lines in this paragraph.
560
+
561
+ A float value indicates a number of lines. A |Length| value indicates a fixed spacing.
562
+ Value is contained in `./a:lnSpc/a:spcPts/@val` or `./a:lnSpc/a:spcPct/@val`. Value is
563
+ |None| if no element is present.
564
+ """
565
+ lnSpc = self.lnSpc
566
+ if lnSpc is None:
567
+ return None
568
+ if lnSpc.spcPts is not None:
569
+ return lnSpc.spcPts.val
570
+ return cast(CT_TextSpacingPercent, lnSpc.spcPct).val
571
+
572
+ @line_spacing.setter
573
+ def line_spacing(self, value: float | Length | None):
574
+ self._remove_lnSpc()
575
+ if value is None:
576
+ return
577
+ if isinstance(value, Length):
578
+ self._add_lnSpc().set_spcPts(value)
579
+ else:
580
+ self._add_lnSpc().set_spcPct(value)
581
+
582
+ @property
583
+ def bullet(self) -> BulletStyle:
584
+ """The style of bullet used for this paragraph."""
585
+ buNone = self.buNone
586
+ buChar = self.buChar
587
+ buAutoNum = self.buAutoNum
588
+
589
+ if buChar is not None:
590
+ return BulletStyle.custom(buChar.char)
591
+ elif buAutoNum is not None:
592
+ return BulletStyle.numbered(
593
+ buAutoNum.val,
594
+ int(buAutoNum.get("startAt")) if buAutoNum.get("startAt") is not None else None,
595
+ )
596
+ elif buNone is not None:
597
+ return BulletStyle.NO_BULLET
598
+ else:
599
+ return BulletStyle.DEFAULT
600
+
601
+ @bullet.setter
602
+ def bullet(self, value: BulletStyle):
603
+ if not isinstance(value, BulletStyle):
604
+ raise TypeError("bullet must be a BulletStyle")
605
+ if value.style == BulletStyleType.NUMBERED:
606
+ MSO_NUMBERED_BULLET_STYLE.to_xml(value.value)
607
+ elif value.style == BulletStyleType.CUSTOM:
608
+ if not isinstance(value.value, str) or not value.value:
609
+ raise ValueError("custom bullet must be a nonempty string")
610
+ tags = ("a:buNone", "a:buChar", "a:buAutoNum", "a:buBlip")
611
+ if value == BulletStyle.DEFAULT:
612
+ tags += (
613
+ "a:buClr",
614
+ "a:buClrTx",
615
+ "a:buFont",
616
+ "a:buFontTx",
617
+ "a:buSzPct",
618
+ "a:buSzPts",
619
+ "a:buSzTx",
620
+ )
621
+ for child in list(self):
622
+ if child.tag in {qn(tag) for tag in tags}:
623
+ self.remove(child)
624
+
625
+ if value == BulletStyle.DEFAULT:
626
+ return
627
+ elif value == BulletStyle.NO_BULLET:
628
+ self._add_buNone()
629
+ elif value.style == BulletStyleType.CUSTOM:
630
+ buChar = self._add_buChar()
631
+ buChar.char = cast(str, value.value)
632
+ elif value.style == BulletStyleType.NUMBERED:
633
+ buAutoNum = self._add_buAutoNum()
634
+ buAutoNum.val = cast(MSO_NUMBERED_BULLET_STYLE, value.value)
635
+ if value.start_at is not None:
636
+ buAutoNum.set("startAt", str(value.start_at))
637
+
638
+ @property
639
+ def space_after(self) -> Length | None:
640
+ """The EMU equivalent of the centipoints value in `./a:spcAft/a:spcPts/@val`."""
641
+ spcAft = self.spcAft
642
+ if spcAft is None:
643
+ return None
644
+ spcPts = spcAft.spcPts
645
+ if spcPts is None:
646
+ return None
647
+ return spcPts.val
648
+
649
+ @space_after.setter
650
+ def space_after(self, value: Length | None):
651
+ self._remove_spcAft()
652
+ if value is not None:
653
+ self._add_spcAft().set_spcPts(value)
654
+
655
+ @property
656
+ def space_before(self):
657
+ """The EMU equivalent of the centipoints value in `./a:spcBef/a:spcPts/@val`."""
658
+ spcBef = self.spcBef
659
+ if spcBef is None:
660
+ return None
661
+ spcPts = spcBef.spcPts
662
+ if spcPts is None:
663
+ return None
664
+ return spcPts.val
665
+
666
+ @space_before.setter
667
+ def space_before(self, value: Length | None):
668
+ self._remove_spcBef()
669
+ if value is not None:
670
+ self._add_spcBef().set_spcPts(value)
671
+
672
+
673
+ class CT_TextSpacing(BaseOxmlElement):
674
+ """Used for `a:lnSpc`, `a:spcBef`, and `a:spcAft` elements."""
675
+
676
+ get_or_add_spcPct: Callable[[], CT_TextSpacingPercent]
677
+ get_or_add_spcPts: Callable[[], CT_TextSpacingPoint]
678
+ _remove_spcPct: Callable[[], None]
679
+ _remove_spcPts: Callable[[], None]
680
+
681
+ # this should actually be a OneAndOnlyOneChoice, but that's not
682
+ # implemented yet.
683
+ spcPct: CT_TextSpacingPercent | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
684
+ "a:spcPct"
685
+ )
686
+ spcPts: CT_TextSpacingPoint | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
687
+ "a:spcPts"
688
+ )
689
+
690
+ def set_spcPct(self, value: float):
691
+ """Set spacing to `value` lines, e.g. 1.75 lines.
692
+
693
+ A ./a:spcPts child is removed if present.
694
+ """
695
+ self._remove_spcPts()
696
+ spcPct = self.get_or_add_spcPct()
697
+ spcPct.val = value
698
+
699
+ def set_spcPts(self, value: Length):
700
+ """Set spacing to `value` points. A ./a:spcPct child is removed if present."""
701
+ self._remove_spcPct()
702
+ spcPts = self.get_or_add_spcPts()
703
+ spcPts.val = value
704
+
705
+
706
+ class CT_TextSpacingPercent(BaseOxmlElement):
707
+ """`a:spcPct` element, specifying spacing in thousandths of a percent in its `val` attribute."""
708
+
709
+ val: float = RequiredAttribute( # pyright: ignore[reportAssignmentType]
710
+ "val", ST_TextSpacingPercentOrPercentString
711
+ )
712
+
713
+
714
+ class CT_TextSpacingPoint(BaseOxmlElement):
715
+ """`a:spcPts` element, specifying spacing in centipoints in its `val` attribute."""
716
+
717
+ val: Length = RequiredAttribute( # pyright: ignore[reportAssignmentType]
718
+ "val", ST_TextSpacingPoint
719
+ )
720
+
721
+
722
+ class CT_TextNoBullet(BaseOxmlElement):
723
+ """
724
+ <a:buNone> element, specifying that a paragraph should not be bulleted.
725
+ """
726
+
727
+ pass
728
+
729
+
730
+ class CT_TextCharBullet(BaseOxmlElement):
731
+ """
732
+ <a:buChar> element, specifying that a paragraph should have a character bullet.
733
+ """
734
+
735
+ char: str = RequiredAttribute( # pyright: ignore[reportAssignmentType]
736
+ "char", XsdString
737
+ )
738
+
739
+
740
+ class CT_TextAutoNumberBullet(BaseOxmlElement):
741
+ """
742
+ <a:buAutoNum> element, specifying that a paragraph should have an automatically
743
+ incremented bullet of the specified `type`.
744
+ """
745
+
746
+ val: MSO_NUMBERED_BULLET_STYLE = RequiredAttribute( # pyright: ignore[reportAssignmentType]
747
+ "type", MSO_NUMBERED_BULLET_STYLE
748
+ )