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.
Files changed (126) hide show
  1. docx/__init__.py +68 -0
  2. docx/api.py +37 -0
  3. docx/blkcntnr.py +101 -0
  4. docx/comments.py +163 -0
  5. docx/dml/__init__.py +0 -0
  6. docx/dml/color.py +112 -0
  7. docx/document.py +275 -0
  8. docx/drawing/__init__.py +59 -0
  9. docx/enum/__init__.py +0 -0
  10. docx/enum/base.py +150 -0
  11. docx/enum/dml.py +103 -0
  12. docx/enum/section.py +86 -0
  13. docx/enum/shape.py +19 -0
  14. docx/enum/style.py +452 -0
  15. docx/enum/table.py +136 -0
  16. docx/enum/text.py +367 -0
  17. docx/exceptions.py +18 -0
  18. docx/image/__init__.py +23 -0
  19. docx/image/bmp.py +43 -0
  20. docx/image/constants.py +172 -0
  21. docx/image/exceptions.py +13 -0
  22. docx/image/gif.py +38 -0
  23. docx/image/helpers.py +86 -0
  24. docx/image/image.py +234 -0
  25. docx/image/jpeg.py +425 -0
  26. docx/image/png.py +253 -0
  27. docx/image/tiff.py +289 -0
  28. docx/opc/__init__.py +0 -0
  29. docx/opc/constants.py +306 -0
  30. docx/opc/coreprops.py +142 -0
  31. docx/opc/exceptions.py +12 -0
  32. docx/opc/oxml.py +247 -0
  33. docx/opc/package.py +219 -0
  34. docx/opc/packuri.py +109 -0
  35. docx/opc/part.py +247 -0
  36. docx/opc/parts/__init__.py +0 -0
  37. docx/opc/parts/coreprops.py +48 -0
  38. docx/opc/phys_pkg.py +119 -0
  39. docx/opc/pkgreader.py +254 -0
  40. docx/opc/pkgwriter.py +115 -0
  41. docx/opc/rel.py +153 -0
  42. docx/opc/shared.py +31 -0
  43. docx/opc/spec.py +24 -0
  44. docx/oxml/__init__.py +261 -0
  45. docx/oxml/comments.py +124 -0
  46. docx/oxml/coreprops.py +298 -0
  47. docx/oxml/document.py +88 -0
  48. docx/oxml/drawing.py +11 -0
  49. docx/oxml/exceptions.py +10 -0
  50. docx/oxml/ns.py +109 -0
  51. docx/oxml/numbering.py +109 -0
  52. docx/oxml/parser.py +62 -0
  53. docx/oxml/section.py +537 -0
  54. docx/oxml/settings.py +138 -0
  55. docx/oxml/shape.py +299 -0
  56. docx/oxml/shared.py +52 -0
  57. docx/oxml/simpletypes.py +434 -0
  58. docx/oxml/styles.py +341 -0
  59. docx/oxml/table.py +977 -0
  60. docx/oxml/text/__init__.py +0 -0
  61. docx/oxml/text/font.py +333 -0
  62. docx/oxml/text/hyperlink.py +45 -0
  63. docx/oxml/text/pagebreak.py +278 -0
  64. docx/oxml/text/paragraph.py +106 -0
  65. docx/oxml/text/parfmt.py +392 -0
  66. docx/oxml/text/run.py +307 -0
  67. docx/oxml/xmlchemy.py +696 -0
  68. docx/package.py +110 -0
  69. docx/parts/__init__.py +0 -0
  70. docx/parts/comments.py +51 -0
  71. docx/parts/document.py +182 -0
  72. docx/parts/hdrftr.py +53 -0
  73. docx/parts/image.py +80 -0
  74. docx/parts/numbering.py +32 -0
  75. docx/parts/settings.py +50 -0
  76. docx/parts/story.py +95 -0
  77. docx/parts/styles.py +42 -0
  78. docx/parts/theme.py +53 -0
  79. docx/py.typed +0 -0
  80. docx/section.py +479 -0
  81. docx/settings.py +35 -0
  82. docx/shape.py +103 -0
  83. docx/shared.py +382 -0
  84. docx/styles/__init__.py +40 -0
  85. docx/styles/latent.py +198 -0
  86. docx/styles/style.py +264 -0
  87. docx/styles/styles.py +147 -0
  88. docx/table.py +537 -0
  89. docx/templates/default-comments.xml +12 -0
  90. docx/templates/default-docx-template/[Content_Types].xml +17 -0
  91. docx/templates/default-docx-template/_rels/.rels +7 -0
  92. docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
  93. docx/templates/default-docx-template/customXml/item1.xml +2 -0
  94. docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
  95. docx/templates/default-docx-template/docProps/app.xml +36 -0
  96. docx/templates/default-docx-template/docProps/core.xml +13 -0
  97. docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
  98. docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
  99. docx/templates/default-docx-template/word/document.xml +11 -0
  100. docx/templates/default-docx-template/word/fontTable.xml +61 -0
  101. docx/templates/default-docx-template/word/numbering.xml +201 -0
  102. docx/templates/default-docx-template/word/settings.xml +53 -0
  103. docx/templates/default-docx-template/word/styles.xml +11844 -0
  104. docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
  105. docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
  106. docx/templates/default-docx-template/word/webSettings.xml +5 -0
  107. docx/templates/default-footer.xml +27 -0
  108. docx/templates/default-header.xml +27 -0
  109. docx/templates/default-settings.xml +26 -0
  110. docx/templates/default-styles.xml +190 -0
  111. docx/templates/default.docx +0 -0
  112. docx/text/__init__.py +0 -0
  113. docx/text/font.py +472 -0
  114. docx/text/hyperlink.py +121 -0
  115. docx/text/pagebreak.py +104 -0
  116. docx/text/paragraph.py +173 -0
  117. docx/text/parfmt.py +286 -0
  118. docx/text/run.py +257 -0
  119. docx/text/tabstops.py +123 -0
  120. docx/theme.py +67 -0
  121. docx/types.py +34 -0
  122. ps_python_docx-1.3.0.dist-info/METADATA +77 -0
  123. ps_python_docx-1.3.0.dist-info/RECORD +126 -0
  124. ps_python_docx-1.3.0.dist-info/WHEEL +5 -0
  125. ps_python_docx-1.3.0.dist-info/licenses/LICENSE +20 -0
  126. 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
+ )