python-docx-oss 0.1.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 +71 -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 +285 -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 +32 -0
- docx/image/bmp.py +43 -0
- docx/image/constants.py +172 -0
- docx/image/emf.py +46 -0
- docx/image/exceptions.py +13 -0
- docx/image/gif.py +38 -0
- docx/image/helpers.py +86 -0
- docx/image/image.py +237 -0
- docx/image/jpeg.py +469 -0
- docx/image/png.py +253 -0
- docx/image/svg.py +118 -0
- docx/image/tiff.py +289 -0
- docx/opc/__init__.py +0 -0
- docx/opc/constants.py +307 -0
- docx/opc/coreprops.py +142 -0
- docx/opc/customprops.py +59 -0
- docx/opc/exceptions.py +12 -0
- docx/opc/oxml.py +247 -0
- docx/opc/package.py +254 -0
- docx/opc/packuri.py +109 -0
- docx/opc/part.py +267 -0
- docx/opc/parts/__init__.py +0 -0
- docx/opc/parts/coreprops.py +48 -0
- docx/opc/parts/customprops.py +61 -0
- docx/opc/phys_pkg.py +119 -0
- docx/opc/pkgreader.py +254 -0
- docx/opc/pkgwriter.py +115 -0
- docx/opc/rel.py +169 -0
- docx/opc/shared.py +31 -0
- docx/opc/spec.py +24 -0
- docx/oxml/__init__.py +252 -0
- docx/oxml/comments.py +124 -0
- docx/oxml/coreprops.py +298 -0
- docx/oxml/customprops.py +141 -0
- docx/oxml/dml/CT_ExtensionList.py +48 -0
- docx/oxml/document.py +88 -0
- docx/oxml/drawing.py +11 -0
- docx/oxml/exceptions.py +10 -0
- docx/oxml/ns.py +111 -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 +426 -0
- docx/oxml/shared.py +52 -0
- docx/oxml/simpletypes.py +434 -0
- docx/oxml/styles.py +320 -0
- docx/oxml/table.py +977 -0
- docx/oxml/text/__init__.py +0 -0
- docx/oxml/text/font.py +358 -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 +316 -0
- docx/oxml/xmlchemy.py +711 -0
- docx/package.py +119 -0
- docx/parts/__init__.py +0 -0
- docx/parts/comments.py +51 -0
- docx/parts/customxml.py +67 -0
- docx/parts/document.py +193 -0
- docx/parts/hdrftr.py +53 -0
- docx/parts/image.py +80 -0
- docx/parts/numbering.py +51 -0
- docx/parts/settings.py +50 -0
- docx/parts/story.py +113 -0
- docx/parts/styles.py +42 -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 +254 -0
- docx/styles/styles.py +136 -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-numbering.xml +20 -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 +466 -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 +272 -0
- docx/text/tabstops.py +123 -0
- docx/types.py +34 -0
- python_docx_oss-0.1.0.dist-info/METADATA +57 -0
- python_docx_oss-0.1.0.dist-info/RECORD +132 -0
- python_docx_oss-0.1.0.dist-info/WHEEL +5 -0
- python_docx_oss-0.1.0.dist-info/licenses/LICENSE +21 -0
- python_docx_oss-0.1.0.dist-info/top_level.txt +1 -0
docx/package.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""WordprocessingML Package class and related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import IO, TYPE_CHECKING, cast
|
|
6
|
+
|
|
7
|
+
from docx.image.image import Image
|
|
8
|
+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
9
|
+
from docx.opc.package import OpcPackage
|
|
10
|
+
from docx.opc.packuri import PackURI
|
|
11
|
+
from docx.parts.image import ImagePart
|
|
12
|
+
from docx.shared import lazyproperty
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from docx.opc.customprops import CustomProperties
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Package(OpcPackage):
|
|
19
|
+
"""Customizations specific to a WordprocessingML package."""
|
|
20
|
+
|
|
21
|
+
def after_unmarshal(self):
|
|
22
|
+
"""Called by loading code after all parts and relationships have been loaded.
|
|
23
|
+
|
|
24
|
+
This method affords the opportunity for any required post-processing.
|
|
25
|
+
"""
|
|
26
|
+
self._gather_image_parts()
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def custom_properties(self) -> CustomProperties:
|
|
30
|
+
"""|CustomProperties| object providing read/write access to the custom
|
|
31
|
+
properties for this document."""
|
|
32
|
+
return super().custom_properties
|
|
33
|
+
|
|
34
|
+
def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
|
|
35
|
+
"""Return |ImagePart| containing image specified by `image_descriptor`.
|
|
36
|
+
|
|
37
|
+
The image-part is newly created if a matching one is not already present in the
|
|
38
|
+
collection.
|
|
39
|
+
"""
|
|
40
|
+
return self.image_parts.get_or_add_image_part(image_descriptor)
|
|
41
|
+
|
|
42
|
+
@lazyproperty
|
|
43
|
+
def image_parts(self) -> ImageParts:
|
|
44
|
+
"""|ImageParts| collection object for this package."""
|
|
45
|
+
return ImageParts()
|
|
46
|
+
|
|
47
|
+
def _gather_image_parts(self):
|
|
48
|
+
"""Load the image part collection with all the image parts in package."""
|
|
49
|
+
for rel in self.iter_rels():
|
|
50
|
+
if rel.is_external:
|
|
51
|
+
continue
|
|
52
|
+
if rel.reltype != RT.IMAGE:
|
|
53
|
+
continue
|
|
54
|
+
if rel.target_part in self.image_parts:
|
|
55
|
+
continue
|
|
56
|
+
self.image_parts.append(cast("ImagePart", rel.target_part))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ImageParts:
|
|
60
|
+
"""Collection of |ImagePart| objects corresponding to images in the package."""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self._image_parts: list[ImagePart] = []
|
|
64
|
+
|
|
65
|
+
def __contains__(self, item: object):
|
|
66
|
+
return self._image_parts.__contains__(item)
|
|
67
|
+
|
|
68
|
+
def __iter__(self):
|
|
69
|
+
return self._image_parts.__iter__()
|
|
70
|
+
|
|
71
|
+
def __len__(self):
|
|
72
|
+
return self._image_parts.__len__()
|
|
73
|
+
|
|
74
|
+
def append(self, item: ImagePart):
|
|
75
|
+
self._image_parts.append(item)
|
|
76
|
+
|
|
77
|
+
def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
|
|
78
|
+
"""Return |ImagePart| object containing image identified by `image_descriptor`.
|
|
79
|
+
|
|
80
|
+
The image-part is newly created if a matching one is not present in the
|
|
81
|
+
collection.
|
|
82
|
+
"""
|
|
83
|
+
image = Image.from_file(image_descriptor)
|
|
84
|
+
matching_image_part = self._get_by_sha1(image.sha1)
|
|
85
|
+
if matching_image_part is not None:
|
|
86
|
+
return matching_image_part
|
|
87
|
+
return self._add_image_part(image)
|
|
88
|
+
|
|
89
|
+
def _add_image_part(self, image: Image):
|
|
90
|
+
"""Return |ImagePart| instance newly created from `image` and appended to the collection."""
|
|
91
|
+
partname = self._next_image_partname(image.ext)
|
|
92
|
+
image_part = ImagePart.from_image(image, partname)
|
|
93
|
+
self.append(image_part)
|
|
94
|
+
return image_part
|
|
95
|
+
|
|
96
|
+
def _get_by_sha1(self, sha1: str) -> ImagePart | None:
|
|
97
|
+
"""Return the image part in this collection having a SHA1 hash matching `sha1`,
|
|
98
|
+
or |None| if not found."""
|
|
99
|
+
for image_part in self._image_parts:
|
|
100
|
+
if image_part.sha1 == sha1:
|
|
101
|
+
return image_part
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def _next_image_partname(self, ext: str) -> PackURI:
|
|
105
|
+
"""The next available image partname, starting from ``/word/media/image1.{ext}``
|
|
106
|
+
where unused numbers are reused.
|
|
107
|
+
|
|
108
|
+
The partname is unique by number, without regard to the extension. `ext` does
|
|
109
|
+
not include the leading period.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def image_partname(n: int) -> PackURI:
|
|
113
|
+
return PackURI("/word/media/image%d.%s" % (n, ext))
|
|
114
|
+
|
|
115
|
+
used_numbers = [image_part.partname.idx for image_part in self]
|
|
116
|
+
for n in range(1, len(self) + 1):
|
|
117
|
+
if n not in used_numbers:
|
|
118
|
+
return image_partname(n)
|
|
119
|
+
return image_partname(len(self) + 1)
|
docx/parts/__init__.py
ADDED
|
File without changes
|
docx/parts/comments.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Contains comments added to the document."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING, cast
|
|
7
|
+
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from docx.comments import Comments
|
|
11
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
12
|
+
from docx.opc.packuri import PackURI
|
|
13
|
+
from docx.oxml.comments import CT_Comments
|
|
14
|
+
from docx.oxml.parser import parse_xml
|
|
15
|
+
from docx.package import Package
|
|
16
|
+
from docx.parts.story import StoryPart
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from docx.oxml.comments import CT_Comments
|
|
20
|
+
from docx.package import Package
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CommentsPart(StoryPart):
|
|
24
|
+
"""Container part for comments added to the document."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self, partname: PackURI, content_type: str, element: CT_Comments, package: Package
|
|
28
|
+
):
|
|
29
|
+
super().__init__(partname, content_type, element, package)
|
|
30
|
+
self._comments = element
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def comments(self) -> Comments:
|
|
34
|
+
"""A |Comments| proxy object for the `w:comments` root element of this part."""
|
|
35
|
+
return Comments(self._comments, self)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def default(cls, package: Package) -> Self:
|
|
39
|
+
"""A newly created comments part, containing a default empty `w:comments` element."""
|
|
40
|
+
partname = PackURI("/word/comments.xml")
|
|
41
|
+
content_type = CT.WML_COMMENTS
|
|
42
|
+
element = cast("CT_Comments", parse_xml(cls._default_comments_xml()))
|
|
43
|
+
return cls(partname, content_type, element, package)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _default_comments_xml(cls) -> bytes:
|
|
47
|
+
"""A byte-string containing XML for a default comments part."""
|
|
48
|
+
path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-comments.xml")
|
|
49
|
+
with open(path, "rb") as f:
|
|
50
|
+
xml_bytes = f.read()
|
|
51
|
+
return xml_bytes
|
docx/parts/customxml.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|CustomXmlPart| and closely related objects
|
|
3
|
+
"""
|
|
4
|
+
from lxml import etree
|
|
5
|
+
from lxml.etree import fromstring
|
|
6
|
+
|
|
7
|
+
from docx.opc.package import OpcPackage
|
|
8
|
+
from docx.opc.packuri import PackURI
|
|
9
|
+
from docx.opc.part import XmlPart
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CustomXmlPart(XmlPart):
|
|
13
|
+
"""
|
|
14
|
+
Proxy for the /customXml/*.xml part containing custom items definitions
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def default(
|
|
19
|
+
cls,
|
|
20
|
+
file_name: str,
|
|
21
|
+
content_type: str,
|
|
22
|
+
xml: str,
|
|
23
|
+
package: OpcPackage,
|
|
24
|
+
) -> "CustomXmlPart":
|
|
25
|
+
"""
|
|
26
|
+
Create a default xml file in /customXml/*file_name*.xml
|
|
27
|
+
with default content *xml* and type *content_type*
|
|
28
|
+
"""
|
|
29
|
+
return cls(
|
|
30
|
+
PackURI(f"/customXml/{file_name}.xml"),
|
|
31
|
+
content_type,
|
|
32
|
+
fromstring(xml),
|
|
33
|
+
package,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def tag(self) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Return the root element tag name.
|
|
40
|
+
"""
|
|
41
|
+
return self.element.tag
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def attrib(self) -> dict:
|
|
45
|
+
"""
|
|
46
|
+
Return the root element attributes.
|
|
47
|
+
"""
|
|
48
|
+
return self.element.attrib
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def items(self) -> list[etree._Element]:
|
|
52
|
+
"""
|
|
53
|
+
Return all children belong to root element.
|
|
54
|
+
"""
|
|
55
|
+
return self.element.getchildren()
|
|
56
|
+
|
|
57
|
+
def add_item(self, tag: str, text: str = "", **attrib):
|
|
58
|
+
"""
|
|
59
|
+
Add a child element to root.
|
|
60
|
+
"""
|
|
61
|
+
etree.SubElement(self.element, tag, **attrib).text = text
|
|
62
|
+
|
|
63
|
+
def delete_item(self, item_element: etree._Element):
|
|
64
|
+
"""
|
|
65
|
+
Delete a item element. other nodes index may change after deletion.
|
|
66
|
+
"""
|
|
67
|
+
self.element.remove(item_element)
|
docx/parts/document.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""|DocumentPart| and closely related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import IO, TYPE_CHECKING, cast
|
|
6
|
+
|
|
7
|
+
from docx.document import Document
|
|
8
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
9
|
+
from docx.opc.constants import RELATIONSHIP_TYPE as RT
|
|
10
|
+
from docx.parts.comments import CommentsPart
|
|
11
|
+
from docx.parts.customxml import CustomXmlPart
|
|
12
|
+
from docx.parts.hdrftr import FooterPart, HeaderPart
|
|
13
|
+
from docx.parts.numbering import NumberingPart
|
|
14
|
+
from docx.parts.settings import SettingsPart
|
|
15
|
+
from docx.parts.story import StoryPart
|
|
16
|
+
from docx.parts.styles import StylesPart
|
|
17
|
+
from docx.shape import InlineShapes
|
|
18
|
+
from docx.shared import lazyproperty
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from docx.comments import Comments
|
|
22
|
+
from docx.enum.style import WD_STYLE_TYPE
|
|
23
|
+
from docx.opc.coreprops import CoreProperties
|
|
24
|
+
from docx.opc.customprops import CustomProperties
|
|
25
|
+
from docx.settings import Settings
|
|
26
|
+
from docx.styles.style import BaseStyle
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DocumentPart(StoryPart):
|
|
30
|
+
"""Main document part of a WordprocessingML (WML) package, aka a .docx file.
|
|
31
|
+
|
|
32
|
+
Acts as broker to other parts such as image, core properties, and style parts. It
|
|
33
|
+
also acts as a convenient delegate when a mid-document object needs a service
|
|
34
|
+
involving a remote ancestor. The `Parented.part` property inherited by many content
|
|
35
|
+
objects provides access to this part object for that purpose.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def add_custom_xml_part(
|
|
39
|
+
self, xml: str, file_name: str = "item1", content_type: str = CT.XML
|
|
40
|
+
) -> CustomXmlPart:
|
|
41
|
+
"""Add a file in /customXml/*file_name*.xml with default content *xml*."""
|
|
42
|
+
part = CustomXmlPart.default(file_name, content_type, xml, self.package)
|
|
43
|
+
return self.rels.get_or_add(RT.CUSTOM_XML, part).target_part
|
|
44
|
+
|
|
45
|
+
def add_footer_part(self):
|
|
46
|
+
"""Return (footer_part, rId) pair for newly-created footer part."""
|
|
47
|
+
footer_part = FooterPart.new(self.package)
|
|
48
|
+
rId = self.relate_to(footer_part, RT.FOOTER)
|
|
49
|
+
return footer_part, rId
|
|
50
|
+
|
|
51
|
+
def add_header_part(self):
|
|
52
|
+
"""Return (header_part, rId) pair for newly-created header part."""
|
|
53
|
+
header_part = HeaderPart.new(self.package)
|
|
54
|
+
rId = self.relate_to(header_part, RT.HEADER)
|
|
55
|
+
return header_part, rId
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def comments(self) -> Comments:
|
|
59
|
+
"""|Comments| object providing access to the comments added to this document."""
|
|
60
|
+
return self._comments_part.comments
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def core_properties(self) -> CoreProperties:
|
|
64
|
+
"""A |CoreProperties| object providing read/write access to the core properties
|
|
65
|
+
of this document."""
|
|
66
|
+
return self.package.core_properties
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def custom_properties(self) -> CustomProperties:
|
|
70
|
+
"""A |CustomProperties| object providing read/write access to the custom
|
|
71
|
+
properties of this document."""
|
|
72
|
+
return self.package.custom_properties
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def custom_xml_parts(self) -> list[CustomXmlPart]:
|
|
76
|
+
"""A list of |CustomXmlPart| objects for this document."""
|
|
77
|
+
try:
|
|
78
|
+
return self.rels.parts_with_reltype(RT.CUSTOM_XML)
|
|
79
|
+
except KeyError:
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def document(self):
|
|
84
|
+
"""A |Document| object providing access to the content of this document."""
|
|
85
|
+
return Document(self._element, self)
|
|
86
|
+
|
|
87
|
+
def drop_header_part(self, rId: str) -> None:
|
|
88
|
+
"""Remove related header part identified by `rId`."""
|
|
89
|
+
self.drop_rel(rId)
|
|
90
|
+
|
|
91
|
+
def footer_part(self, rId: str):
|
|
92
|
+
"""Return |FooterPart| related by `rId`."""
|
|
93
|
+
return self.related_parts[rId]
|
|
94
|
+
|
|
95
|
+
def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle:
|
|
96
|
+
"""Return the style in this document matching `style_id`.
|
|
97
|
+
|
|
98
|
+
Returns the default style for `style_type` if `style_id` is |None| or does not
|
|
99
|
+
match a defined style of `style_type`.
|
|
100
|
+
"""
|
|
101
|
+
return self.styles.get_by_id(style_id, style_type)
|
|
102
|
+
|
|
103
|
+
def get_style_id(self, style_or_name, style_type):
|
|
104
|
+
"""Return the style_id (|str|) of the style of `style_type` matching
|
|
105
|
+
`style_or_name`.
|
|
106
|
+
|
|
107
|
+
Returns |None| if the style resolves to the default style for `style_type` or if
|
|
108
|
+
`style_or_name` is itself |None|. Raises if `style_or_name` is a style of the
|
|
109
|
+
wrong type or names a style not present in the document.
|
|
110
|
+
"""
|
|
111
|
+
return self.styles.get_style_id(style_or_name, style_type)
|
|
112
|
+
|
|
113
|
+
def header_part(self, rId: str):
|
|
114
|
+
"""Return |HeaderPart| related by `rId`."""
|
|
115
|
+
return self.related_parts[rId]
|
|
116
|
+
|
|
117
|
+
@lazyproperty
|
|
118
|
+
def inline_shapes(self):
|
|
119
|
+
"""The |InlineShapes| instance containing the inline shapes in the document."""
|
|
120
|
+
return InlineShapes(self._element.body, self)
|
|
121
|
+
|
|
122
|
+
@lazyproperty
|
|
123
|
+
def numbering_part(self) -> NumberingPart:
|
|
124
|
+
"""A |NumberingPart| object providing access to the numbering definitions for this document.
|
|
125
|
+
|
|
126
|
+
Creates an empty numbering part if one is not present.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
return cast(NumberingPart, self.part_related_by(RT.NUMBERING))
|
|
130
|
+
except KeyError:
|
|
131
|
+
numbering_part = NumberingPart.default(self.package)
|
|
132
|
+
self.relate_to(numbering_part, RT.NUMBERING)
|
|
133
|
+
return numbering_part
|
|
134
|
+
|
|
135
|
+
def save(self, path_or_stream: str | IO[bytes]):
|
|
136
|
+
"""Save this document to `path_or_stream`, which can be either a path to a
|
|
137
|
+
filesystem location (a string) or a file-like object."""
|
|
138
|
+
self.package.save(path_or_stream)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def settings(self) -> Settings:
|
|
142
|
+
"""A |Settings| object providing access to the settings in the settings part of
|
|
143
|
+
this document."""
|
|
144
|
+
return self._settings_part.settings
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def styles(self):
|
|
148
|
+
"""A |Styles| object providing access to the styles in the styles part of this
|
|
149
|
+
document."""
|
|
150
|
+
return self._styles_part.styles
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def _comments_part(self) -> CommentsPart:
|
|
154
|
+
"""A |CommentsPart| object providing access to the comments added to this document.
|
|
155
|
+
|
|
156
|
+
Creates a default comments part if one is not present.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
return cast(CommentsPart, self.part_related_by(RT.COMMENTS))
|
|
160
|
+
except KeyError:
|
|
161
|
+
assert self.package is not None
|
|
162
|
+
comments_part = CommentsPart.default(self.package)
|
|
163
|
+
self.relate_to(comments_part, RT.COMMENTS)
|
|
164
|
+
return comments_part
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def _settings_part(self) -> SettingsPart:
|
|
168
|
+
"""A |SettingsPart| object providing access to the document-level settings for
|
|
169
|
+
this document.
|
|
170
|
+
|
|
171
|
+
Creates a default settings part if one is not present.
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
return cast(SettingsPart, self.part_related_by(RT.SETTINGS))
|
|
175
|
+
except KeyError:
|
|
176
|
+
settings_part = SettingsPart.default(self.package)
|
|
177
|
+
self.relate_to(settings_part, RT.SETTINGS)
|
|
178
|
+
return settings_part
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def _styles_part(self) -> StylesPart:
|
|
182
|
+
"""Instance of |StylesPart| for this document.
|
|
183
|
+
|
|
184
|
+
Creates an empty styles part if one is not present.
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
return cast(StylesPart, self.part_related_by(RT.STYLES))
|
|
188
|
+
except KeyError:
|
|
189
|
+
package = self.package
|
|
190
|
+
assert package is not None
|
|
191
|
+
styles_part = StylesPart.default(package)
|
|
192
|
+
self.relate_to(styles_part, RT.STYLES)
|
|
193
|
+
return styles_part
|
docx/parts/hdrftr.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Header and footer part objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
9
|
+
from docx.oxml.parser import parse_xml
|
|
10
|
+
from docx.parts.story import StoryPart
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from docx.package import Package
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FooterPart(StoryPart):
|
|
17
|
+
"""Definition of a section footer."""
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def new(cls, package: Package):
|
|
21
|
+
"""Return newly created footer part."""
|
|
22
|
+
partname = package.next_partname("/word/footer%d.xml")
|
|
23
|
+
content_type = CT.WML_FOOTER
|
|
24
|
+
element = parse_xml(cls._default_footer_xml())
|
|
25
|
+
return cls(partname, content_type, element, package)
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def _default_footer_xml(cls):
|
|
29
|
+
"""Return bytes containing XML for a default footer part."""
|
|
30
|
+
path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-footer.xml")
|
|
31
|
+
with open(path, "rb") as f:
|
|
32
|
+
xml_bytes = f.read()
|
|
33
|
+
return xml_bytes
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class HeaderPart(StoryPart):
|
|
37
|
+
"""Definition of a section header."""
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def new(cls, package: Package):
|
|
41
|
+
"""Return newly created header part."""
|
|
42
|
+
partname = package.next_partname("/word/header%d.xml")
|
|
43
|
+
content_type = CT.WML_HEADER
|
|
44
|
+
element = parse_xml(cls._default_header_xml())
|
|
45
|
+
return cls(partname, content_type, element, package)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def _default_header_xml(cls):
|
|
49
|
+
"""Return bytes containing XML for a default header part."""
|
|
50
|
+
path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-header.xml")
|
|
51
|
+
with open(path, "rb") as f:
|
|
52
|
+
xml_bytes = f.read()
|
|
53
|
+
return xml_bytes
|
docx/parts/image.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""The proxy class for an image part, and related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from docx.image.image import Image
|
|
9
|
+
from docx.opc.part import Part
|
|
10
|
+
from docx.shared import Emu, Inches
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from docx.opc.package import OpcPackage
|
|
14
|
+
from docx.opc.packuri import PackURI
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ImagePart(Part):
|
|
18
|
+
"""An image part.
|
|
19
|
+
|
|
20
|
+
Corresponds to the target part of a relationship with type RELATIONSHIP_TYPE.IMAGE.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self, partname: PackURI, content_type: str, blob: bytes, image: Image | None = None
|
|
25
|
+
):
|
|
26
|
+
super(ImagePart, self).__init__(partname, content_type, blob)
|
|
27
|
+
self._image = image
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def default_cx(self):
|
|
31
|
+
"""Native width of this image, calculated from its width in pixels and
|
|
32
|
+
horizontal dots per inch (dpi)."""
|
|
33
|
+
px_width = self.image.px_width
|
|
34
|
+
horz_dpi = self.image.horz_dpi
|
|
35
|
+
width_in_inches = px_width / horz_dpi
|
|
36
|
+
return Inches(width_in_inches)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def default_cy(self):
|
|
40
|
+
"""Native height of this image, calculated from its height in pixels and
|
|
41
|
+
vertical dots per inch (dpi)."""
|
|
42
|
+
px_height = self.image.px_height
|
|
43
|
+
horz_dpi = self.image.horz_dpi
|
|
44
|
+
height_in_emu = int(round(914400 * px_height / horz_dpi))
|
|
45
|
+
return Emu(height_in_emu)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def filename(self):
|
|
49
|
+
"""Filename from which this image part was originally created.
|
|
50
|
+
|
|
51
|
+
A generic name, e.g. 'image.png', is substituted if no name is available, for
|
|
52
|
+
example when the image was loaded from an unnamed stream. In that case a default
|
|
53
|
+
extension is applied based on the detected MIME type of the image.
|
|
54
|
+
"""
|
|
55
|
+
if self._image is not None:
|
|
56
|
+
return self._image.filename
|
|
57
|
+
return "image.%s" % self.partname.ext
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_image(cls, image: Image, partname: PackURI):
|
|
61
|
+
"""Return an |ImagePart| instance newly created from `image` and assigned
|
|
62
|
+
`partname`."""
|
|
63
|
+
return ImagePart(partname, image.content_type, image.blob, image)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def image(self) -> Image:
|
|
67
|
+
if self._image is None:
|
|
68
|
+
self._image = Image.from_blob(self.blob)
|
|
69
|
+
return self._image
|
|
70
|
+
|
|
71
|
+
@classmethod
|
|
72
|
+
def load(cls, partname: PackURI, content_type: str, blob: bytes, package: OpcPackage):
|
|
73
|
+
"""Called by ``docx.opc.package.PartFactory`` to load an image part from a
|
|
74
|
+
package being opened by ``Document(...)`` call."""
|
|
75
|
+
return cls(partname, content_type, blob)
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def sha1(self):
|
|
79
|
+
"""SHA1 hash digest of the blob of this image part."""
|
|
80
|
+
return hashlib.sha1(self.blob).hexdigest()
|
docx/parts/numbering.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""|NumberingPart| and closely related objects."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from ..opc.constants import CONTENT_TYPE as CT
|
|
6
|
+
from ..opc.packuri import PackURI
|
|
7
|
+
from ..opc.part import XmlPart
|
|
8
|
+
from ..oxml import parse_xml
|
|
9
|
+
from ..shared import lazyproperty
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NumberingPart(XmlPart):
|
|
13
|
+
"""Proxy for the numbering.xml part containing numbering definitions for a document
|
|
14
|
+
or glossary."""
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def default(cls, package):
|
|
18
|
+
"""Return newly created empty numbering part, containing only the root ``<w:numbering>``
|
|
19
|
+
element."""
|
|
20
|
+
partname = PackURI("/word/numbering.xml")
|
|
21
|
+
content_type = CT.WML_NUMBERING
|
|
22
|
+
element = parse_xml(cls._default_numbering_xml())
|
|
23
|
+
return cls(partname, content_type, element, package)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def _default_numbering_xml(cls):
|
|
27
|
+
"""Return a bytestream containing XML for a default numbering part."""
|
|
28
|
+
path = os.path.join(
|
|
29
|
+
os.path.split(__file__)[0], "..", "templates", "default-numbering.xml"
|
|
30
|
+
)
|
|
31
|
+
with open(path, "rb") as f:
|
|
32
|
+
xml_bytes = f.read()
|
|
33
|
+
return xml_bytes
|
|
34
|
+
|
|
35
|
+
@lazyproperty
|
|
36
|
+
def numbering_definitions(self):
|
|
37
|
+
"""The |_NumberingDefinitions| instance containing the numbering definitions
|
|
38
|
+
(<w:num> element proxies) for this numbering part."""
|
|
39
|
+
return _NumberingDefinitions(self._element)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _NumberingDefinitions:
|
|
43
|
+
"""Collection of |_NumberingDefinition| instances corresponding to the ``<w:num>``
|
|
44
|
+
elements in a numbering part."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, numbering_elm):
|
|
47
|
+
super(_NumberingDefinitions, self).__init__()
|
|
48
|
+
self._numbering = numbering_elm
|
|
49
|
+
|
|
50
|
+
def __len__(self):
|
|
51
|
+
return len(self._numbering.num_lst)
|
docx/parts/settings.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""|SettingsPart| and closely related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import TYPE_CHECKING, cast
|
|
7
|
+
|
|
8
|
+
from docx.opc.constants import CONTENT_TYPE as CT
|
|
9
|
+
from docx.opc.packuri import PackURI
|
|
10
|
+
from docx.opc.part import XmlPart
|
|
11
|
+
from docx.oxml.parser import parse_xml
|
|
12
|
+
from docx.settings import Settings
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from docx.oxml.settings import CT_Settings
|
|
16
|
+
from docx.package import Package
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SettingsPart(XmlPart):
|
|
20
|
+
"""Document-level settings part of a WordprocessingML (WML) package."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self, partname: PackURI, content_type: str, element: CT_Settings, package: Package
|
|
24
|
+
):
|
|
25
|
+
super().__init__(partname, content_type, element, package)
|
|
26
|
+
self._settings = element
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def default(cls, package: Package):
|
|
30
|
+
"""Return a newly created settings part, containing a default `w:settings` element tree."""
|
|
31
|
+
partname = PackURI("/word/settings.xml")
|
|
32
|
+
content_type = CT.WML_SETTINGS
|
|
33
|
+
element = cast("CT_Settings", parse_xml(cls._default_settings_xml()))
|
|
34
|
+
return cls(partname, content_type, element, package)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def settings(self) -> Settings:
|
|
38
|
+
"""A |Settings| proxy object for the `w:settings` element in this part.
|
|
39
|
+
|
|
40
|
+
Contains the document-level settings for this document.
|
|
41
|
+
"""
|
|
42
|
+
return Settings(self._settings)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _default_settings_xml(cls):
|
|
46
|
+
"""Return a bytestream containing XML for a default settings part."""
|
|
47
|
+
path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-settings.xml")
|
|
48
|
+
with open(path, "rb") as f:
|
|
49
|
+
xml_bytes = f.read()
|
|
50
|
+
return xml_bytes
|