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.
- pptx/__init__.py +84 -0
- pptx/action.py +270 -0
- pptx/api.py +49 -0
- pptx/chart/__init__.py +0 -0
- pptx/chart/axis.py +523 -0
- pptx/chart/category.py +200 -0
- pptx/chart/chart.py +280 -0
- pptx/chart/data.py +864 -0
- pptx/chart/datalabel.py +288 -0
- pptx/chart/legend.py +79 -0
- pptx/chart/marker.py +70 -0
- pptx/chart/plot.py +412 -0
- pptx/chart/point.py +101 -0
- pptx/chart/series.py +258 -0
- pptx/chart/xlsx.py +272 -0
- pptx/chart/xmlwriter.py +1840 -0
- pptx/dml/__init__.py +0 -0
- pptx/dml/chtfmt.py +40 -0
- pptx/dml/color.py +301 -0
- pptx/dml/effect.py +41 -0
- pptx/dml/fill.py +468 -0
- pptx/dml/line.py +100 -0
- pptx/enum/__init__.py +0 -0
- pptx/enum/action.py +71 -0
- pptx/enum/base.py +175 -0
- pptx/enum/chart.py +492 -0
- pptx/enum/dml.py +405 -0
- pptx/enum/lang.py +685 -0
- pptx/enum/shapes.py +1029 -0
- pptx/enum/text.py +371 -0
- pptx/exc.py +23 -0
- pptx/media.py +197 -0
- pptx/opc/__init__.py +0 -0
- pptx/opc/constants.py +331 -0
- pptx/opc/oxml.py +188 -0
- pptx/opc/package.py +762 -0
- pptx/opc/packuri.py +109 -0
- pptx/opc/serialized.py +296 -0
- pptx/opc/shared.py +20 -0
- pptx/opc/spec.py +44 -0
- pptx/oxml/__init__.py +492 -0
- pptx/oxml/action.py +53 -0
- pptx/oxml/chart/__init__.py +0 -0
- pptx/oxml/chart/axis.py +297 -0
- pptx/oxml/chart/chart.py +282 -0
- pptx/oxml/chart/datalabel.py +252 -0
- pptx/oxml/chart/legend.py +72 -0
- pptx/oxml/chart/marker.py +61 -0
- pptx/oxml/chart/plot.py +345 -0
- pptx/oxml/chart/series.py +254 -0
- pptx/oxml/chart/shared.py +219 -0
- pptx/oxml/coreprops.py +288 -0
- pptx/oxml/dml/__init__.py +0 -0
- pptx/oxml/dml/color.py +111 -0
- pptx/oxml/dml/fill.py +197 -0
- pptx/oxml/dml/line.py +12 -0
- pptx/oxml/ns.py +129 -0
- pptx/oxml/presentation.py +130 -0
- pptx/oxml/shapes/__init__.py +19 -0
- pptx/oxml/shapes/autoshape.py +455 -0
- pptx/oxml/shapes/connector.py +107 -0
- pptx/oxml/shapes/graphfrm.py +342 -0
- pptx/oxml/shapes/groupshape.py +280 -0
- pptx/oxml/shapes/picture.py +270 -0
- pptx/oxml/shapes/shared.py +523 -0
- pptx/oxml/simpletypes.py +740 -0
- pptx/oxml/slide.py +347 -0
- pptx/oxml/table.py +588 -0
- pptx/oxml/text.py +748 -0
- pptx/oxml/theme.py +29 -0
- pptx/oxml/xmlchemy.py +717 -0
- pptx/package.py +222 -0
- pptx/parts/__init__.py +0 -0
- pptx/parts/chart.py +95 -0
- pptx/parts/coreprops.py +167 -0
- pptx/parts/embeddedpackage.py +93 -0
- pptx/parts/image.py +275 -0
- pptx/parts/media.py +37 -0
- pptx/parts/presentation.py +126 -0
- pptx/parts/slide.py +297 -0
- pptx/parts/theme.py +13 -0
- pptx/presentation.py +113 -0
- pptx/py.typed +0 -0
- pptx/shapes/__init__.py +26 -0
- pptx/shapes/autoshape.py +355 -0
- pptx/shapes/base.py +293 -0
- pptx/shapes/connector.py +297 -0
- pptx/shapes/freeform.py +337 -0
- pptx/shapes/graphfrm.py +166 -0
- pptx/shapes/group.py +69 -0
- pptx/shapes/picture.py +203 -0
- pptx/shapes/placeholder.py +407 -0
- pptx/shapes/shapetree.py +1190 -0
- pptx/shared.py +82 -0
- pptx/slide.py +577 -0
- pptx/spec.py +632 -0
- pptx/table.py +520 -0
- pptx/templates/default.pptx +0 -0
- pptx/templates/docx-icon.emf +0 -0
- pptx/templates/generic-icon.emf +0 -0
- pptx/templates/notes.xml +23 -0
- pptx/templates/notesMaster.xml +352 -0
- pptx/templates/pptx-icon.emf +0 -0
- pptx/templates/theme.xml +321 -0
- pptx/templates/xlsx-icon.emf +0 -0
- pptx/text/__init__.py +0 -0
- pptx/text/fonts.py +399 -0
- pptx/text/layout.py +325 -0
- pptx/text/text.py +770 -0
- pptx/theme.py +135 -0
- pptx/types.py +36 -0
- pptx/util.py +284 -0
- ps_python_pptx-1.1.0.dist-info/METADATA +75 -0
- ps_python_pptx-1.1.0.dist-info/RECORD +117 -0
- ps_python_pptx-1.1.0.dist-info/WHEEL +5 -0
- ps_python_pptx-1.1.0.dist-info/licenses/LICENSE +20 -0
- 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
|
+
)
|