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/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)
|