python-pptx2 2.13.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 (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
pptx2/opc/packuri.py ADDED
@@ -0,0 +1,109 @@
1
+ """Provides the PackURI value type and known pack-URI strings such as PACKAGE_URI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import posixpath
6
+ import re
7
+
8
+
9
+ class PackURI(str):
10
+ """Proxy for a pack URI (partname).
11
+
12
+ Provides utility properties the baseURI and the filename slice. Behaves as |str| otherwise.
13
+ """
14
+
15
+ _filename_re = re.compile("([a-zA-Z]+)([0-9][0-9]*)?")
16
+
17
+ def __new__(cls, pack_uri_str: str):
18
+ if not pack_uri_str[0] == "/":
19
+ raise ValueError(f"PackURI must begin with slash, got {repr(pack_uri_str)}")
20
+ return str.__new__(cls, pack_uri_str)
21
+
22
+ @staticmethod
23
+ def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI:
24
+ """Construct an absolute pack URI formed by translating `relative_ref` onto `baseURI`."""
25
+ joined_uri = posixpath.join(baseURI, relative_ref)
26
+ abs_uri = posixpath.abspath(joined_uri)
27
+ return PackURI(abs_uri)
28
+
29
+ @property
30
+ def baseURI(self) -> str:
31
+ """The base URI of this pack URI; the directory portion, roughly speaking.
32
+
33
+ E.g. `"/ppt/slides"` for `"/ppt/slides/slide1.xml"`.
34
+
35
+ For the package pseudo-partname "/", the baseURI is "/".
36
+ """
37
+ return posixpath.split(self)[0]
38
+
39
+ @property
40
+ def ext(self) -> str:
41
+ """The extension portion of this pack URI.
42
+
43
+ E.g. `"xml"` for `"/ppt/slides/slide1.xml"`. Note the leading period is not included.
44
+ """
45
+ # -- raw_ext is either empty string or starts with period, e.g. ".xml" --
46
+ raw_ext = posixpath.splitext(self)[1]
47
+ return raw_ext[1:] if raw_ext.startswith(".") else raw_ext
48
+
49
+ @property
50
+ def filename(self) -> str:
51
+ """The "filename" portion of this pack URI.
52
+
53
+ E.g. `"slide1.xml"` for `"/ppt/slides/slide1.xml"`.
54
+
55
+ For the package pseudo-partname "/", `filename` is ''.
56
+ """
57
+ return posixpath.split(self)[1]
58
+
59
+ @property
60
+ def idx(self) -> int | None:
61
+ """Optional int partname index.
62
+
63
+ Value is an integer for an "array" partname or None for singleton partname, e.g. `21` for
64
+ `"/ppt/slides/slide21.xml"` and |None| for `"/ppt/presentation.xml"`.
65
+ """
66
+ filename = self.filename
67
+ if not filename:
68
+ return None
69
+ name_part = posixpath.splitext(filename)[0] # filename w/ext removed
70
+ match = self._filename_re.match(name_part)
71
+ if match is None:
72
+ return None
73
+ if match.group(2):
74
+ return int(match.group(2))
75
+ return None
76
+
77
+ @property
78
+ def membername(self) -> str:
79
+ """The pack URI with the leading slash stripped off.
80
+
81
+ This is the form used as the Zip file membername for the package item. Returns "" for the
82
+ package pseudo-partname "/".
83
+ """
84
+ return self[1:]
85
+
86
+ def relative_ref(self, baseURI: str) -> str:
87
+ """Return string containing relative reference to package item from `baseURI`.
88
+
89
+ E.g. PackURI("/ppt/slideLayouts/slideLayout1.xml") would return
90
+ "../slideLayouts/slideLayout1.xml" for baseURI "/ppt/slides".
91
+ """
92
+ # workaround for posixpath bug in 2.6, doesn't generate correct
93
+ # relative path when `start` (second) parameter is root ("/")
94
+ return self[1:] if baseURI == "/" else posixpath.relpath(self, baseURI)
95
+
96
+ @property
97
+ def rels_uri(self) -> PackURI:
98
+ """The pack URI of the .rels part corresponding to the current pack URI.
99
+
100
+ Only produces sensible output if the pack URI is a partname or the package pseudo-partname
101
+ "/".
102
+ """
103
+ rels_filename = "%s.rels" % self.filename
104
+ rels_uri_str = posixpath.join(self.baseURI, "_rels", rels_filename)
105
+ return PackURI(rels_uri_str)
106
+
107
+
108
+ PACKAGE_URI = PackURI("/")
109
+ CONTENT_TYPES_URI = PackURI("/[Content_Types].xml")
@@ -0,0 +1,296 @@
1
+ """API for reading/writing serialized Open Packaging Convention (OPC) package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import posixpath
7
+ import zipfile
8
+ from typing import IO, TYPE_CHECKING, Any, Container, Sequence
9
+
10
+ from pptx2.exc import PackageNotFoundError
11
+ from pptx2.opc.constants import CONTENT_TYPE as CT
12
+ from pptx2.opc.oxml import CT_Types, serialize_part_xml
13
+ from pptx2.opc.packuri import CONTENT_TYPES_URI, PACKAGE_URI, PackURI
14
+ from pptx2.opc.shared import CaseInsensitiveDict
15
+ from pptx2.opc.spec import default_content_types
16
+ from pptx2.util import lazyproperty
17
+
18
+ if TYPE_CHECKING:
19
+ from pptx2.opc.package import Part, _Relationships # pyright: ignore[reportPrivateUsage]
20
+
21
+
22
+ class PackageReader(Container[bytes]):
23
+ """Provides access to package-parts of an OPC package with dict semantics.
24
+
25
+ The package may be in zip-format (a .pptx file) or expanded into a directory structure,
26
+ perhaps by unzipping a .pptx file.
27
+ """
28
+
29
+ def __init__(self, pkg_file: str | IO[bytes]):
30
+ self._pkg_file = pkg_file
31
+
32
+ def __contains__(self, pack_uri: object) -> bool:
33
+ """Return True when part identified by `pack_uri` is present in package."""
34
+ return pack_uri in self._blob_reader
35
+
36
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
37
+ """Return bytes for part corresponding to `pack_uri`."""
38
+ return self._blob_reader[pack_uri]
39
+
40
+ def rels_xml_for(self, partname: PackURI) -> bytes | None:
41
+ """Return optional rels item XML for `partname`.
42
+
43
+ Returns `None` if no rels item is present for `partname`. `partname` is a |PackURI|
44
+ instance.
45
+ """
46
+ blob_reader, uri = self._blob_reader, partname.rels_uri
47
+ return blob_reader[uri] if uri in blob_reader else None
48
+
49
+ @lazyproperty
50
+ def _blob_reader(self) -> _PhysPkgReader:
51
+ """|_PhysPkgReader| subtype providing read access to the package file."""
52
+ return _PhysPkgReader.factory(self._pkg_file)
53
+
54
+
55
+ class PackageWriter:
56
+ """Writes a zip-format OPC package to `pkg_file`.
57
+
58
+ `pkg_file` can be either a path to a zip file (a string) or a file-like object. `pkg_rels` is
59
+ the |_Relationships| object containing relationships for the package. `parts` is a sequence of
60
+ |Part| subtype instance to be written to the package.
61
+
62
+ Its single API classmethod is :meth:`write`. This class is not intended to be instantiated.
63
+ """
64
+
65
+ def __init__(self, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]):
66
+ self._pkg_file = pkg_file
67
+ self._pkg_rels = pkg_rels
68
+ self._parts = parts
69
+
70
+ @classmethod
71
+ def write(
72
+ cls, pkg_file: str | IO[bytes], pkg_rels: _Relationships, parts: Sequence[Part]
73
+ ) -> None:
74
+ """Write a physical package (.pptx file) to `pkg_file`.
75
+
76
+ The serialized package contains `pkg_rels` and `parts`, a content-types stream based on
77
+ the content type of each part, and a .rels file for each part that has relationships.
78
+ """
79
+ cls(pkg_file, pkg_rels, parts)._write()
80
+
81
+ def _write(self) -> None:
82
+ """Write physical package (.pptx file)."""
83
+ with _PhysPkgWriter.factory(self._pkg_file) as phys_writer:
84
+ self._write_content_types_stream(phys_writer)
85
+ self._write_pkg_rels(phys_writer)
86
+ self._write_parts(phys_writer)
87
+
88
+ def _write_content_types_stream(self, phys_writer: _PhysPkgWriter) -> None:
89
+ """Write `[Content_Types].xml` part to the physical package.
90
+
91
+ This part must contain an appropriate content type lookup target for each part in the
92
+ package.
93
+ """
94
+ phys_writer.write(
95
+ CONTENT_TYPES_URI,
96
+ serialize_part_xml(_ContentTypesItem.xml_for(self._parts)),
97
+ )
98
+
99
+ def _write_parts(self, phys_writer: _PhysPkgWriter) -> None:
100
+ """Write blob of each part in `parts` to the package.
101
+
102
+ A rels item for each part is also written when the part has relationships.
103
+ """
104
+ for part in self._parts:
105
+ phys_writer.write(part.partname, part.blob)
106
+ if part._rels: # pyright: ignore[reportPrivateUsage]
107
+ phys_writer.write(part.partname.rels_uri, part.rels.xml)
108
+
109
+ def _write_pkg_rels(self, phys_writer: _PhysPkgWriter) -> None:
110
+ """Write the XML rels item for `pkg_rels` ('/_rels/.rels') to the package."""
111
+ phys_writer.write(PACKAGE_URI.rels_uri, self._pkg_rels.xml)
112
+
113
+
114
+ class _PhysPkgReader(Container[PackURI]):
115
+ """Base class for physical package reader objects."""
116
+
117
+ def __contains__(self, item: object) -> bool:
118
+ """Must be implemented by each subclass."""
119
+ raise NotImplementedError( # pragma: no cover
120
+ "`%s` must implement `.__contains__()`" % type(self).__name__
121
+ )
122
+
123
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
124
+ """Blob for part corresponding to `pack_uri`."""
125
+ raise NotImplementedError( # pragma: no cover
126
+ f"`{type(self).__name__}` must implement `.__contains__()`"
127
+ )
128
+
129
+ @classmethod
130
+ def factory(cls, pkg_file: str | IO[bytes]) -> _PhysPkgReader:
131
+ """Return |_PhysPkgReader| subtype instance appropriage for `pkg_file`."""
132
+ # --- for pkg_file other than str, assume it's a stream and pass it to Zip
133
+ # --- reader to sort out
134
+ if not isinstance(pkg_file, str):
135
+ return _ZipPkgReader(pkg_file)
136
+
137
+ # --- otherwise we treat `pkg_file` as a path ---
138
+ if os.path.isdir(pkg_file):
139
+ return _DirPkgReader(pkg_file)
140
+
141
+ if zipfile.is_zipfile(pkg_file):
142
+ return _ZipPkgReader(pkg_file)
143
+
144
+ raise PackageNotFoundError("Package not found at '%s'" % pkg_file)
145
+
146
+
147
+ class _DirPkgReader(_PhysPkgReader):
148
+ """Implements |PhysPkgReader| interface for OPC package extracted into directory.
149
+
150
+ `path` is the path to a directory containing an expanded package.
151
+ """
152
+
153
+ def __init__(self, path: str):
154
+ self._path = os.path.abspath(path)
155
+
156
+ def __contains__(self, pack_uri: object) -> bool:
157
+ """Return True when part identified by `pack_uri` is present in zip archive."""
158
+ if not isinstance(pack_uri, PackURI):
159
+ return False
160
+ return os.path.exists(posixpath.join(self._path, pack_uri.membername))
161
+
162
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
163
+ """Return bytes of file corresponding to `pack_uri` in package directory."""
164
+ path = os.path.join(self._path, pack_uri.membername)
165
+ try:
166
+ with open(path, "rb") as f:
167
+ return f.read()
168
+ except IOError:
169
+ raise KeyError("no member '%s' in package" % pack_uri)
170
+
171
+
172
+ class _ZipPkgReader(_PhysPkgReader):
173
+ """Implements |PhysPkgReader| interface for a zip-file OPC package."""
174
+
175
+ def __init__(self, pkg_file: str | IO[bytes]):
176
+ self._pkg_file = pkg_file
177
+
178
+ def __contains__(self, pack_uri: object) -> bool:
179
+ """Return True when part identified by `pack_uri` is present in zip archive."""
180
+ return pack_uri in self._blobs
181
+
182
+ def __getitem__(self, pack_uri: PackURI) -> bytes:
183
+ """Return bytes for part corresponding to `pack_uri`.
184
+
185
+ Raises |KeyError| if no matching member is present in zip archive.
186
+ """
187
+ if pack_uri not in self._blobs:
188
+ raise KeyError("no member '%s' in package" % pack_uri)
189
+ return self._blobs[pack_uri]
190
+
191
+ @lazyproperty
192
+ def _blobs(self) -> dict[PackURI, bytes]:
193
+ """dict mapping partname to package part binaries."""
194
+ with zipfile.ZipFile(self._pkg_file, "r") as z:
195
+ return {PackURI("/%s" % name): z.read(name) for name in z.namelist()}
196
+
197
+
198
+ class _PhysPkgWriter:
199
+ """Base class for physical package writer objects."""
200
+
201
+ @classmethod
202
+ def factory(cls, pkg_file: str | IO[bytes]) -> _ZipPkgWriter:
203
+ """Return |_PhysPkgWriter| subtype instance appropriage for `pkg_file`.
204
+
205
+ Currently the only subtype is `_ZipPkgWriter`, but a `_DirPkgWriter` could be implemented
206
+ or even a `_StreamPkgWriter`.
207
+ """
208
+ return _ZipPkgWriter(pkg_file)
209
+
210
+ def write(self, pack_uri: PackURI, blob: bytes) -> None:
211
+ """Write `blob` to package with membername corresponding to `pack_uri`."""
212
+ raise NotImplementedError( # pragma: no cover
213
+ f"`{type(self).__name__}` must implement `.write()`"
214
+ )
215
+
216
+
217
+ class _ZipPkgWriter(_PhysPkgWriter):
218
+ """Implements |PhysPkgWriter| interface for a zip-file (.pptx file) OPC package."""
219
+
220
+ def __init__(self, pkg_file: str | IO[bytes]):
221
+ self._pkg_file = pkg_file
222
+
223
+ def __enter__(self) -> _ZipPkgWriter:
224
+ """Enable use as a context-manager. Opening zip for writing happens here."""
225
+ return self
226
+
227
+ def __exit__(self, *exc: list[Any]) -> None:
228
+ """Close the zip archive on exit from context.
229
+
230
+ Closing flushes any pending physical writes and releasing any resources it's using.
231
+ """
232
+ self._zipf.close()
233
+
234
+ def write(self, pack_uri: PackURI, blob: bytes) -> None:
235
+ """Write `blob` to zip package with membername corresponding to `pack_uri`."""
236
+ self._zipf.writestr(pack_uri.membername, blob)
237
+
238
+ @lazyproperty
239
+ def _zipf(self) -> zipfile.ZipFile:
240
+ """`ZipFile` instance open for writing."""
241
+ return zipfile.ZipFile(
242
+ self._pkg_file, "w", compression=zipfile.ZIP_DEFLATED, strict_timestamps=False
243
+ )
244
+
245
+
246
+ class _ContentTypesItem:
247
+ """Composes content-types "part" ([Content_Types].xml) for a collection of parts."""
248
+
249
+ def __init__(self, parts: Sequence[Part]):
250
+ self._parts = parts
251
+
252
+ @classmethod
253
+ def xml_for(cls, parts: Sequence[Part]) -> CT_Types:
254
+ """Return content-types XML mapping each part in `parts` to a content-type.
255
+
256
+ The resulting XML is suitable for storage as `[Content_Types].xml` in an OPC package.
257
+ """
258
+ return cls(parts)._xml
259
+
260
+ @lazyproperty
261
+ def _xml(self) -> CT_Types:
262
+ """lxml.etree._Element containing the content-types item.
263
+
264
+ This XML object is suitable for serialization to the `[Content_Types].xml` item for an OPC
265
+ package. Although the sequence of elements is not strictly significant, as an aid to
266
+ testing and readability Default elements are sorted by extension and Override elements are
267
+ sorted by partname.
268
+ """
269
+ defaults, overrides = self._defaults_and_overrides
270
+ _types_elm = CT_Types.new()
271
+
272
+ for ext, content_type in sorted(defaults.items()):
273
+ _types_elm.add_default(ext, content_type)
274
+ for partname, content_type in sorted(overrides.items()):
275
+ _types_elm.add_override(partname, content_type)
276
+
277
+ return _types_elm
278
+
279
+ @lazyproperty
280
+ def _defaults_and_overrides(self) -> tuple[dict[str, str], dict[PackURI, str]]:
281
+ """pair of dict (defaults, overrides) accounting for all parts.
282
+
283
+ `defaults` is {ext: content_type} and overrides is {partname: content_type}.
284
+ """
285
+ defaults = CaseInsensitiveDict(rels=CT.OPC_RELATIONSHIPS, xml=CT.XML)
286
+ overrides: dict[PackURI, str] = {}
287
+
288
+ for part in self._parts:
289
+ partname, content_type = part.partname, part.content_type
290
+ ext = partname.ext
291
+ if (ext.lower(), content_type) in default_content_types:
292
+ defaults[ext] = content_type
293
+ else:
294
+ overrides[partname] = content_type
295
+
296
+ return defaults, overrides
pptx2/opc/shared.py ADDED
@@ -0,0 +1,20 @@
1
+ """Objects shared by modules in the pptx2.opc sub-package."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class CaseInsensitiveDict(dict):
7
+ """Mapping type like dict except it matches key without respect to case.
8
+
9
+ For example, D['A'] == D['a']. Note this is not general-purpose, just complete
10
+ enough to satisfy opc package needs. It assumes str keys for example.
11
+ """
12
+
13
+ def __contains__(self, key):
14
+ return super(CaseInsensitiveDict, self).__contains__(key.lower())
15
+
16
+ def __getitem__(self, key):
17
+ return super(CaseInsensitiveDict, self).__getitem__(key.lower())
18
+
19
+ def __setitem__(self, key, value):
20
+ return super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
pptx2/opc/spec.py ADDED
@@ -0,0 +1,45 @@
1
+ """Provides mappings that embody aspects of the Open XML spec ISO/IEC 29500."""
2
+
3
+ from pptx2.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
+ ("mov", CT.MOV),
17
+ ("mp4", CT.MP4),
18
+ ("mpg", CT.MPG),
19
+ ("png", CT.PNG),
20
+ ("rels", CT.OPC_RELATIONSHIPS),
21
+ ("tif", CT.TIFF),
22
+ ("tiff", CT.TIFF),
23
+ ("vid", CT.VIDEO),
24
+ ("wdp", CT.MS_PHOTO),
25
+ ("wmf", CT.X_WMF),
26
+ ("wmv", CT.WMV),
27
+ ("xlsx", CT.SML_SHEET),
28
+ ("xml", CT.XML),
29
+ )
30
+
31
+
32
+ image_content_types = {
33
+ "bmp": CT.BMP,
34
+ "emf": CT.X_EMF,
35
+ "gif": CT.GIF,
36
+ "jpe": CT.JPEG,
37
+ "jpeg": CT.JPEG,
38
+ "jpg": CT.JPEG,
39
+ "png": CT.PNG,
40
+ "svg": CT.SVG,
41
+ "tif": CT.TIFF,
42
+ "tiff": CT.TIFF,
43
+ "wdp": CT.MS_PHOTO,
44
+ "wmf": CT.X_WMF,
45
+ }