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
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Shared oxml objects for charts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pptx.oxml import parse_xml
|
|
6
|
+
from pptx.oxml.ns import nsdecls
|
|
7
|
+
from pptx.oxml.simpletypes import (
|
|
8
|
+
ST_LayoutMode,
|
|
9
|
+
XsdBoolean,
|
|
10
|
+
XsdDouble,
|
|
11
|
+
XsdString,
|
|
12
|
+
XsdUnsignedInt,
|
|
13
|
+
)
|
|
14
|
+
from pptx.oxml.xmlchemy import (
|
|
15
|
+
BaseOxmlElement,
|
|
16
|
+
OptionalAttribute,
|
|
17
|
+
RequiredAttribute,
|
|
18
|
+
ZeroOrOne,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CT_Boolean(BaseOxmlElement):
|
|
23
|
+
"""
|
|
24
|
+
Common complex type used for elements having a True/False value.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
val = OptionalAttribute("val", XsdBoolean, default=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CT_Boolean_Explicit(BaseOxmlElement):
|
|
31
|
+
"""Always spells out the `val` attribute, e.g. `val=1`.
|
|
32
|
+
|
|
33
|
+
At least one boolean element is improperly interpreted by one or more
|
|
34
|
+
versions of PowerPoint. The `c:overlay` element is interpreted as |False|
|
|
35
|
+
when no `val` attribute is present, contrary to the behavior described in
|
|
36
|
+
the schema. A remedy for this is to interpret a missing `val` attribute
|
|
37
|
+
as |True| (consistent with the spec), but always write the attribute
|
|
38
|
+
whenever there is occasion for changing the element.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_val = OptionalAttribute("val", XsdBoolean, default=True)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def val(self):
|
|
45
|
+
return self._val
|
|
46
|
+
|
|
47
|
+
@val.setter
|
|
48
|
+
def val(self, value):
|
|
49
|
+
val_str = "1" if bool(value) is True else "0"
|
|
50
|
+
self.set("val", val_str)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CT_Double(BaseOxmlElement):
|
|
54
|
+
"""
|
|
55
|
+
Used for floating point values.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
val = RequiredAttribute("val", XsdDouble)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CT_Layout(BaseOxmlElement):
|
|
62
|
+
"""
|
|
63
|
+
``<c:layout>`` custom element class
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
manualLayout = ZeroOrOne("c:manualLayout", successors=("c:extLst",))
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def horz_offset(self):
|
|
70
|
+
"""
|
|
71
|
+
The float value in ./c:manualLayout/c:x when
|
|
72
|
+
c:layout/c:manualLayout/c:xMode@val == "factor". 0.0 if that XPath
|
|
73
|
+
expression finds no match.
|
|
74
|
+
"""
|
|
75
|
+
manualLayout = self.manualLayout
|
|
76
|
+
if manualLayout is None:
|
|
77
|
+
return 0.0
|
|
78
|
+
return manualLayout.horz_offset
|
|
79
|
+
|
|
80
|
+
@horz_offset.setter
|
|
81
|
+
def horz_offset(self, offset):
|
|
82
|
+
"""
|
|
83
|
+
Set the value of ./c:manualLayout/c:x@val to *offset* and
|
|
84
|
+
./c:manualLayout/c:xMode@val to "factor". Remove ./c:manualLayout if
|
|
85
|
+
*offset* == 0.
|
|
86
|
+
"""
|
|
87
|
+
if offset == 0.0:
|
|
88
|
+
self._remove_manualLayout()
|
|
89
|
+
return
|
|
90
|
+
manualLayout = self.get_or_add_manualLayout()
|
|
91
|
+
manualLayout.horz_offset = offset
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CT_LayoutMode(BaseOxmlElement):
|
|
95
|
+
"""
|
|
96
|
+
Used for ``<c:xMode>``, ``<c:yMode>``, ``<c:wMode>``, and ``<c:hMode>``
|
|
97
|
+
child elements of CT_ManualLayout.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
val = OptionalAttribute("val", ST_LayoutMode, default=ST_LayoutMode.FACTOR)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class CT_ManualLayout(BaseOxmlElement):
|
|
104
|
+
"""
|
|
105
|
+
``<c:manualLayout>`` custom element class
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
_tag_seq = (
|
|
109
|
+
"c:layoutTarget",
|
|
110
|
+
"c:xMode",
|
|
111
|
+
"c:yMode",
|
|
112
|
+
"c:wMode",
|
|
113
|
+
"c:hMode",
|
|
114
|
+
"c:x",
|
|
115
|
+
"c:y",
|
|
116
|
+
"c:w",
|
|
117
|
+
"c:h",
|
|
118
|
+
"c:extLst",
|
|
119
|
+
)
|
|
120
|
+
xMode = ZeroOrOne("c:xMode", successors=_tag_seq[2:])
|
|
121
|
+
x = ZeroOrOne("c:x", successors=_tag_seq[6:])
|
|
122
|
+
del _tag_seq
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def horz_offset(self):
|
|
126
|
+
"""
|
|
127
|
+
The float value in ./c:x@val when ./c:xMode@val == "factor". 0.0 when
|
|
128
|
+
./c:x is not present or ./c:xMode@val != "factor".
|
|
129
|
+
"""
|
|
130
|
+
x, xMode = self.x, self.xMode
|
|
131
|
+
if x is None or xMode is None or xMode.val != ST_LayoutMode.FACTOR:
|
|
132
|
+
return 0.0
|
|
133
|
+
return x.val
|
|
134
|
+
|
|
135
|
+
@horz_offset.setter
|
|
136
|
+
def horz_offset(self, offset):
|
|
137
|
+
"""
|
|
138
|
+
Set the value of ./c:x@val to *offset* and ./c:xMode@val to "factor".
|
|
139
|
+
"""
|
|
140
|
+
self.get_or_add_xMode().val = ST_LayoutMode.FACTOR
|
|
141
|
+
self.get_or_add_x().val = offset
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class CT_NumFmt(BaseOxmlElement):
|
|
145
|
+
"""
|
|
146
|
+
``<c:numFmt>`` element specifying the formatting for number labels on a
|
|
147
|
+
tick mark or data point.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
formatCode = RequiredAttribute("formatCode", XsdString)
|
|
151
|
+
sourceLinked = OptionalAttribute("sourceLinked", XsdBoolean)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class CT_Title(BaseOxmlElement):
|
|
155
|
+
"""`c:title` custom element class."""
|
|
156
|
+
|
|
157
|
+
_tag_seq = ("c:tx", "c:layout", "c:overlay", "c:spPr", "c:txPr", "c:extLst")
|
|
158
|
+
tx = ZeroOrOne("c:tx", successors=_tag_seq[1:])
|
|
159
|
+
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[4:])
|
|
160
|
+
del _tag_seq
|
|
161
|
+
|
|
162
|
+
def get_or_add_tx_rich(self):
|
|
163
|
+
"""Return `c:tx/c:rich`, newly created if not present.
|
|
164
|
+
|
|
165
|
+
Return the `c:rich` grandchild at `c:tx/c:rich`. Both the `c:tx` and
|
|
166
|
+
`c:rich` elements are created if not already present. Any
|
|
167
|
+
`c:tx/c:strRef` element is removed. (Such an element would contain
|
|
168
|
+
a cell reference for the axis title text in the chart's Excel
|
|
169
|
+
worksheet.)
|
|
170
|
+
"""
|
|
171
|
+
tx = self.get_or_add_tx()
|
|
172
|
+
tx._remove_strRef()
|
|
173
|
+
return tx.get_or_add_rich()
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def tx_rich(self):
|
|
177
|
+
"""Return `c:tx/c:rich` or |None| if not present."""
|
|
178
|
+
richs = self.xpath("c:tx/c:rich")
|
|
179
|
+
if not richs:
|
|
180
|
+
return None
|
|
181
|
+
return richs[0]
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def new_title():
|
|
185
|
+
"""Return "loose" `c:title` element containing default children."""
|
|
186
|
+
return parse_xml(
|
|
187
|
+
"<c:title %s>" " <c:layout/>" ' <c:overlay val="0"/>' "</c:title>" % nsdecls("c")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class CT_Tx(BaseOxmlElement):
|
|
192
|
+
"""
|
|
193
|
+
``<c:tx>`` element containing the text for a label on a data point or
|
|
194
|
+
other chart item.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
strRef = ZeroOrOne("c:strRef")
|
|
198
|
+
rich = ZeroOrOne("c:rich")
|
|
199
|
+
|
|
200
|
+
def _new_rich(self):
|
|
201
|
+
return parse_xml(
|
|
202
|
+
"<c:rich %s>"
|
|
203
|
+
" <a:bodyPr/>"
|
|
204
|
+
" <a:lstStyle/>"
|
|
205
|
+
" <a:p>"
|
|
206
|
+
" <a:pPr>"
|
|
207
|
+
" <a:defRPr/>"
|
|
208
|
+
" </a:pPr>"
|
|
209
|
+
" </a:p>"
|
|
210
|
+
"</c:rich>" % nsdecls("c", "a")
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class CT_UnsignedInt(BaseOxmlElement):
|
|
215
|
+
"""
|
|
216
|
+
``<c:idx>`` element and others.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
val = RequiredAttribute("val", XsdUnsignedInt)
|
pptx/oxml/coreprops.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""lxml custom element classes for core properties-related XML elements."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
import re
|
|
7
|
+
from typing import Callable, cast
|
|
8
|
+
|
|
9
|
+
from lxml.etree import _Element # pyright: ignore[reportPrivateUsage]
|
|
10
|
+
|
|
11
|
+
from pptx.oxml import parse_xml
|
|
12
|
+
from pptx.oxml.ns import nsdecls, qn
|
|
13
|
+
from pptx.oxml.xmlchemy import BaseOxmlElement, ZeroOrOne
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CT_CoreProperties(BaseOxmlElement):
|
|
17
|
+
"""`cp:coreProperties` element.
|
|
18
|
+
|
|
19
|
+
The root element of the Core Properties part stored as `/docProps/core.xml`. Implements many
|
|
20
|
+
of the Dublin Core document metadata elements. String elements resolve to an empty string ('')
|
|
21
|
+
if the element is not present in the XML. String elements are limited in length to 255 unicode
|
|
22
|
+
characters.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
get_or_add_revision: Callable[[], _Element]
|
|
26
|
+
|
|
27
|
+
category = ZeroOrOne("cp:category", successors=())
|
|
28
|
+
contentStatus = ZeroOrOne("cp:contentStatus", successors=())
|
|
29
|
+
created = ZeroOrOne("dcterms:created", successors=())
|
|
30
|
+
creator = ZeroOrOne("dc:creator", successors=())
|
|
31
|
+
description = ZeroOrOne("dc:description", successors=())
|
|
32
|
+
identifier = ZeroOrOne("dc:identifier", successors=())
|
|
33
|
+
keywords = ZeroOrOne("cp:keywords", successors=())
|
|
34
|
+
language = ZeroOrOne("dc:language", successors=())
|
|
35
|
+
lastModifiedBy = ZeroOrOne("cp:lastModifiedBy", successors=())
|
|
36
|
+
lastPrinted = ZeroOrOne("cp:lastPrinted", successors=())
|
|
37
|
+
modified = ZeroOrOne("dcterms:modified", successors=())
|
|
38
|
+
revision: _Element | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
|
|
39
|
+
"cp:revision", successors=()
|
|
40
|
+
)
|
|
41
|
+
subject = ZeroOrOne("dc:subject", successors=())
|
|
42
|
+
title = ZeroOrOne("dc:title", successors=())
|
|
43
|
+
version = ZeroOrOne("cp:version", successors=())
|
|
44
|
+
|
|
45
|
+
_coreProperties_tmpl = "<cp:coreProperties %s/>\n" % nsdecls("cp", "dc", "dcterms")
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def new_coreProperties() -> CT_CoreProperties:
|
|
49
|
+
"""Return a new `cp:coreProperties` element"""
|
|
50
|
+
return cast(CT_CoreProperties, parse_xml(CT_CoreProperties._coreProperties_tmpl))
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def author_text(self) -> str:
|
|
54
|
+
return self._text_of_element("creator")
|
|
55
|
+
|
|
56
|
+
@author_text.setter
|
|
57
|
+
def author_text(self, value: str):
|
|
58
|
+
self._set_element_text("creator", value)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def category_text(self) -> str:
|
|
62
|
+
return self._text_of_element("category")
|
|
63
|
+
|
|
64
|
+
@category_text.setter
|
|
65
|
+
def category_text(self, value: str):
|
|
66
|
+
self._set_element_text("category", value)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def comments_text(self) -> str:
|
|
70
|
+
return self._text_of_element("description")
|
|
71
|
+
|
|
72
|
+
@comments_text.setter
|
|
73
|
+
def comments_text(self, value: str):
|
|
74
|
+
self._set_element_text("description", value)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def contentStatus_text(self) -> str:
|
|
78
|
+
return self._text_of_element("contentStatus")
|
|
79
|
+
|
|
80
|
+
@contentStatus_text.setter
|
|
81
|
+
def contentStatus_text(self, value: str):
|
|
82
|
+
self._set_element_text("contentStatus", value)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def created_datetime(self):
|
|
86
|
+
return self._datetime_of_element("created")
|
|
87
|
+
|
|
88
|
+
@created_datetime.setter
|
|
89
|
+
def created_datetime(self, value: dt.datetime):
|
|
90
|
+
self._set_element_datetime("created", value)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def identifier_text(self) -> str:
|
|
94
|
+
return self._text_of_element("identifier")
|
|
95
|
+
|
|
96
|
+
@identifier_text.setter
|
|
97
|
+
def identifier_text(self, value: str):
|
|
98
|
+
self._set_element_text("identifier", value)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def keywords_text(self) -> str:
|
|
102
|
+
return self._text_of_element("keywords")
|
|
103
|
+
|
|
104
|
+
@keywords_text.setter
|
|
105
|
+
def keywords_text(self, value: str):
|
|
106
|
+
self._set_element_text("keywords", value)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def language_text(self) -> str:
|
|
110
|
+
return self._text_of_element("language")
|
|
111
|
+
|
|
112
|
+
@language_text.setter
|
|
113
|
+
def language_text(self, value: str):
|
|
114
|
+
self._set_element_text("language", value)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def lastModifiedBy_text(self) -> str:
|
|
118
|
+
return self._text_of_element("lastModifiedBy")
|
|
119
|
+
|
|
120
|
+
@lastModifiedBy_text.setter
|
|
121
|
+
def lastModifiedBy_text(self, value: str):
|
|
122
|
+
self._set_element_text("lastModifiedBy", value)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def lastPrinted_datetime(self):
|
|
126
|
+
return self._datetime_of_element("lastPrinted")
|
|
127
|
+
|
|
128
|
+
@lastPrinted_datetime.setter
|
|
129
|
+
def lastPrinted_datetime(self, value: dt.datetime):
|
|
130
|
+
self._set_element_datetime("lastPrinted", value)
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def modified_datetime(self):
|
|
134
|
+
return self._datetime_of_element("modified")
|
|
135
|
+
|
|
136
|
+
@modified_datetime.setter
|
|
137
|
+
def modified_datetime(self, value: dt.datetime):
|
|
138
|
+
self._set_element_datetime("modified", value)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def revision_number(self) -> int:
|
|
142
|
+
"""Integer value of revision property."""
|
|
143
|
+
revision = self.revision
|
|
144
|
+
if revision is None:
|
|
145
|
+
return 0
|
|
146
|
+
revision_str = revision.text
|
|
147
|
+
if revision_str is None:
|
|
148
|
+
return 0
|
|
149
|
+
try:
|
|
150
|
+
revision = int(revision_str)
|
|
151
|
+
except ValueError:
|
|
152
|
+
# -- non-integer revision strings also resolve to 0 --
|
|
153
|
+
return 0
|
|
154
|
+
# -- as do negative integers --
|
|
155
|
+
if revision < 0:
|
|
156
|
+
return 0
|
|
157
|
+
return revision
|
|
158
|
+
|
|
159
|
+
@revision_number.setter
|
|
160
|
+
def revision_number(self, value: int):
|
|
161
|
+
"""Set revision property to string value of integer `value`."""
|
|
162
|
+
if not isinstance(value, int) or value < 1: # pyright: ignore[reportUnnecessaryIsInstance]
|
|
163
|
+
tmpl = "revision property requires positive int, got '%s'"
|
|
164
|
+
raise ValueError(tmpl % value)
|
|
165
|
+
revision = self.get_or_add_revision()
|
|
166
|
+
revision.text = str(value)
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def subject_text(self) -> str:
|
|
170
|
+
return self._text_of_element("subject")
|
|
171
|
+
|
|
172
|
+
@subject_text.setter
|
|
173
|
+
def subject_text(self, value: str):
|
|
174
|
+
self._set_element_text("subject", value)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def title_text(self) -> str:
|
|
178
|
+
return self._text_of_element("title")
|
|
179
|
+
|
|
180
|
+
@title_text.setter
|
|
181
|
+
def title_text(self, value: str):
|
|
182
|
+
self._set_element_text("title", value)
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def version_text(self) -> str:
|
|
186
|
+
return self._text_of_element("version")
|
|
187
|
+
|
|
188
|
+
@version_text.setter
|
|
189
|
+
def version_text(self, value: str):
|
|
190
|
+
self._set_element_text("version", value)
|
|
191
|
+
|
|
192
|
+
def _datetime_of_element(self, property_name: str) -> dt.datetime | None:
|
|
193
|
+
element = cast("_Element | None", getattr(self, property_name))
|
|
194
|
+
if element is None:
|
|
195
|
+
return None
|
|
196
|
+
datetime_str = element.text
|
|
197
|
+
if datetime_str is None:
|
|
198
|
+
return None
|
|
199
|
+
try:
|
|
200
|
+
return self._parse_W3CDTF_to_datetime(datetime_str)
|
|
201
|
+
except ValueError:
|
|
202
|
+
# invalid datetime strings are ignored
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def _get_or_add(self, prop_name: str):
|
|
206
|
+
"""Return element returned by 'get_or_add_' method for `prop_name`."""
|
|
207
|
+
get_or_add_method_name = "get_or_add_%s" % prop_name
|
|
208
|
+
get_or_add_method = getattr(self, get_or_add_method_name)
|
|
209
|
+
element = get_or_add_method()
|
|
210
|
+
return element
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
def _offset_dt(cls, datetime: dt.datetime, offset_str: str):
|
|
214
|
+
"""Return |datetime| instance offset from `datetime` by offset specified in `offset_str`.
|
|
215
|
+
|
|
216
|
+
`offset_str` is a string like `'-07:00'`.
|
|
217
|
+
"""
|
|
218
|
+
match = cls._offset_pattern.match(offset_str)
|
|
219
|
+
if match is None:
|
|
220
|
+
raise ValueError(f"{repr(offset_str)} is not a valid offset string")
|
|
221
|
+
sign, hours_str, minutes_str = match.groups()
|
|
222
|
+
sign_factor = -1 if sign == "+" else 1
|
|
223
|
+
hours = int(hours_str) * sign_factor
|
|
224
|
+
minutes = int(minutes_str) * sign_factor
|
|
225
|
+
td = dt.timedelta(hours=hours, minutes=minutes)
|
|
226
|
+
return datetime + td
|
|
227
|
+
|
|
228
|
+
_offset_pattern = re.compile(r"([+-])(\d\d):(\d\d)")
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def _parse_W3CDTF_to_datetime(cls, w3cdtf_str: str) -> dt.datetime:
|
|
232
|
+
# valid W3CDTF date cases:
|
|
233
|
+
# yyyy e.g. '2003'
|
|
234
|
+
# yyyy-mm e.g. '2003-12'
|
|
235
|
+
# yyyy-mm-dd e.g. '2003-12-31'
|
|
236
|
+
# UTC timezone e.g. '2003-12-31T10:14:55Z'
|
|
237
|
+
# numeric timezone e.g. '2003-12-31T10:14:55-08:00'
|
|
238
|
+
templates = ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%d", "%Y-%m", "%Y")
|
|
239
|
+
# strptime isn't smart enough to parse literal timezone offsets like
|
|
240
|
+
# '-07:30', so we have to do it ourselves
|
|
241
|
+
parseable_part = w3cdtf_str[:19]
|
|
242
|
+
offset_str = w3cdtf_str[19:]
|
|
243
|
+
timestamp = None
|
|
244
|
+
for tmpl in templates:
|
|
245
|
+
try:
|
|
246
|
+
timestamp = dt.datetime.strptime(parseable_part, tmpl)
|
|
247
|
+
except ValueError:
|
|
248
|
+
continue
|
|
249
|
+
if timestamp is None:
|
|
250
|
+
tmpl = "could not parse W3CDTF datetime string '%s'"
|
|
251
|
+
raise ValueError(tmpl % w3cdtf_str)
|
|
252
|
+
if len(offset_str) == 6:
|
|
253
|
+
return cls._offset_dt(timestamp, offset_str)
|
|
254
|
+
return timestamp
|
|
255
|
+
|
|
256
|
+
def _set_element_datetime(self, prop_name: str, value: dt.datetime) -> None:
|
|
257
|
+
"""Set date/time value of child element having `prop_name` to `value`."""
|
|
258
|
+
if not isinstance(value, dt.datetime): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
259
|
+
tmpl = "property requires <type 'datetime.datetime'> object, got %s"
|
|
260
|
+
raise ValueError(tmpl % type(value))
|
|
261
|
+
element = self._get_or_add(prop_name)
|
|
262
|
+
dt_str = value.strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
263
|
+
element.text = dt_str
|
|
264
|
+
if prop_name in ("created", "modified"):
|
|
265
|
+
# These two require an explicit 'xsi:type="dcterms:W3CDTF"'
|
|
266
|
+
# attribute. The first and last line are a hack required to add
|
|
267
|
+
# the xsi namespace to the root element rather than each child
|
|
268
|
+
# element in which it is referenced
|
|
269
|
+
self.set(qn("xsi:foo"), "bar")
|
|
270
|
+
element.set(qn("xsi:type"), "dcterms:W3CDTF")
|
|
271
|
+
del self.attrib[qn("xsi:foo")]
|
|
272
|
+
|
|
273
|
+
def _set_element_text(self, prop_name: str, value: str) -> None:
|
|
274
|
+
"""Set string value of `name` property to `value`."""
|
|
275
|
+
value = str(value)
|
|
276
|
+
if len(value) > 255:
|
|
277
|
+
tmpl = "exceeded 255 char limit for property, got:\n\n'%s'"
|
|
278
|
+
raise ValueError(tmpl % value)
|
|
279
|
+
element = self._get_or_add(prop_name)
|
|
280
|
+
element.text = value
|
|
281
|
+
|
|
282
|
+
def _text_of_element(self, property_name: str) -> str:
|
|
283
|
+
element = getattr(self, property_name)
|
|
284
|
+
if element is None:
|
|
285
|
+
return ""
|
|
286
|
+
if element.text is None:
|
|
287
|
+
return ""
|
|
288
|
+
return element.text
|
|
File without changes
|
pptx/oxml/dml/color.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""lxml custom element classes for DrawingML-related XML elements."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pptx.enum.dml import MSO_THEME_COLOR
|
|
6
|
+
from pptx.oxml.simpletypes import ST_HexColorRGB, ST_Percentage
|
|
7
|
+
from pptx.oxml.xmlchemy import (
|
|
8
|
+
BaseOxmlElement,
|
|
9
|
+
Choice,
|
|
10
|
+
RequiredAttribute,
|
|
11
|
+
ZeroOrOne,
|
|
12
|
+
ZeroOrOneChoice,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _BaseColorElement(BaseOxmlElement):
|
|
17
|
+
"""
|
|
18
|
+
Base class for <a:srgbClr> and <a:schemeClr> elements.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
lumMod = ZeroOrOne("a:lumMod")
|
|
22
|
+
lumOff = ZeroOrOne("a:lumOff")
|
|
23
|
+
|
|
24
|
+
def add_lumMod(self, value):
|
|
25
|
+
"""
|
|
26
|
+
Return a newly added <a:lumMod> child element.
|
|
27
|
+
"""
|
|
28
|
+
lumMod = self._add_lumMod()
|
|
29
|
+
lumMod.val = value
|
|
30
|
+
return lumMod
|
|
31
|
+
|
|
32
|
+
def add_lumOff(self, value):
|
|
33
|
+
"""
|
|
34
|
+
Return a newly added <a:lumOff> child element.
|
|
35
|
+
"""
|
|
36
|
+
lumOff = self._add_lumOff()
|
|
37
|
+
lumOff.val = value
|
|
38
|
+
return lumOff
|
|
39
|
+
|
|
40
|
+
def clear_lum(self):
|
|
41
|
+
"""
|
|
42
|
+
Return self after removing any <a:lumMod> and <a:lumOff> child
|
|
43
|
+
elements.
|
|
44
|
+
"""
|
|
45
|
+
self._remove_lumMod()
|
|
46
|
+
self._remove_lumOff()
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CT_Color(BaseOxmlElement):
|
|
51
|
+
"""Custom element class for `a:fgClr`, `a:bgClr` and perhaps others."""
|
|
52
|
+
|
|
53
|
+
eg_colorChoice = ZeroOrOneChoice(
|
|
54
|
+
(
|
|
55
|
+
Choice("a:scrgbClr"),
|
|
56
|
+
Choice("a:srgbClr"),
|
|
57
|
+
Choice("a:hslClr"),
|
|
58
|
+
Choice("a:sysClr"),
|
|
59
|
+
Choice("a:schemeClr"),
|
|
60
|
+
Choice("a:prstClr"),
|
|
61
|
+
),
|
|
62
|
+
successors=(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CT_HslColor(_BaseColorElement):
|
|
67
|
+
"""
|
|
68
|
+
Custom element class for <a:hslClr> element.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class CT_Percentage(BaseOxmlElement):
|
|
73
|
+
"""
|
|
74
|
+
Custom element class for <a:lumMod> and <a:lumOff> elements.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
val = RequiredAttribute("val", ST_Percentage)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CT_PresetColor(_BaseColorElement):
|
|
81
|
+
"""
|
|
82
|
+
Custom element class for <a:prstClr> element.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CT_SchemeColor(_BaseColorElement):
|
|
87
|
+
"""
|
|
88
|
+
Custom element class for <a:schemeClr> element.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
val = RequiredAttribute("val", MSO_THEME_COLOR)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CT_ScRgbColor(_BaseColorElement):
|
|
95
|
+
"""
|
|
96
|
+
Custom element class for <a:scrgbClr> element.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class CT_SRgbColor(_BaseColorElement):
|
|
101
|
+
"""
|
|
102
|
+
Custom element class for <a:srgbClr> element.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
val = RequiredAttribute("val", ST_HexColorRGB)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CT_SystemColor(_BaseColorElement):
|
|
109
|
+
"""
|
|
110
|
+
Custom element class for <a:sysClr> element.
|
|
111
|
+
"""
|