ps-python-docx 1.3.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.
- docx/__init__.py +68 -0
- docx/api.py +37 -0
- docx/blkcntnr.py +101 -0
- docx/comments.py +163 -0
- docx/dml/__init__.py +0 -0
- docx/dml/color.py +112 -0
- docx/document.py +275 -0
- docx/drawing/__init__.py +59 -0
- docx/enum/__init__.py +0 -0
- docx/enum/base.py +150 -0
- docx/enum/dml.py +103 -0
- docx/enum/section.py +86 -0
- docx/enum/shape.py +19 -0
- docx/enum/style.py +452 -0
- docx/enum/table.py +136 -0
- docx/enum/text.py +367 -0
- docx/exceptions.py +18 -0
- docx/image/__init__.py +23 -0
- docx/image/bmp.py +43 -0
- docx/image/constants.py +172 -0
- docx/image/exceptions.py +13 -0
- docx/image/gif.py +38 -0
- docx/image/helpers.py +86 -0
- docx/image/image.py +234 -0
- docx/image/jpeg.py +425 -0
- docx/image/png.py +253 -0
- docx/image/tiff.py +289 -0
- docx/opc/__init__.py +0 -0
- docx/opc/constants.py +306 -0
- docx/opc/coreprops.py +142 -0
- docx/opc/exceptions.py +12 -0
- docx/opc/oxml.py +247 -0
- docx/opc/package.py +219 -0
- docx/opc/packuri.py +109 -0
- docx/opc/part.py +247 -0
- docx/opc/parts/__init__.py +0 -0
- docx/opc/parts/coreprops.py +48 -0
- docx/opc/phys_pkg.py +119 -0
- docx/opc/pkgreader.py +254 -0
- docx/opc/pkgwriter.py +115 -0
- docx/opc/rel.py +153 -0
- docx/opc/shared.py +31 -0
- docx/opc/spec.py +24 -0
- docx/oxml/__init__.py +261 -0
- docx/oxml/comments.py +124 -0
- docx/oxml/coreprops.py +298 -0
- docx/oxml/document.py +88 -0
- docx/oxml/drawing.py +11 -0
- docx/oxml/exceptions.py +10 -0
- docx/oxml/ns.py +109 -0
- docx/oxml/numbering.py +109 -0
- docx/oxml/parser.py +62 -0
- docx/oxml/section.py +537 -0
- docx/oxml/settings.py +138 -0
- docx/oxml/shape.py +299 -0
- docx/oxml/shared.py +52 -0
- docx/oxml/simpletypes.py +434 -0
- docx/oxml/styles.py +341 -0
- docx/oxml/table.py +977 -0
- docx/oxml/text/__init__.py +0 -0
- docx/oxml/text/font.py +333 -0
- docx/oxml/text/hyperlink.py +45 -0
- docx/oxml/text/pagebreak.py +278 -0
- docx/oxml/text/paragraph.py +106 -0
- docx/oxml/text/parfmt.py +392 -0
- docx/oxml/text/run.py +307 -0
- docx/oxml/xmlchemy.py +696 -0
- docx/package.py +110 -0
- docx/parts/__init__.py +0 -0
- docx/parts/comments.py +51 -0
- docx/parts/document.py +182 -0
- docx/parts/hdrftr.py +53 -0
- docx/parts/image.py +80 -0
- docx/parts/numbering.py +32 -0
- docx/parts/settings.py +50 -0
- docx/parts/story.py +95 -0
- docx/parts/styles.py +42 -0
- docx/parts/theme.py +53 -0
- docx/py.typed +0 -0
- docx/section.py +479 -0
- docx/settings.py +35 -0
- docx/shape.py +103 -0
- docx/shared.py +382 -0
- docx/styles/__init__.py +40 -0
- docx/styles/latent.py +198 -0
- docx/styles/style.py +264 -0
- docx/styles/styles.py +147 -0
- docx/table.py +537 -0
- docx/templates/default-comments.xml +12 -0
- docx/templates/default-docx-template/[Content_Types].xml +17 -0
- docx/templates/default-docx-template/_rels/.rels +7 -0
- docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
- docx/templates/default-docx-template/customXml/item1.xml +2 -0
- docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
- docx/templates/default-docx-template/docProps/app.xml +36 -0
- docx/templates/default-docx-template/docProps/core.xml +13 -0
- docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
- docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
- docx/templates/default-docx-template/word/document.xml +11 -0
- docx/templates/default-docx-template/word/fontTable.xml +61 -0
- docx/templates/default-docx-template/word/numbering.xml +201 -0
- docx/templates/default-docx-template/word/settings.xml +53 -0
- docx/templates/default-docx-template/word/styles.xml +11844 -0
- docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
- docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
- docx/templates/default-docx-template/word/webSettings.xml +5 -0
- docx/templates/default-footer.xml +27 -0
- docx/templates/default-header.xml +27 -0
- docx/templates/default-settings.xml +26 -0
- docx/templates/default-styles.xml +190 -0
- docx/templates/default.docx +0 -0
- docx/text/__init__.py +0 -0
- docx/text/font.py +472 -0
- docx/text/hyperlink.py +121 -0
- docx/text/pagebreak.py +104 -0
- docx/text/paragraph.py +173 -0
- docx/text/parfmt.py +286 -0
- docx/text/run.py +257 -0
- docx/text/tabstops.py +123 -0
- docx/theme.py +67 -0
- docx/types.py +34 -0
- ps_python_docx-1.3.0.dist-info/METADATA +77 -0
- ps_python_docx-1.3.0.dist-info/RECORD +126 -0
- ps_python_docx-1.3.0.dist-info/WHEEL +5 -0
- ps_python_docx-1.3.0.dist-info/licenses/LICENSE +20 -0
- ps_python_docx-1.3.0.dist-info/top_level.txt +1 -0
docx/opc/pkgreader.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""Low-level, read-only API to a serialized Open Packaging Convention (OPC) package."""
|
|
2
|
+
|
|
3
|
+
from docx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
|
|
4
|
+
from docx.opc.oxml import parse_xml
|
|
5
|
+
from docx.opc.packuri import PACKAGE_URI, PackURI
|
|
6
|
+
from docx.opc.phys_pkg import PhysPkgReader
|
|
7
|
+
from docx.opc.shared import CaseInsensitiveDict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PackageReader:
|
|
11
|
+
"""Provides access to the contents of a zip-format OPC package via its
|
|
12
|
+
:attr:`serialized_parts` and :attr:`pkg_srels` attributes."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, content_types, pkg_srels, sparts):
|
|
15
|
+
super(PackageReader, self).__init__()
|
|
16
|
+
self._pkg_srels = pkg_srels
|
|
17
|
+
self._sparts = sparts
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def from_file(pkg_file):
|
|
21
|
+
"""Return a |PackageReader| instance loaded with contents of `pkg_file`."""
|
|
22
|
+
phys_reader = PhysPkgReader(pkg_file)
|
|
23
|
+
content_types = _ContentTypeMap.from_xml(phys_reader.content_types_xml)
|
|
24
|
+
pkg_srels = PackageReader._srels_for(phys_reader, PACKAGE_URI)
|
|
25
|
+
sparts = PackageReader._load_serialized_parts(phys_reader, pkg_srels, content_types)
|
|
26
|
+
phys_reader.close()
|
|
27
|
+
return PackageReader(content_types, pkg_srels, sparts)
|
|
28
|
+
|
|
29
|
+
def iter_sparts(self):
|
|
30
|
+
"""Generate a 4-tuple `(partname, content_type, reltype, blob)` for each of the
|
|
31
|
+
serialized parts in the package."""
|
|
32
|
+
for s in self._sparts:
|
|
33
|
+
yield (s.partname, s.content_type, s.reltype, s.blob)
|
|
34
|
+
|
|
35
|
+
def iter_srels(self):
|
|
36
|
+
"""Generate a 2-tuple `(source_uri, srel)` for each of the relationships in the
|
|
37
|
+
package."""
|
|
38
|
+
for srel in self._pkg_srels:
|
|
39
|
+
yield (PACKAGE_URI, srel)
|
|
40
|
+
for spart in self._sparts:
|
|
41
|
+
for srel in spart.srels:
|
|
42
|
+
yield (spart.partname, srel)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _load_serialized_parts(phys_reader, pkg_srels, content_types):
|
|
46
|
+
"""Return a list of |_SerializedPart| instances corresponding to the parts in
|
|
47
|
+
`phys_reader` accessible by walking the relationship graph starting with
|
|
48
|
+
`pkg_srels`."""
|
|
49
|
+
sparts = []
|
|
50
|
+
part_walker = PackageReader._walk_phys_parts(phys_reader, pkg_srels)
|
|
51
|
+
for partname, blob, reltype, srels in part_walker:
|
|
52
|
+
content_type = content_types[partname]
|
|
53
|
+
spart = _SerializedPart(partname, content_type, reltype, blob, srels)
|
|
54
|
+
sparts.append(spart)
|
|
55
|
+
return tuple(sparts)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _srels_for(phys_reader, source_uri):
|
|
59
|
+
"""Return |_SerializedRelationships| instance populated with relationships for
|
|
60
|
+
source identified by `source_uri`."""
|
|
61
|
+
rels_xml = phys_reader.rels_xml_for(source_uri)
|
|
62
|
+
return _SerializedRelationships.load_from_xml(source_uri.baseURI, rels_xml)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _walk_phys_parts(phys_reader, srels, visited_partnames=None):
|
|
66
|
+
"""Generate a 4-tuple `(partname, blob, reltype, srels)` for each of the parts
|
|
67
|
+
in `phys_reader` by walking the relationship graph rooted at srels."""
|
|
68
|
+
if visited_partnames is None:
|
|
69
|
+
visited_partnames = []
|
|
70
|
+
for srel in srels:
|
|
71
|
+
if srel.is_external:
|
|
72
|
+
continue
|
|
73
|
+
partname = srel.target_partname
|
|
74
|
+
if partname in visited_partnames:
|
|
75
|
+
continue
|
|
76
|
+
visited_partnames.append(partname)
|
|
77
|
+
reltype = srel.reltype
|
|
78
|
+
part_srels = PackageReader._srels_for(phys_reader, partname)
|
|
79
|
+
blob = phys_reader.blob_for(partname)
|
|
80
|
+
yield (partname, blob, reltype, part_srels)
|
|
81
|
+
next_walker = PackageReader._walk_phys_parts(phys_reader, part_srels, visited_partnames)
|
|
82
|
+
for partname, blob, reltype, srels in next_walker:
|
|
83
|
+
yield (partname, blob, reltype, srels)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _ContentTypeMap:
|
|
87
|
+
"""Value type providing dictionary semantics for looking up content type by part
|
|
88
|
+
name, e.g. ``content_type = cti['/ppt/presentation.xml']``."""
|
|
89
|
+
|
|
90
|
+
def __init__(self):
|
|
91
|
+
super(_ContentTypeMap, self).__init__()
|
|
92
|
+
self._overrides = CaseInsensitiveDict()
|
|
93
|
+
self._defaults = CaseInsensitiveDict()
|
|
94
|
+
|
|
95
|
+
def __getitem__(self, partname):
|
|
96
|
+
"""Return content type for part identified by `partname`."""
|
|
97
|
+
if not isinstance(partname, PackURI):
|
|
98
|
+
tmpl = "_ContentTypeMap key must be <type 'PackURI'>, got %s"
|
|
99
|
+
raise KeyError(tmpl % type(partname))
|
|
100
|
+
if partname in self._overrides:
|
|
101
|
+
return self._overrides[partname]
|
|
102
|
+
if partname.ext in self._defaults:
|
|
103
|
+
return self._defaults[partname.ext]
|
|
104
|
+
tmpl = "no content type for partname '%s' in [Content_Types].xml"
|
|
105
|
+
raise KeyError(tmpl % partname)
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def from_xml(content_types_xml):
|
|
109
|
+
"""Return a new |_ContentTypeMap| instance populated with the contents of
|
|
110
|
+
`content_types_xml`."""
|
|
111
|
+
types_elm = parse_xml(content_types_xml)
|
|
112
|
+
ct_map = _ContentTypeMap()
|
|
113
|
+
for o in types_elm.overrides:
|
|
114
|
+
ct_map._add_override(o.partname, o.content_type)
|
|
115
|
+
for d in types_elm.defaults:
|
|
116
|
+
ct_map._add_default(d.extension, d.content_type)
|
|
117
|
+
return ct_map
|
|
118
|
+
|
|
119
|
+
def _add_default(self, extension, content_type):
|
|
120
|
+
"""Add the default mapping of `extension` to `content_type` to this content type
|
|
121
|
+
mapping."""
|
|
122
|
+
self._defaults[extension] = content_type
|
|
123
|
+
|
|
124
|
+
def _add_override(self, partname, content_type):
|
|
125
|
+
"""Add the default mapping of `partname` to `content_type` to this content type
|
|
126
|
+
mapping."""
|
|
127
|
+
self._overrides[partname] = content_type
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _SerializedPart:
|
|
131
|
+
"""Value object for an OPC package part.
|
|
132
|
+
|
|
133
|
+
Provides access to the partname, content type, blob, and serialized relationships
|
|
134
|
+
for the part.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, partname, content_type, reltype, blob, srels):
|
|
138
|
+
super(_SerializedPart, self).__init__()
|
|
139
|
+
self._partname = partname
|
|
140
|
+
self._content_type = content_type
|
|
141
|
+
self._reltype = reltype
|
|
142
|
+
self._blob = blob
|
|
143
|
+
self._srels = srels
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def partname(self):
|
|
147
|
+
return self._partname
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def content_type(self):
|
|
151
|
+
return self._content_type
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def blob(self):
|
|
155
|
+
return self._blob
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def reltype(self):
|
|
159
|
+
"""The referring relationship type of this part."""
|
|
160
|
+
return self._reltype
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def srels(self):
|
|
164
|
+
return self._srels
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class _SerializedRelationship:
|
|
168
|
+
"""Value object representing a serialized relationship in an OPC package.
|
|
169
|
+
|
|
170
|
+
Serialized, in this case, means any target part is referred to via its partname
|
|
171
|
+
rather than a direct link to an in-memory |Part| object.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def __init__(self, baseURI, rel_elm):
|
|
175
|
+
super(_SerializedRelationship, self).__init__()
|
|
176
|
+
self._baseURI = baseURI
|
|
177
|
+
self._rId = rel_elm.rId
|
|
178
|
+
self._reltype = rel_elm.reltype
|
|
179
|
+
self._target_mode = rel_elm.target_mode
|
|
180
|
+
self._target_ref = rel_elm.target_ref
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def is_external(self):
|
|
184
|
+
"""True if target_mode is ``RTM.EXTERNAL``"""
|
|
185
|
+
return self._target_mode == RTM.EXTERNAL
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def reltype(self):
|
|
189
|
+
"""Relationship type, like ``RT.OFFICE_DOCUMENT``"""
|
|
190
|
+
return self._reltype
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def rId(self):
|
|
194
|
+
"""Relationship id, like 'rId9', corresponds to the ``Id`` attribute on the
|
|
195
|
+
``CT_Relationship`` element."""
|
|
196
|
+
return self._rId
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def target_mode(self):
|
|
200
|
+
"""String in ``TargetMode`` attribute of ``CT_Relationship`` element, one of
|
|
201
|
+
``RTM.INTERNAL`` or ``RTM.EXTERNAL``."""
|
|
202
|
+
return self._target_mode
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def target_ref(self):
|
|
206
|
+
"""String in ``Target`` attribute of ``CT_Relationship`` element, a relative
|
|
207
|
+
part reference for internal target mode or an arbitrary URI, e.g. an HTTP URL,
|
|
208
|
+
for external target mode."""
|
|
209
|
+
return self._target_ref
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def target_partname(self):
|
|
213
|
+
"""|PackURI| instance containing partname targeted by this relationship.
|
|
214
|
+
|
|
215
|
+
Raises ``ValueError`` on reference if target_mode is ``'External'``. Use
|
|
216
|
+
:attr:`target_mode` to check before referencing.
|
|
217
|
+
"""
|
|
218
|
+
if self.is_external:
|
|
219
|
+
msg = (
|
|
220
|
+
"target_partname attribute on Relationship is undefined w"
|
|
221
|
+
'here TargetMode == "External"'
|
|
222
|
+
)
|
|
223
|
+
raise ValueError(msg)
|
|
224
|
+
# lazy-load _target_partname attribute
|
|
225
|
+
if not hasattr(self, "_target_partname"):
|
|
226
|
+
self._target_partname = PackURI.from_rel_ref(self._baseURI, self.target_ref)
|
|
227
|
+
return self._target_partname
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class _SerializedRelationships:
|
|
231
|
+
"""Read-only sequence of |_SerializedRelationship| instances corresponding to the
|
|
232
|
+
relationships item XML passed to constructor."""
|
|
233
|
+
|
|
234
|
+
def __init__(self):
|
|
235
|
+
super(_SerializedRelationships, self).__init__()
|
|
236
|
+
self._srels = []
|
|
237
|
+
|
|
238
|
+
def __iter__(self):
|
|
239
|
+
"""Support iteration, e.g. 'for x in srels:'."""
|
|
240
|
+
return self._srels.__iter__()
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def load_from_xml(baseURI, rels_item_xml):
|
|
244
|
+
"""Return |_SerializedRelationships| instance loaded with the relationships
|
|
245
|
+
contained in `rels_item_xml`.
|
|
246
|
+
|
|
247
|
+
Returns an empty collection if `rels_item_xml` is |None|.
|
|
248
|
+
"""
|
|
249
|
+
srels = _SerializedRelationships()
|
|
250
|
+
if rels_item_xml is not None:
|
|
251
|
+
rels_elm = parse_xml(rels_item_xml)
|
|
252
|
+
for rel_elm in rels_elm.Relationship_lst:
|
|
253
|
+
srels._srels.append(_SerializedRelationship(baseURI, rel_elm))
|
|
254
|
+
return srels
|
docx/opc/pkgwriter.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Provides low-level, write-only API to serialized (OPC) package.
|
|
2
|
+
|
|
3
|
+
OPC stands for Open Packaging Convention. This is e, essentially an implementation of
|
|
4
|
+
OpcPackage.save().
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Iterable
|
|
10
|
+
|
|
11
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
12
|
+
from docx.opc.oxml import CT_Types, serialize_part_xml
|
|
13
|
+
from docx.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI
|
|
14
|
+
from docx.opc.phys_pkg import PhysPkgWriter
|
|
15
|
+
from docx.opc.shared import CaseInsensitiveDict
|
|
16
|
+
from docx.opc.spec import default_content_types
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from docx.opc.part import Part
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PackageWriter:
|
|
23
|
+
"""Writes a zip-format OPC package to `pkg_file`, where `pkg_file` can be either a
|
|
24
|
+
path to a zip file (a string) or a file-like object.
|
|
25
|
+
|
|
26
|
+
Its single API method, :meth:`write`, is static, so this class is not intended to be
|
|
27
|
+
instantiated.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def write(pkg_file, pkg_rels, parts):
|
|
32
|
+
"""Write a physical package (.pptx file) to `pkg_file` containing `pkg_rels` and
|
|
33
|
+
`parts` and a content types stream based on the content types of the parts."""
|
|
34
|
+
phys_writer = PhysPkgWriter(pkg_file)
|
|
35
|
+
PackageWriter._write_content_types_stream(phys_writer, parts)
|
|
36
|
+
PackageWriter._write_pkg_rels(phys_writer, pkg_rels)
|
|
37
|
+
PackageWriter._write_parts(phys_writer, parts)
|
|
38
|
+
phys_writer.close()
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _write_content_types_stream(phys_writer, parts):
|
|
42
|
+
"""Write ``[Content_Types].xml`` part to the physical package with an
|
|
43
|
+
appropriate content type lookup target for each part in `parts`."""
|
|
44
|
+
cti = _ContentTypesItem.from_parts(parts)
|
|
45
|
+
phys_writer.write(CONTENT_TYPES_URI, cti.blob)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _write_parts(phys_writer: PhysPkgWriter, parts: Iterable[Part]):
|
|
49
|
+
"""Write the blob of each part in `parts` to the package, along with a rels item
|
|
50
|
+
for its relationships if and only if it has any."""
|
|
51
|
+
for part in parts:
|
|
52
|
+
phys_writer.write(part.partname, part.blob)
|
|
53
|
+
if len(part.rels):
|
|
54
|
+
phys_writer.write(part.partname.rels_uri, part.rels.xml)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _write_pkg_rels(phys_writer, pkg_rels):
|
|
58
|
+
"""Write the XML rels item for `pkg_rels` ('/_rels/.rels') to the package."""
|
|
59
|
+
phys_writer.write(PACKAGE_URI.rels_uri, pkg_rels.xml)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _ContentTypesItem:
|
|
63
|
+
"""Service class that composes a content types item ([Content_Types].xml) based on a
|
|
64
|
+
list of parts.
|
|
65
|
+
|
|
66
|
+
Not meant to be instantiated directly, its single interface method is xml_for(),
|
|
67
|
+
e.g. ``_ContentTypesItem.xml_for(parts)``.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
self._defaults = CaseInsensitiveDict()
|
|
72
|
+
self._overrides = {}
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def blob(self):
|
|
76
|
+
"""Return XML form of this content types item, suitable for storage as
|
|
77
|
+
``[Content_Types].xml`` in an OPC package."""
|
|
78
|
+
return serialize_part_xml(self._element)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_parts(cls, parts):
|
|
82
|
+
"""Return content types XML mapping each part in `parts` to the appropriate
|
|
83
|
+
content type and suitable for storage as ``[Content_Types].xml`` in an OPC
|
|
84
|
+
package."""
|
|
85
|
+
cti = cls()
|
|
86
|
+
cti._defaults["rels"] = CT.OPC_RELATIONSHIPS
|
|
87
|
+
cti._defaults["xml"] = CT.XML
|
|
88
|
+
for part in parts:
|
|
89
|
+
cti._add_content_type(part.partname, part.content_type)
|
|
90
|
+
return cti
|
|
91
|
+
|
|
92
|
+
def _add_content_type(self, partname, content_type):
|
|
93
|
+
"""Add a content type for the part with `partname` and `content_type`, using a
|
|
94
|
+
default or override as appropriate."""
|
|
95
|
+
ext = partname.ext
|
|
96
|
+
if (ext.lower(), content_type) in default_content_types:
|
|
97
|
+
self._defaults[ext] = content_type
|
|
98
|
+
else:
|
|
99
|
+
self._overrides[partname] = content_type
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def _element(self):
|
|
103
|
+
"""Return XML form of this content types item, suitable for storage as
|
|
104
|
+
``[Content_Types].xml`` in an OPC package.
|
|
105
|
+
|
|
106
|
+
Although the sequence of elements is not strictly significant, as an aid to
|
|
107
|
+
testing and readability Default elements are sorted by extension and Override
|
|
108
|
+
elements are sorted by partname.
|
|
109
|
+
"""
|
|
110
|
+
_types_elm = CT_Types.new()
|
|
111
|
+
for ext in sorted(self._defaults.keys()):
|
|
112
|
+
_types_elm.add_default(ext, self._defaults[ext])
|
|
113
|
+
for partname in sorted(self._overrides.keys()):
|
|
114
|
+
_types_elm.add_override(partname, self._overrides[partname])
|
|
115
|
+
return _types_elm
|
docx/opc/rel.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Relationship-related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, cast
|
|
6
|
+
|
|
7
|
+
from docx.opc.oxml import CT_Relationships
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from docx.opc.part import Part
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Relationships(Dict[str, "_Relationship"]):
|
|
14
|
+
"""Collection object for |_Relationship| instances, having list semantics."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, baseURI: str):
|
|
17
|
+
super(Relationships, self).__init__()
|
|
18
|
+
self._baseURI = baseURI
|
|
19
|
+
self._target_parts_by_rId: dict[str, Any] = {}
|
|
20
|
+
|
|
21
|
+
def add_relationship(
|
|
22
|
+
self, reltype: str, target: Part | str, rId: str, is_external: bool = False
|
|
23
|
+
) -> "_Relationship":
|
|
24
|
+
"""Return a newly added |_Relationship| instance."""
|
|
25
|
+
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
|
|
26
|
+
self[rId] = rel
|
|
27
|
+
if not is_external:
|
|
28
|
+
self._target_parts_by_rId[rId] = target
|
|
29
|
+
return rel
|
|
30
|
+
|
|
31
|
+
def get_or_add(self, reltype: str, target_part: Part) -> _Relationship:
|
|
32
|
+
"""Return relationship of `reltype` to `target_part`, newly added if not already
|
|
33
|
+
present in collection."""
|
|
34
|
+
rel = self._get_matching(reltype, target_part)
|
|
35
|
+
if rel is None:
|
|
36
|
+
rId = self._next_rId
|
|
37
|
+
rel = self.add_relationship(reltype, target_part, rId)
|
|
38
|
+
return rel
|
|
39
|
+
|
|
40
|
+
def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str:
|
|
41
|
+
"""Return rId of external relationship of `reltype` to `target_ref`, newly added
|
|
42
|
+
if not already present in collection."""
|
|
43
|
+
rel = self._get_matching(reltype, target_ref, is_external=True)
|
|
44
|
+
if rel is None:
|
|
45
|
+
rId = self._next_rId
|
|
46
|
+
rel = self.add_relationship(reltype, target_ref, rId, is_external=True)
|
|
47
|
+
return rel.rId
|
|
48
|
+
|
|
49
|
+
def part_with_reltype(self, reltype: str) -> Part:
|
|
50
|
+
"""Return target part of rel with matching `reltype`, raising |KeyError| if not
|
|
51
|
+
found and |ValueError| if more than one matching relationship is found."""
|
|
52
|
+
rel = self._get_rel_of_type(reltype)
|
|
53
|
+
return rel.target_part
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def related_parts(self):
|
|
57
|
+
"""Dict mapping rIds to target parts for all the internal relationships in the
|
|
58
|
+
collection."""
|
|
59
|
+
return self._target_parts_by_rId
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def xml(self) -> str:
|
|
63
|
+
"""Serialize this relationship collection into XML suitable for storage as a
|
|
64
|
+
.rels file in an OPC package."""
|
|
65
|
+
rels_elm = CT_Relationships.new()
|
|
66
|
+
for rel in self.values():
|
|
67
|
+
rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external)
|
|
68
|
+
return rels_elm.xml
|
|
69
|
+
|
|
70
|
+
def _get_matching(
|
|
71
|
+
self, reltype: str, target: Part | str, is_external: bool = False
|
|
72
|
+
) -> _Relationship | None:
|
|
73
|
+
"""Return relationship of matching `reltype`, `target`, and `is_external` from
|
|
74
|
+
collection, or None if not found."""
|
|
75
|
+
|
|
76
|
+
def matches(rel: _Relationship, reltype: str, target: Part | str, is_external: bool):
|
|
77
|
+
if rel.reltype != reltype:
|
|
78
|
+
return False
|
|
79
|
+
if rel.is_external != is_external:
|
|
80
|
+
return False
|
|
81
|
+
rel_target = rel.target_ref if rel.is_external else rel.target_part
|
|
82
|
+
return rel_target == target
|
|
83
|
+
|
|
84
|
+
for rel in self.values():
|
|
85
|
+
if matches(rel, reltype, target, is_external):
|
|
86
|
+
return rel
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def _get_rel_of_type(self, reltype: str):
|
|
90
|
+
"""Return single relationship of type `reltype` from the collection.
|
|
91
|
+
|
|
92
|
+
Raises |KeyError| if no matching relationship is found. Raises |ValueError| if
|
|
93
|
+
more than one matching relationship is found.
|
|
94
|
+
"""
|
|
95
|
+
matching = [rel for rel in self.values() if rel.reltype == reltype]
|
|
96
|
+
if len(matching) == 0:
|
|
97
|
+
tmpl = "no relationship of type '%s' in collection"
|
|
98
|
+
raise KeyError(tmpl % reltype)
|
|
99
|
+
if len(matching) > 1:
|
|
100
|
+
tmpl = "multiple relationships of type '%s' in collection"
|
|
101
|
+
raise ValueError(tmpl % reltype)
|
|
102
|
+
return matching[0]
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def _next_rId(self) -> str: # pyright: ignore[reportReturnType]
|
|
106
|
+
"""Next available rId in collection, starting from 'rId1' and making use of any
|
|
107
|
+
gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3']."""
|
|
108
|
+
for n in range(1, len(self) + 2):
|
|
109
|
+
rId_candidate = "rId%d" % n # like 'rId19'
|
|
110
|
+
if rId_candidate not in self:
|
|
111
|
+
return rId_candidate
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class _Relationship:
|
|
115
|
+
"""Value object for relationship to part."""
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self, rId: str, reltype: str, target: Part | str, baseURI: str, external: bool = False
|
|
119
|
+
):
|
|
120
|
+
super(_Relationship, self).__init__()
|
|
121
|
+
self._rId = rId
|
|
122
|
+
self._reltype = reltype
|
|
123
|
+
self._target = target
|
|
124
|
+
self._baseURI = baseURI
|
|
125
|
+
self._is_external = bool(external)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_external(self) -> bool:
|
|
129
|
+
return self._is_external
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def reltype(self) -> str:
|
|
133
|
+
return self._reltype
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def rId(self) -> str:
|
|
137
|
+
return self._rId
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def target_part(self) -> Part:
|
|
141
|
+
if self._is_external:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"target_part property on _Relationship is undefined when target mode is External"
|
|
144
|
+
)
|
|
145
|
+
return cast("Part", self._target)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def target_ref(self) -> str:
|
|
149
|
+
if self._is_external:
|
|
150
|
+
return cast(str, self._target)
|
|
151
|
+
else:
|
|
152
|
+
target = cast("Part", self._target)
|
|
153
|
+
return target.partname.relative_ref(self._baseURI)
|
docx/opc/shared.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Objects shared by opc modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, TypeVar
|
|
6
|
+
|
|
7
|
+
_T = TypeVar("_T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CaseInsensitiveDict(Dict[str, Any]):
|
|
11
|
+
"""Mapping type that behaves like dict except that it matches without respect to the
|
|
12
|
+
case of the key.
|
|
13
|
+
|
|
14
|
+
E.g. cid['A'] == cid['a']. Note this is not general-purpose, just complete enough to
|
|
15
|
+
satisfy opc package needs. It assumes str keys, and that it is created empty; keys
|
|
16
|
+
passed in constructor are not accounted for
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __contains__(self, key):
|
|
20
|
+
return super(CaseInsensitiveDict, self).__contains__(key.lower())
|
|
21
|
+
|
|
22
|
+
def __getitem__(self, key):
|
|
23
|
+
return super(CaseInsensitiveDict, self).__getitem__(key.lower())
|
|
24
|
+
|
|
25
|
+
def __setitem__(self, key, value):
|
|
26
|
+
return super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cls_method_fn(cls: type, method_name: str):
|
|
30
|
+
"""Return method of `cls` having `method_name`."""
|
|
31
|
+
return getattr(cls, method_name)
|
docx/opc/spec.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500."""
|
|
2
|
+
|
|
3
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
4
|
+
|
|
5
|
+
default_content_types = (
|
|
6
|
+
("bin", CT.PML_PRINTER_SETTINGS),
|
|
7
|
+
("bin", CT.SML_PRINTER_SETTINGS),
|
|
8
|
+
("bin", CT.WML_PRINTER_SETTINGS),
|
|
9
|
+
("bmp", CT.BMP),
|
|
10
|
+
("emf", CT.X_EMF),
|
|
11
|
+
("fntdata", CT.X_FONTDATA),
|
|
12
|
+
("gif", CT.GIF),
|
|
13
|
+
("jpe", CT.JPEG),
|
|
14
|
+
("jpeg", CT.JPEG),
|
|
15
|
+
("jpg", CT.JPEG),
|
|
16
|
+
("png", CT.PNG),
|
|
17
|
+
("rels", CT.OPC_RELATIONSHIPS),
|
|
18
|
+
("tif", CT.TIFF),
|
|
19
|
+
("tiff", CT.TIFF),
|
|
20
|
+
("wdp", CT.MS_PHOTO),
|
|
21
|
+
("wmf", CT.X_WMF),
|
|
22
|
+
("xlsx", CT.SML_SHEET),
|
|
23
|
+
("xml", CT.XML),
|
|
24
|
+
)
|