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/packuri.py ADDED
@@ -0,0 +1,109 @@
1
+ """Provides the PackURI value type.
2
+
3
+ Also some useful known pack URI strings such as PACKAGE_URI.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import posixpath
9
+ import re
10
+
11
+
12
+ class PackURI(str):
13
+ """Provides access to pack URI components such as the baseURI and the filename slice.
14
+
15
+ Behaves as |str| otherwise.
16
+ """
17
+
18
+ _filename_re = re.compile("([a-zA-Z]+)([1-9][0-9]*)?")
19
+
20
+ def __new__(cls, pack_uri_str: str):
21
+ if pack_uri_str[0] != "/":
22
+ tmpl = "PackURI must begin with slash, got '%s'"
23
+ raise ValueError(tmpl % pack_uri_str)
24
+ return str.__new__(cls, pack_uri_str)
25
+
26
+ @staticmethod
27
+ def from_rel_ref(baseURI: str, relative_ref: str) -> PackURI:
28
+ """The absolute PackURI formed by translating `relative_ref` onto `baseURI`."""
29
+ joined_uri = posixpath.join(baseURI, relative_ref)
30
+ abs_uri = posixpath.abspath(joined_uri)
31
+ return PackURI(abs_uri)
32
+
33
+ @property
34
+ def baseURI(self) -> str:
35
+ """The base URI of this pack URI, the directory portion, roughly speaking.
36
+
37
+ E.g. ``'/ppt/slides'`` for ``'/ppt/slides/slide1.xml'``. For the package pseudo-
38
+ partname '/', baseURI is '/'.
39
+ """
40
+ return posixpath.split(self)[0]
41
+
42
+ @property
43
+ def ext(self) -> str:
44
+ """The extension portion of this pack URI, e.g. ``'xml'`` for ``'/word/document.xml'``.
45
+
46
+ Note the period is not included.
47
+ """
48
+ # raw_ext is either empty string or starts with period, e.g. '.xml'
49
+ raw_ext = posixpath.splitext(self)[1]
50
+ return raw_ext[1:] if raw_ext.startswith(".") else raw_ext
51
+
52
+ @property
53
+ def filename(self):
54
+ """The "filename" portion of this pack URI, e.g. ``'slide1.xml'`` for
55
+ ``'/ppt/slides/slide1.xml'``.
56
+
57
+ For the package pseudo-partname '/', filename is ''.
58
+ """
59
+ return posixpath.split(self)[1]
60
+
61
+ @property
62
+ def idx(self):
63
+ """Return partname index as integer for tuple partname or None for singleton
64
+ partname, e.g. ``21`` for ``'/ppt/slides/slide21.xml'`` and |None| for
65
+ ``'/ppt/presentation.xml'``."""
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):
79
+ """The pack URI with the leading slash stripped off, the form used as the Zip
80
+ file membername for the package item.
81
+
82
+ Returns '' for the package pseudo-partname '/'.
83
+ """
84
+ return self[1:]
85
+
86
+ def relative_ref(self, baseURI: 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):
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
101
+ pseudo-partname '/'.
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")
docx/opc/part.py ADDED
@@ -0,0 +1,247 @@
1
+ # pyright: reportImportCycles=false
2
+
3
+ """Open Packaging Convention (OPC) objects related to package parts."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Callable, Type, cast
8
+
9
+ from docx.opc.oxml import serialize_part_xml
10
+ from docx.opc.packuri import PackURI
11
+ from docx.opc.rel import Relationships
12
+ from docx.opc.shared import cls_method_fn
13
+ from docx.oxml.parser import parse_xml
14
+ from docx.shared import lazyproperty
15
+
16
+ if TYPE_CHECKING:
17
+ from docx.oxml.xmlchemy import BaseOxmlElement
18
+ from docx.package import Package
19
+
20
+
21
+ class Part:
22
+ """Base class for package parts.
23
+
24
+ Provides common properties and methods, but intended to be subclassed in client code
25
+ to implement specific part behaviors.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ partname: PackURI,
31
+ content_type: str,
32
+ blob: bytes | None = None,
33
+ package: Package | None = None,
34
+ ):
35
+ super(Part, self).__init__()
36
+ self._partname = partname
37
+ self._content_type = content_type
38
+ self._blob = blob
39
+ self._package = package
40
+
41
+ def after_unmarshal(self):
42
+ """Entry point for post-unmarshaling processing, for example to parse the part
43
+ XML.
44
+
45
+ May be overridden by subclasses without forwarding call to super.
46
+ """
47
+ # don't place any code here, just catch call if not overridden by
48
+ # subclass
49
+ pass
50
+
51
+ def before_marshal(self):
52
+ """Entry point for pre-serialization processing, for example to finalize part
53
+ naming if necessary.
54
+
55
+ May be overridden by subclasses without forwarding call to super.
56
+ """
57
+ # don't place any code here, just catch call if not overridden by
58
+ # subclass
59
+ pass
60
+
61
+ @property
62
+ def blob(self) -> bytes:
63
+ """Contents of this package part as a sequence of bytes.
64
+
65
+ May be text or binary. Intended to be overridden by subclasses. Default behavior
66
+ is to return load blob.
67
+ """
68
+ return self._blob or b""
69
+
70
+ @property
71
+ def content_type(self):
72
+ """Content type of this part."""
73
+ return self._content_type
74
+
75
+ def drop_rel(self, rId: str):
76
+ """Remove the relationship identified by `rId` if its reference count is less
77
+ than 2.
78
+
79
+ Relationships with a reference count of 0 are implicit relationships.
80
+ """
81
+ if self._rel_ref_count(rId) < 2:
82
+ del self.rels[rId]
83
+
84
+ @classmethod
85
+ def load(cls, partname: PackURI, content_type: str, blob: bytes, package: Package):
86
+ return cls(partname, content_type, blob, package)
87
+
88
+ def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False):
89
+ """Return newly added |_Relationship| instance of `reltype`.
90
+
91
+ The new relationship relates the `target` part to this part with key `rId`.
92
+
93
+ Target mode is set to ``RTM.EXTERNAL`` if `is_external` is |True|. Intended for
94
+ use during load from a serialized package, where the rId is well-known. Other
95
+ methods exist for adding a new relationship to a part when manipulating a part.
96
+ """
97
+ return self.rels.add_relationship(reltype, target, rId, is_external)
98
+
99
+ @property
100
+ def package(self):
101
+ """|OpcPackage| instance this part belongs to."""
102
+ return self._package
103
+
104
+ @property
105
+ def partname(self):
106
+ """|PackURI| instance holding partname of this part, e.g.
107
+ '/ppt/slides/slide1.xml'."""
108
+ return self._partname
109
+
110
+ @partname.setter
111
+ def partname(self, partname: str):
112
+ if not isinstance(partname, PackURI):
113
+ tmpl = "partname must be instance of PackURI, got '%s'"
114
+ raise TypeError(tmpl % type(partname).__name__)
115
+ self._partname = partname
116
+
117
+ def part_related_by(self, reltype: str) -> Part:
118
+ """Return part to which this part has a relationship of `reltype`.
119
+
120
+ Raises |KeyError| if no such relationship is found and |ValueError| if more than
121
+ one such relationship is found. Provides ability to resolve implicitly related
122
+ part, such as Slide -> SlideLayout.
123
+ """
124
+ return self.rels.part_with_reltype(reltype)
125
+
126
+ def relate_to(self, target: Part | str, reltype: str, is_external: bool = False) -> str:
127
+ """Return rId key of relationship of `reltype` to `target`.
128
+
129
+ The returned `rId` is from an existing relationship if there is one, otherwise a
130
+ new relationship is created.
131
+ """
132
+ if is_external:
133
+ return self.rels.get_or_add_ext_rel(reltype, cast(str, target))
134
+ else:
135
+ rel = self.rels.get_or_add(reltype, cast(Part, target))
136
+ return rel.rId
137
+
138
+ @property
139
+ def related_parts(self):
140
+ """Dictionary mapping related parts by rId, so child objects can resolve
141
+ explicit relationships present in the part XML, e.g. sldIdLst to a specific
142
+ |Slide| instance."""
143
+ return self.rels.related_parts
144
+
145
+ @lazyproperty
146
+ def rels(self):
147
+ """|Relationships| instance holding the relationships for this part."""
148
+ # -- prevent breakage in `python-docx-template` by retaining legacy `._rels` attribute --
149
+ self._rels = Relationships(self._partname.baseURI)
150
+ return self._rels
151
+
152
+ def target_ref(self, rId: str) -> str:
153
+ """Return URL contained in target ref of relationship identified by `rId`."""
154
+ rel = self.rels[rId]
155
+ return rel.target_ref
156
+
157
+ def _rel_ref_count(self, rId: str) -> int:
158
+ """Return the count of references in this part to the relationship identified by `rId`.
159
+
160
+ Only an XML part can contain references, so this is 0 for `Part`.
161
+ """
162
+ return 0
163
+
164
+
165
+ class PartFactory:
166
+ """Provides a way for client code to specify a subclass of |Part| to be constructed
167
+ by |Unmarshaller| based on its content type and/or a custom callable.
168
+
169
+ Setting ``PartFactory.part_class_selector`` to a callable object will cause that
170
+ object to be called with the parameters ``content_type, reltype``, once for each
171
+ part in the package. If the callable returns an object, it is used as the class for
172
+ that part. If it returns |None|, part class selection falls back to the content type
173
+ map defined in ``PartFactory.part_type_for``. If no class is returned from either of
174
+ these, the class contained in ``PartFactory.default_part_type`` is used to construct
175
+ the part, which is by default ``opc.package.Part``.
176
+ """
177
+
178
+ part_class_selector: Callable[[str, str], Type[Part] | None] | None
179
+ part_type_for: dict[str, Type[Part]] = {}
180
+ default_part_type = Part
181
+
182
+ def __new__(
183
+ cls,
184
+ partname: PackURI,
185
+ content_type: str,
186
+ reltype: str,
187
+ blob: bytes,
188
+ package: Package,
189
+ ):
190
+ PartClass: Type[Part] | None = None
191
+ if cls.part_class_selector is not None:
192
+ part_class_selector = cls_method_fn(cls, "part_class_selector")
193
+ PartClass = part_class_selector(content_type, reltype)
194
+ if PartClass is None:
195
+ PartClass = cls._part_cls_for(content_type)
196
+ return PartClass.load(partname, content_type, blob, package)
197
+
198
+ @classmethod
199
+ def _part_cls_for(cls, content_type: str):
200
+ """Return the custom part class registered for `content_type`, or the default
201
+ part class if no custom class is registered for `content_type`."""
202
+ if content_type in cls.part_type_for:
203
+ return cls.part_type_for[content_type]
204
+ return cls.default_part_type
205
+
206
+
207
+ class XmlPart(Part):
208
+ """Base class for package parts containing an XML payload, which is most of them.
209
+
210
+ Provides additional methods to the |Part| base class that take care of parsing and
211
+ reserializing the XML payload and managing relationships to other parts.
212
+ """
213
+
214
+ def __init__(
215
+ self, partname: PackURI, content_type: str, element: BaseOxmlElement, package: Package
216
+ ):
217
+ super(XmlPart, self).__init__(partname, content_type, package=package)
218
+ self._element = element
219
+
220
+ @property
221
+ def blob(self):
222
+ return serialize_part_xml(self._element)
223
+
224
+ @property
225
+ def element(self):
226
+ """The root XML element of this XML part."""
227
+ return self._element
228
+
229
+ @classmethod
230
+ def load(cls, partname: PackURI, content_type: str, blob: bytes, package: Package):
231
+ element = parse_xml(blob)
232
+ return cls(partname, content_type, element, package)
233
+
234
+ @property
235
+ def part(self):
236
+ """Part of the parent protocol, "children" of the document will not know the
237
+ part that contains them so must ask their parent object.
238
+
239
+ That chain of delegation ends here for child objects.
240
+ """
241
+ return self
242
+
243
+ def _rel_ref_count(self, rId: str) -> int:
244
+ """Return the count of references in this part's XML to the relationship
245
+ identified by `rId`."""
246
+ rIds = cast("list[str]", self._element.xpath("//@r:id"))
247
+ return len([_rId for _rId in rIds if _rId == rId])
File without changes
@@ -0,0 +1,48 @@
1
+ """Core properties part, corresponds to ``/docProps/core.xml`` part in package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime as dt
6
+ from typing import TYPE_CHECKING
7
+
8
+ from docx.opc.constants import CONTENT_TYPE as CT
9
+ from docx.opc.coreprops import CoreProperties
10
+ from docx.opc.packuri import PackURI
11
+ from docx.opc.part import XmlPart
12
+ from docx.oxml.coreprops import CT_CoreProperties
13
+
14
+ if TYPE_CHECKING:
15
+ from docx.opc.package import OpcPackage
16
+
17
+
18
+ class CorePropertiesPart(XmlPart):
19
+ """Corresponds to part named ``/docProps/core.xml``.
20
+
21
+ The "core" is short for "Dublin Core" and contains document metadata relatively common across
22
+ documents of all types, not just DOCX.
23
+ """
24
+
25
+ @classmethod
26
+ def default(cls, package: OpcPackage):
27
+ """Return a new |CorePropertiesPart| object initialized with default values for
28
+ its base properties."""
29
+ core_properties_part = cls._new(package)
30
+ core_properties = core_properties_part.core_properties
31
+ core_properties.title = "Word Document"
32
+ core_properties.last_modified_by = "python-docx"
33
+ core_properties.revision = 1
34
+ core_properties.modified = dt.datetime.now(dt.timezone.utc)
35
+ return core_properties_part
36
+
37
+ @property
38
+ def core_properties(self):
39
+ """A |CoreProperties| object providing read/write access to the core properties
40
+ contained in this core properties part."""
41
+ return CoreProperties(self.element)
42
+
43
+ @classmethod
44
+ def _new(cls, package: OpcPackage) -> CorePropertiesPart:
45
+ partname = PackURI("/docProps/core.xml")
46
+ content_type = CT.OPC_CORE_PROPERTIES
47
+ coreProperties = CT_CoreProperties.new()
48
+ return CorePropertiesPart(partname, content_type, coreProperties, package)
docx/opc/phys_pkg.py ADDED
@@ -0,0 +1,119 @@
1
+ """Provides a general interface to a `physical` OPC package, such as a zip file."""
2
+
3
+ import os
4
+ from zipfile import ZIP_DEFLATED, ZipFile, is_zipfile
5
+
6
+ from docx.opc.exceptions import PackageNotFoundError
7
+ from docx.opc.packuri import CONTENT_TYPES_URI
8
+
9
+
10
+ class PhysPkgReader:
11
+ """Factory for physical package reader objects."""
12
+
13
+ def __new__(cls, pkg_file):
14
+ # if `pkg_file` is a string, treat it as a path
15
+ if isinstance(pkg_file, str):
16
+ if os.path.isdir(pkg_file):
17
+ reader_cls = _DirPkgReader
18
+ elif is_zipfile(pkg_file):
19
+ reader_cls = _ZipPkgReader
20
+ else:
21
+ raise PackageNotFoundError("Package not found at '%s'" % pkg_file)
22
+ else: # assume it's a stream and pass it to Zip reader to sort out
23
+ reader_cls = _ZipPkgReader
24
+
25
+ return super(PhysPkgReader, cls).__new__(reader_cls)
26
+
27
+
28
+ class PhysPkgWriter:
29
+ """Factory for physical package writer objects."""
30
+
31
+ def __new__(cls, pkg_file):
32
+ return super(PhysPkgWriter, cls).__new__(_ZipPkgWriter)
33
+
34
+
35
+ class _DirPkgReader(PhysPkgReader):
36
+ """Implements |PhysPkgReader| interface for an OPC package extracted into a
37
+ directory."""
38
+
39
+ def __init__(self, path):
40
+ """`path` is the path to a directory containing an expanded package."""
41
+ super(_DirPkgReader, self).__init__()
42
+ self._path = os.path.abspath(path)
43
+
44
+ def blob_for(self, pack_uri):
45
+ """Return contents of file corresponding to `pack_uri` in package directory."""
46
+ path = os.path.join(self._path, pack_uri.membername)
47
+ with open(path, "rb") as f:
48
+ blob = f.read()
49
+ return blob
50
+
51
+ def close(self):
52
+ """Provides interface consistency with |ZipFileSystem|, but does nothing, a
53
+ directory file system doesn't need closing."""
54
+ pass
55
+
56
+ @property
57
+ def content_types_xml(self):
58
+ """Return the `[Content_Types].xml` blob from the package."""
59
+ return self.blob_for(CONTENT_TYPES_URI)
60
+
61
+ def rels_xml_for(self, source_uri):
62
+ """Return rels item XML for source with `source_uri`, or None if the item has no
63
+ rels item."""
64
+ try:
65
+ rels_xml = self.blob_for(source_uri.rels_uri)
66
+ except IOError:
67
+ rels_xml = None
68
+ return rels_xml
69
+
70
+
71
+ class _ZipPkgReader(PhysPkgReader):
72
+ """Implements |PhysPkgReader| interface for a zip file OPC package."""
73
+
74
+ def __init__(self, pkg_file):
75
+ super(_ZipPkgReader, self).__init__()
76
+ self._zipf = ZipFile(pkg_file, "r")
77
+
78
+ def blob_for(self, pack_uri):
79
+ """Return blob corresponding to `pack_uri`.
80
+
81
+ Raises |ValueError| if no matching member is present in zip archive.
82
+ """
83
+ return self._zipf.read(pack_uri.membername)
84
+
85
+ def close(self):
86
+ """Close the zip archive, releasing any resources it is using."""
87
+ self._zipf.close()
88
+
89
+ @property
90
+ def content_types_xml(self):
91
+ """Return the `[Content_Types].xml` blob from the zip package."""
92
+ return self.blob_for(CONTENT_TYPES_URI)
93
+
94
+ def rels_xml_for(self, source_uri):
95
+ """Return rels item XML for source with `source_uri` or None if no rels item is
96
+ present."""
97
+ try:
98
+ rels_xml = self.blob_for(source_uri.rels_uri)
99
+ except KeyError:
100
+ rels_xml = None
101
+ return rels_xml
102
+
103
+
104
+ class _ZipPkgWriter(PhysPkgWriter):
105
+ """Implements |PhysPkgWriter| interface for a zip file OPC package."""
106
+
107
+ def __init__(self, pkg_file):
108
+ super(_ZipPkgWriter, self).__init__()
109
+ self._zipf = ZipFile(pkg_file, "w", compression=ZIP_DEFLATED)
110
+
111
+ def close(self):
112
+ """Close the zip archive, flushing any pending physical writes and releasing any
113
+ resources it's using."""
114
+ self._zipf.close()
115
+
116
+ def write(self, pack_uri, blob):
117
+ """Write `blob` to this zip package with the membername corresponding to
118
+ `pack_uri`."""
119
+ self._zipf.writestr(pack_uri.membername, blob)