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/coreprops.py ADDED
@@ -0,0 +1,142 @@
1
+ """Provides CoreProperties, Dublin-Core attributes of the document.
2
+
3
+ These are broadly-standardized attributes like author, last-modified, etc.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import datetime as dt
9
+ from typing import TYPE_CHECKING
10
+
11
+ from docx.oxml.coreprops import CT_CoreProperties
12
+
13
+ if TYPE_CHECKING:
14
+ from docx.oxml.coreprops import CT_CoreProperties
15
+
16
+
17
+ class CoreProperties:
18
+ """Corresponds to part named ``/docProps/core.xml``, containing the core document
19
+ properties for this document package."""
20
+
21
+ def __init__(self, element: CT_CoreProperties):
22
+ self._element = element
23
+
24
+ @property
25
+ def author(self):
26
+ return self._element.author_text
27
+
28
+ @author.setter
29
+ def author(self, value: str):
30
+ self._element.author_text = value
31
+
32
+ @property
33
+ def category(self):
34
+ return self._element.category_text
35
+
36
+ @category.setter
37
+ def category(self, value: str):
38
+ self._element.category_text = value
39
+
40
+ @property
41
+ def comments(self):
42
+ return self._element.comments_text
43
+
44
+ @comments.setter
45
+ def comments(self, value: str):
46
+ self._element.comments_text = value
47
+
48
+ @property
49
+ def content_status(self):
50
+ return self._element.contentStatus_text
51
+
52
+ @content_status.setter
53
+ def content_status(self, value: str):
54
+ self._element.contentStatus_text = value
55
+
56
+ @property
57
+ def created(self):
58
+ return self._element.created_datetime
59
+
60
+ @created.setter
61
+ def created(self, value: dt.datetime):
62
+ self._element.created_datetime = value
63
+
64
+ @property
65
+ def identifier(self):
66
+ return self._element.identifier_text
67
+
68
+ @identifier.setter
69
+ def identifier(self, value: str):
70
+ self._element.identifier_text = value
71
+
72
+ @property
73
+ def keywords(self):
74
+ return self._element.keywords_text
75
+
76
+ @keywords.setter
77
+ def keywords(self, value: str):
78
+ self._element.keywords_text = value
79
+
80
+ @property
81
+ def language(self):
82
+ return self._element.language_text
83
+
84
+ @language.setter
85
+ def language(self, value: str):
86
+ self._element.language_text = value
87
+
88
+ @property
89
+ def last_modified_by(self):
90
+ return self._element.lastModifiedBy_text
91
+
92
+ @last_modified_by.setter
93
+ def last_modified_by(self, value: str):
94
+ self._element.lastModifiedBy_text = value
95
+
96
+ @property
97
+ def last_printed(self):
98
+ return self._element.lastPrinted_datetime
99
+
100
+ @last_printed.setter
101
+ def last_printed(self, value: dt.datetime):
102
+ self._element.lastPrinted_datetime = value
103
+
104
+ @property
105
+ def modified(self):
106
+ return self._element.modified_datetime
107
+
108
+ @modified.setter
109
+ def modified(self, value: dt.datetime):
110
+ self._element.modified_datetime = value
111
+
112
+ @property
113
+ def revision(self):
114
+ return self._element.revision_number
115
+
116
+ @revision.setter
117
+ def revision(self, value: int):
118
+ self._element.revision_number = value
119
+
120
+ @property
121
+ def subject(self):
122
+ return self._element.subject_text
123
+
124
+ @subject.setter
125
+ def subject(self, value: str):
126
+ self._element.subject_text = value
127
+
128
+ @property
129
+ def title(self):
130
+ return self._element.title_text
131
+
132
+ @title.setter
133
+ def title(self, value: str):
134
+ self._element.title_text = value
135
+
136
+ @property
137
+ def version(self):
138
+ return self._element.version_text
139
+
140
+ @version.setter
141
+ def version(self, value: str):
142
+ self._element.version_text = value
docx/opc/exceptions.py ADDED
@@ -0,0 +1,12 @@
1
+ """Exceptions specific to python-opc.
2
+
3
+ The base exception class is OpcError.
4
+ """
5
+
6
+
7
+ class OpcError(Exception):
8
+ """Base error class for python-opc."""
9
+
10
+
11
+ class PackageNotFoundError(OpcError):
12
+ """Raised when a package cannot be found at the specified path."""
docx/opc/oxml.py ADDED
@@ -0,0 +1,247 @@
1
+ # pyright: reportPrivateUsage=false
2
+
3
+ """Temporary stand-in for main oxml module.
4
+
5
+ This module came across with the PackageReader transplant. Probably much will get
6
+ replaced with objects from the pptx.oxml.core and then this module will either get
7
+ deleted or only hold the package related custom element classes.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import cast
13
+
14
+ from lxml import etree
15
+
16
+ from docx.opc.constants import NAMESPACE as NS
17
+ from docx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
18
+
19
+ # configure XML parser
20
+ element_class_lookup = etree.ElementNamespaceClassLookup()
21
+ oxml_parser = etree.XMLParser(remove_blank_text=True, resolve_entities=False)
22
+ oxml_parser.set_element_class_lookup(element_class_lookup)
23
+
24
+ nsmap = {
25
+ "ct": NS.OPC_CONTENT_TYPES,
26
+ "pr": NS.OPC_RELATIONSHIPS,
27
+ "r": NS.OFC_RELATIONSHIPS,
28
+ }
29
+
30
+
31
+ # ===========================================================================
32
+ # functions
33
+ # ===========================================================================
34
+
35
+
36
+ def parse_xml(text: str) -> etree._Element:
37
+ """`etree.fromstring()` replacement that uses oxml parser."""
38
+ return etree.fromstring(text, oxml_parser)
39
+
40
+
41
+ def qn(tag: str) -> str:
42
+ """Stands for "qualified name", a utility function to turn a namespace prefixed tag
43
+ name into a Clark-notation qualified tag name for lxml.
44
+
45
+ For
46
+ example, ``qn('p:cSld')`` returns ``'{http://schemas.../main}cSld'``.
47
+ """
48
+ prefix, tagroot = tag.split(":")
49
+ uri = nsmap[prefix]
50
+ return "{%s}%s" % (uri, tagroot)
51
+
52
+
53
+ def serialize_part_xml(part_elm: etree._Element) -> bytes:
54
+ """Serialize `part_elm` etree element to XML suitable for storage as an XML part.
55
+
56
+ That is to say, no insignificant whitespace added for readability, and an
57
+ appropriate XML declaration added with UTF-8 encoding specified.
58
+ """
59
+ return etree.tostring(part_elm, encoding="UTF-8", standalone=True)
60
+
61
+
62
+ def serialize_for_reading(element: etree._Element) -> str:
63
+ """Serialize `element` to human-readable XML suitable for tests.
64
+
65
+ No XML declaration.
66
+ """
67
+ return etree.tostring(element, encoding="unicode", pretty_print=True)
68
+
69
+
70
+ # ===========================================================================
71
+ # Custom element classes
72
+ # ===========================================================================
73
+
74
+
75
+ class BaseOxmlElement(etree.ElementBase):
76
+ """Base class for all custom element classes, to add standardized behavior to all
77
+ classes in one place."""
78
+
79
+ @property
80
+ def xml(self) -> str:
81
+ """Return XML string for this element, suitable for testing purposes.
82
+
83
+ Pretty printed for readability and without an XML declaration at the top.
84
+ """
85
+ return serialize_for_reading(self)
86
+
87
+
88
+ class CT_Default(BaseOxmlElement):
89
+ """`<Default>` element that appears in `[Content_Types].xml` part.
90
+
91
+ Used to specify a default content type to be applied to any part with the specified extension.
92
+ """
93
+
94
+ @property
95
+ def content_type(self):
96
+ """String held in the ``ContentType`` attribute of this ``<Default>``
97
+ element."""
98
+ return self.get("ContentType")
99
+
100
+ @property
101
+ def extension(self):
102
+ """String held in the ``Extension`` attribute of this ``<Default>`` element."""
103
+ return self.get("Extension")
104
+
105
+ @staticmethod
106
+ def new(ext: str, content_type: str):
107
+ """Return a new ``<Default>`` element with attributes set to parameter values."""
108
+ xml = '<Default xmlns="%s"/>' % nsmap["ct"]
109
+ default = parse_xml(xml)
110
+ default.set("Extension", ext)
111
+ default.set("ContentType", content_type)
112
+ return default
113
+
114
+
115
+ class CT_Override(BaseOxmlElement):
116
+ """``<Override>`` element, specifying the content type to be applied for a part with
117
+ the specified partname."""
118
+
119
+ @property
120
+ def content_type(self):
121
+ """String held in the ``ContentType`` attribute of this ``<Override>``
122
+ element."""
123
+ return self.get("ContentType")
124
+
125
+ @staticmethod
126
+ def new(partname, content_type):
127
+ """Return a new ``<Override>`` element with attributes set to parameter values."""
128
+ xml = '<Override xmlns="%s"/>' % nsmap["ct"]
129
+ override = parse_xml(xml)
130
+ override.set("PartName", partname)
131
+ override.set("ContentType", content_type)
132
+ return override
133
+
134
+ @property
135
+ def partname(self):
136
+ """String held in the ``PartName`` attribute of this ``<Override>`` element."""
137
+ return self.get("PartName")
138
+
139
+
140
+ class CT_Relationship(BaseOxmlElement):
141
+ """`<Relationship>` element, representing a single relationship from source to target part."""
142
+
143
+ @staticmethod
144
+ def new(rId: str, reltype: str, target: str, target_mode: str = RTM.INTERNAL):
145
+ """Return a new ``<Relationship>`` element."""
146
+ xml = '<Relationship xmlns="%s"/>' % nsmap["pr"]
147
+ relationship = parse_xml(xml)
148
+ relationship.set("Id", rId)
149
+ relationship.set("Type", reltype)
150
+ relationship.set("Target", target)
151
+ if target_mode == RTM.EXTERNAL:
152
+ relationship.set("TargetMode", RTM.EXTERNAL)
153
+ return relationship
154
+
155
+ @property
156
+ def rId(self):
157
+ """String held in the ``Id`` attribute of this ``<Relationship>`` element."""
158
+ return self.get("Id")
159
+
160
+ @property
161
+ def reltype(self):
162
+ """String held in the ``Type`` attribute of this ``<Relationship>`` element."""
163
+ return self.get("Type")
164
+
165
+ @property
166
+ def target_ref(self):
167
+ """String held in the ``Target`` attribute of this ``<Relationship>``
168
+ element."""
169
+ return self.get("Target")
170
+
171
+ @property
172
+ def target_mode(self):
173
+ """String held in the ``TargetMode`` attribute of this ``<Relationship>``
174
+ element, either ``Internal`` or ``External``.
175
+
176
+ Defaults to ``Internal``.
177
+ """
178
+ return self.get("TargetMode", RTM.INTERNAL)
179
+
180
+
181
+ class CT_Relationships(BaseOxmlElement):
182
+ """``<Relationships>`` element, the root element in a .rels file."""
183
+
184
+ def add_rel(self, rId: str, reltype: str, target: str, is_external: bool = False):
185
+ """Add a child ``<Relationship>`` element with attributes set according to
186
+ parameter values."""
187
+ target_mode = RTM.EXTERNAL if is_external else RTM.INTERNAL
188
+ relationship = CT_Relationship.new(rId, reltype, target, target_mode)
189
+ self.append(relationship)
190
+
191
+ @staticmethod
192
+ def new() -> CT_Relationships:
193
+ """Return a new ``<Relationships>`` element."""
194
+ xml = '<Relationships xmlns="%s"/>' % nsmap["pr"]
195
+ return cast(CT_Relationships, parse_xml(xml))
196
+
197
+ @property
198
+ def Relationship_lst(self):
199
+ """Return a list containing all the ``<Relationship>`` child elements."""
200
+ return self.findall(qn("pr:Relationship"))
201
+
202
+ @property
203
+ def xml(self):
204
+ """Return XML string for this element, suitable for saving in a .rels stream,
205
+ not pretty printed and with an XML declaration at the top."""
206
+ return serialize_part_xml(self)
207
+
208
+
209
+ class CT_Types(BaseOxmlElement):
210
+ """``<Types>`` element, the container element for Default and Override elements in
211
+ [Content_Types].xml."""
212
+
213
+ def add_default(self, ext, content_type):
214
+ """Add a child ``<Default>`` element with attributes set to parameter values."""
215
+ default = CT_Default.new(ext, content_type)
216
+ self.append(default)
217
+
218
+ def add_override(self, partname, content_type):
219
+ """Add a child ``<Override>`` element with attributes set to parameter
220
+ values."""
221
+ override = CT_Override.new(partname, content_type)
222
+ self.append(override)
223
+
224
+ @property
225
+ def defaults(self):
226
+ return self.findall(qn("ct:Default"))
227
+
228
+ @staticmethod
229
+ def new():
230
+ """Return a new ``<Types>`` element."""
231
+ xml = '<Types xmlns="%s"/>' % nsmap["ct"]
232
+ types = parse_xml(xml)
233
+ return types
234
+
235
+ @property
236
+ def overrides(self):
237
+ return self.findall(qn("ct:Override"))
238
+
239
+
240
+ ct_namespace = element_class_lookup.get_namespace(nsmap["ct"])
241
+ ct_namespace["Default"] = CT_Default
242
+ ct_namespace["Override"] = CT_Override
243
+ ct_namespace["Types"] = CT_Types
244
+
245
+ pr_namespace = element_class_lookup.get_namespace(nsmap["pr"])
246
+ pr_namespace["Relationship"] = CT_Relationship
247
+ pr_namespace["Relationships"] = CT_Relationships
docx/opc/package.py ADDED
@@ -0,0 +1,219 @@
1
+ """Objects that implement reading and writing OPC packages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import IO, TYPE_CHECKING, Iterator, cast
6
+
7
+ from docx.opc.constants import RELATIONSHIP_TYPE as RT
8
+ from docx.opc.packuri import PACKAGE_URI, PackURI
9
+ from docx.opc.part import PartFactory
10
+ from docx.opc.parts.coreprops import CorePropertiesPart
11
+ from docx.opc.pkgreader import PackageReader
12
+ from docx.opc.pkgwriter import PackageWriter
13
+ from docx.opc.rel import Relationships
14
+ from docx.shared import lazyproperty
15
+
16
+ if TYPE_CHECKING:
17
+ from typing_extensions import Self
18
+
19
+ from docx.opc.coreprops import CoreProperties
20
+ from docx.opc.part import Part
21
+ from docx.opc.rel import _Relationship # pyright: ignore[reportPrivateUsage]
22
+
23
+
24
+ class OpcPackage:
25
+ """Main API class for |python-opc|.
26
+
27
+ A new instance is constructed by calling the :meth:`open` class method with a path
28
+ to a package file or file-like object containing one.
29
+ """
30
+
31
+ def after_unmarshal(self):
32
+ """Entry point for any post-unmarshaling processing.
33
+
34
+ May be overridden by subclasses without forwarding call to super.
35
+ """
36
+ # don't place any code here, just catch call if not overridden by
37
+ # subclass
38
+ pass
39
+
40
+ @property
41
+ def core_properties(self) -> CoreProperties:
42
+ """|CoreProperties| object providing read/write access to the Dublin Core
43
+ properties for this document."""
44
+ return self._core_properties_part.core_properties
45
+
46
+ def iter_rels(self) -> Iterator[_Relationship]:
47
+ """Generate exactly one reference to each relationship in the package by
48
+ performing a depth-first traversal of the rels graph."""
49
+
50
+ def walk_rels(
51
+ source: OpcPackage | Part, visited: list[Part] | None = None
52
+ ) -> Iterator[_Relationship]:
53
+ visited = [] if visited is None else visited
54
+ for rel in source.rels.values():
55
+ yield rel
56
+ if rel.is_external:
57
+ continue
58
+ part = rel.target_part
59
+ if part in visited:
60
+ continue
61
+ visited.append(part)
62
+ new_source = part
63
+ for rel in walk_rels(new_source, visited):
64
+ yield rel
65
+
66
+ for rel in walk_rels(self):
67
+ yield rel
68
+
69
+ def iter_parts(self) -> Iterator[Part]:
70
+ """Generate exactly one reference to each of the parts in the package by
71
+ performing a depth-first traversal of the rels graph."""
72
+
73
+ def walk_parts(source, visited=[]):
74
+ for rel in source.rels.values():
75
+ if rel.is_external:
76
+ continue
77
+ part = rel.target_part
78
+ if part in visited:
79
+ continue
80
+ visited.append(part)
81
+ yield part
82
+ new_source = part
83
+ for part in walk_parts(new_source, visited):
84
+ yield part
85
+
86
+ for part in walk_parts(self):
87
+ yield part
88
+
89
+ def load_rel(self, reltype: str, target: Part | str, rId: str, is_external: bool = False):
90
+ """Return newly added |_Relationship| instance of `reltype` between this part
91
+ and `target` 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 the package during processing.
96
+ """
97
+ return self.rels.add_relationship(reltype, target, rId, is_external)
98
+
99
+ @property
100
+ def main_document_part(self):
101
+ """Return a reference to the main document part for this package.
102
+
103
+ Examples include a document part for a WordprocessingML package, a presentation
104
+ part for a PresentationML package, or a workbook part for a SpreadsheetML
105
+ package.
106
+ """
107
+ return self.part_related_by(RT.OFFICE_DOCUMENT)
108
+
109
+ def next_partname(self, template: str) -> PackURI:
110
+ """Return a |PackURI| instance representing partname matching `template`.
111
+
112
+ The returned part-name has the next available numeric suffix to distinguish it
113
+ from other parts of its type. `template` is a printf (%)-style template string
114
+ containing a single replacement item, a '%d' to be used to insert the integer
115
+ portion of the partname. Example: "/word/header%d.xml"
116
+ """
117
+ partnames = {part.partname for part in self.iter_parts()}
118
+ for n in range(1, len(partnames) + 2):
119
+ candidate_partname = template % n
120
+ if candidate_partname not in partnames:
121
+ return PackURI(candidate_partname)
122
+
123
+ @classmethod
124
+ def open(cls, pkg_file: str | IO[bytes]) -> Self:
125
+ """Return an |OpcPackage| instance loaded with the contents of `pkg_file`."""
126
+ pkg_reader = PackageReader.from_file(pkg_file)
127
+ package = cls()
128
+ Unmarshaller.unmarshal(pkg_reader, package, PartFactory)
129
+ return package
130
+
131
+ def part_related_by(self, reltype: str) -> Part:
132
+ """Return part to which this package has a relationship of `reltype`.
133
+
134
+ Raises |KeyError| if no such relationship is found and |ValueError| if more than
135
+ one such relationship is found.
136
+ """
137
+ return self.rels.part_with_reltype(reltype)
138
+
139
+ @property
140
+ def parts(self) -> list[Part]:
141
+ """Return a list containing a reference to each of the parts in this package."""
142
+ return list(self.iter_parts())
143
+
144
+ def relate_to(self, part: Part, reltype: str):
145
+ """Return rId key of new or existing relationship to `part`.
146
+
147
+ If a relationship of `reltype` to `part` already exists, its rId is returned. Otherwise a
148
+ new relationship is created and that rId is returned.
149
+ """
150
+ rel = self.rels.get_or_add(reltype, part)
151
+ return rel.rId
152
+
153
+ @lazyproperty
154
+ def rels(self):
155
+ """Return a reference to the |Relationships| instance holding the collection of
156
+ relationships for this package."""
157
+ return Relationships(PACKAGE_URI.baseURI)
158
+
159
+ def save(self, pkg_file: str | IO[bytes]):
160
+ """Save this package to `pkg_file`.
161
+
162
+ `pkg_file` can be either a file-path or a file-like object.
163
+ """
164
+ for part in self.parts:
165
+ part.before_marshal()
166
+ PackageWriter.write(pkg_file, self.rels, self.parts)
167
+
168
+ @property
169
+ def _core_properties_part(self) -> CorePropertiesPart:
170
+ """|CorePropertiesPart| object related to this package.
171
+
172
+ Creates a default core properties part if one is not present (not common).
173
+ """
174
+ try:
175
+ return cast(CorePropertiesPart, self.part_related_by(RT.CORE_PROPERTIES))
176
+ except KeyError:
177
+ core_properties_part = CorePropertiesPart.default(self)
178
+ self.relate_to(core_properties_part, RT.CORE_PROPERTIES)
179
+ return core_properties_part
180
+
181
+
182
+ class Unmarshaller:
183
+ """Hosts static methods for unmarshalling a package from a |PackageReader|."""
184
+
185
+ @staticmethod
186
+ def unmarshal(pkg_reader, package, part_factory):
187
+ """Construct graph of parts and realized relationships based on the contents of
188
+ `pkg_reader`, delegating construction of each part to `part_factory`.
189
+
190
+ Package relationships are added to `pkg`.
191
+ """
192
+ parts = Unmarshaller._unmarshal_parts(pkg_reader, package, part_factory)
193
+ Unmarshaller._unmarshal_relationships(pkg_reader, package, parts)
194
+ for part in parts.values():
195
+ part.after_unmarshal()
196
+ package.after_unmarshal()
197
+
198
+ @staticmethod
199
+ def _unmarshal_parts(pkg_reader, package, part_factory):
200
+ """Return a dictionary of |Part| instances unmarshalled from `pkg_reader`, keyed
201
+ by partname.
202
+
203
+ Side-effect is that each part in `pkg_reader` is constructed using
204
+ `part_factory`.
205
+ """
206
+ parts = {}
207
+ for partname, content_type, reltype, blob in pkg_reader.iter_sparts():
208
+ parts[partname] = part_factory(partname, content_type, reltype, blob, package)
209
+ return parts
210
+
211
+ @staticmethod
212
+ def _unmarshal_relationships(pkg_reader, package, parts):
213
+ """Add a relationship to the source object corresponding to each of the
214
+ relationships in `pkg_reader` with its target_part set to the actual target part
215
+ in `parts`."""
216
+ for source_uri, srel in pkg_reader.iter_srels():
217
+ source = package if source_uri == "/" else parts[source_uri]
218
+ target = srel.target_ref if srel.is_external else parts[srel.target_partname]
219
+ source.load_rel(srel.reltype, target, srel.rId, srel.is_external)