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/package.py ADDED
@@ -0,0 +1,110 @@
1
+ """WordprocessingML Package class and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import IO, 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
+
15
+ class Package(OpcPackage):
16
+ """Customizations specific to a WordprocessingML package."""
17
+
18
+ def after_unmarshal(self):
19
+ """Called by loading code after all parts and relationships have been loaded.
20
+
21
+ This method affords the opportunity for any required post-processing.
22
+ """
23
+ self._gather_image_parts()
24
+
25
+ def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
26
+ """Return |ImagePart| containing image specified by `image_descriptor`.
27
+
28
+ The image-part is newly created if a matching one is not already present in the
29
+ collection.
30
+ """
31
+ return self.image_parts.get_or_add_image_part(image_descriptor)
32
+
33
+ @lazyproperty
34
+ def image_parts(self) -> ImageParts:
35
+ """|ImageParts| collection object for this package."""
36
+ return ImageParts()
37
+
38
+ def _gather_image_parts(self):
39
+ """Load the image part collection with all the image parts in package."""
40
+ for rel in self.iter_rels():
41
+ if rel.is_external:
42
+ continue
43
+ if rel.reltype != RT.IMAGE:
44
+ continue
45
+ if rel.target_part in self.image_parts:
46
+ continue
47
+ self.image_parts.append(cast("ImagePart", rel.target_part))
48
+
49
+
50
+ class ImageParts:
51
+ """Collection of |ImagePart| objects corresponding to images in the package."""
52
+
53
+ def __init__(self):
54
+ self._image_parts: list[ImagePart] = []
55
+
56
+ def __contains__(self, item: object):
57
+ return self._image_parts.__contains__(item)
58
+
59
+ def __iter__(self):
60
+ return self._image_parts.__iter__()
61
+
62
+ def __len__(self):
63
+ return self._image_parts.__len__()
64
+
65
+ def append(self, item: ImagePart):
66
+ self._image_parts.append(item)
67
+
68
+ def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
69
+ """Return |ImagePart| object containing image identified by `image_descriptor`.
70
+
71
+ The image-part is newly created if a matching one is not present in the
72
+ collection.
73
+ """
74
+ image = Image.from_file(image_descriptor)
75
+ matching_image_part = self._get_by_sha1(image.sha1)
76
+ if matching_image_part is not None:
77
+ return matching_image_part
78
+ return self._add_image_part(image)
79
+
80
+ def _add_image_part(self, image: Image):
81
+ """Return |ImagePart| instance newly created from `image` and appended to the collection."""
82
+ partname = self._next_image_partname(image.ext)
83
+ image_part = ImagePart.from_image(image, partname)
84
+ self.append(image_part)
85
+ return image_part
86
+
87
+ def _get_by_sha1(self, sha1: str) -> ImagePart | None:
88
+ """Return the image part in this collection having a SHA1 hash matching `sha1`,
89
+ or |None| if not found."""
90
+ for image_part in self._image_parts:
91
+ if image_part.sha1 == sha1:
92
+ return image_part
93
+ return None
94
+
95
+ def _next_image_partname(self, ext: str) -> PackURI:
96
+ """The next available image partname, starting from ``/word/media/image1.{ext}``
97
+ where unused numbers are reused.
98
+
99
+ The partname is unique by number, without regard to the extension. `ext` does
100
+ not include the leading period.
101
+ """
102
+
103
+ def image_partname(n: int) -> PackURI:
104
+ return PackURI("/word/media/image%d.%s" % (n, ext))
105
+
106
+ used_numbers = [image_part.partname.idx for image_part in self]
107
+ for n in range(1, len(self) + 1):
108
+ if n not in used_numbers:
109
+ return image_partname(n)
110
+ 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/document.py ADDED
@@ -0,0 +1,182 @@
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 RELATIONSHIP_TYPE as RT
9
+ from docx.parts.comments import CommentsPart
10
+ from docx.parts.hdrftr import FooterPart, HeaderPart
11
+ from docx.parts.numbering import NumberingPart
12
+ from docx.parts.settings import SettingsPart
13
+ from docx.parts.story import StoryPart
14
+ from docx.parts.styles import StylesPart
15
+ from docx.parts.theme import ThemePart
16
+ from docx.shape import InlineShapes
17
+ from docx.shared import lazyproperty
18
+
19
+ if TYPE_CHECKING:
20
+ from docx.comments import Comments
21
+ from docx.enum.style import WD_STYLE_TYPE
22
+ from docx.opc.coreprops import CoreProperties
23
+ from docx.settings import Settings
24
+ from docx.styles.style import BaseStyle
25
+ from docx.theme import ThemeFonts
26
+
27
+
28
+ class DocumentPart(StoryPart):
29
+ """Main document part of a WordprocessingML (WML) package, aka a .docx file.
30
+
31
+ Acts as broker to other parts such as image, core properties, and style parts. It
32
+ also acts as a convenient delegate when a mid-document object needs a service
33
+ involving a remote ancestor. The `Parented.part` property inherited by many content
34
+ objects provides access to this part object for that purpose.
35
+ """
36
+
37
+ def add_footer_part(self):
38
+ """Return (footer_part, rId) pair for newly-created footer part."""
39
+ footer_part = FooterPart.new(self.package)
40
+ rId = self.relate_to(footer_part, RT.FOOTER)
41
+ return footer_part, rId
42
+
43
+ def add_header_part(self):
44
+ """Return (header_part, rId) pair for newly-created header part."""
45
+ header_part = HeaderPart.new(self.package)
46
+ rId = self.relate_to(header_part, RT.HEADER)
47
+ return header_part, rId
48
+
49
+ @property
50
+ def comments(self) -> Comments:
51
+ """|Comments| object providing access to the comments added to this document."""
52
+ return self._comments_part.comments
53
+
54
+ @property
55
+ def core_properties(self) -> CoreProperties:
56
+ """A |CoreProperties| object providing read/write access to the core properties
57
+ of this document."""
58
+ return self.package.core_properties
59
+
60
+ @property
61
+ def document(self):
62
+ """A |Document| object providing access to the content of this document."""
63
+ return Document(self._element, self)
64
+
65
+ def drop_header_part(self, rId: str) -> None:
66
+ """Remove related header part identified by `rId`."""
67
+ self.drop_rel(rId)
68
+
69
+ def footer_part(self, rId: str):
70
+ """Return |FooterPart| related by `rId`."""
71
+ return self.related_parts[rId]
72
+
73
+ def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle:
74
+ """Return the style in this document matching `style_id`.
75
+
76
+ Returns the default style for `style_type` if `style_id` is |None| or does not
77
+ match a defined style of `style_type`.
78
+ """
79
+ return self.styles.get_by_id(style_id, style_type)
80
+
81
+ def get_style_id(self, style_or_name, style_type):
82
+ """Return the style_id (|str|) of the style of `style_type` matching
83
+ `style_or_name`.
84
+
85
+ Returns |None| if the style resolves to the default style for `style_type` or if
86
+ `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the
87
+ wrong type or names a style not present in the document.
88
+ """
89
+ return self.styles.get_style_id(style_or_name, style_type)
90
+
91
+ def header_part(self, rId: str):
92
+ """Return |HeaderPart| related by `rId`."""
93
+ return self.related_parts[rId]
94
+
95
+ @lazyproperty
96
+ def inline_shapes(self):
97
+ """The |InlineShapes| instance containing the inline shapes in the document."""
98
+ return InlineShapes(self._element.body, self)
99
+
100
+ @lazyproperty
101
+ def numbering_part(self) -> NumberingPart:
102
+ """A |NumberingPart| object providing access to the numbering definitions for this document.
103
+
104
+ Creates an empty numbering part if one is not present.
105
+ """
106
+ try:
107
+ return cast(NumberingPart, self.part_related_by(RT.NUMBERING))
108
+ except KeyError:
109
+ numbering_part = NumberingPart.new()
110
+ self.relate_to(numbering_part, RT.NUMBERING)
111
+ return numbering_part
112
+
113
+ def save(self, path_or_stream: str | IO[bytes]):
114
+ """Save this document to `path_or_stream`, which can be either a path to a
115
+ filesystem location (a string) or a file-like object."""
116
+ self.package.save(path_or_stream)
117
+
118
+ @property
119
+ def settings(self) -> Settings:
120
+ """A |Settings| object providing access to the settings in the settings part of
121
+ this document."""
122
+ return self._settings_part.settings
123
+
124
+ @property
125
+ def styles(self):
126
+ """A |Styles| object providing access to the styles in the styles part of this
127
+ document."""
128
+ return self._styles_part.styles
129
+
130
+ @property
131
+ def theme_fonts(self) -> ThemeFonts:
132
+ """Font scheme of the related theme, creating a default theme if absent."""
133
+ try:
134
+ theme_part = cast(ThemePart, self.part_related_by(RT.THEME))
135
+ except KeyError:
136
+ assert self.package is not None
137
+ theme_part = ThemePart.default(self.package)
138
+ self.relate_to(theme_part, RT.THEME)
139
+ return theme_part.fonts
140
+
141
+ @property
142
+ def _comments_part(self) -> CommentsPart:
143
+ """A |CommentsPart| object providing access to the comments added to this document.
144
+
145
+ Creates a default comments part if one is not present.
146
+ """
147
+ try:
148
+ return cast(CommentsPart, self.part_related_by(RT.COMMENTS))
149
+ except KeyError:
150
+ assert self.package is not None
151
+ comments_part = CommentsPart.default(self.package)
152
+ self.relate_to(comments_part, RT.COMMENTS)
153
+ return comments_part
154
+
155
+ @property
156
+ def _settings_part(self) -> SettingsPart:
157
+ """A |SettingsPart| object providing access to the document-level settings for
158
+ this document.
159
+
160
+ Creates a default settings part if one is not present.
161
+ """
162
+ try:
163
+ return cast(SettingsPart, self.part_related_by(RT.SETTINGS))
164
+ except KeyError:
165
+ settings_part = SettingsPart.default(self.package)
166
+ self.relate_to(settings_part, RT.SETTINGS)
167
+ return settings_part
168
+
169
+ @property
170
+ def _styles_part(self) -> StylesPart:
171
+ """Instance of |StylesPart| for this document.
172
+
173
+ Creates an empty styles part if one is not present.
174
+ """
175
+ try:
176
+ return cast(StylesPart, self.part_related_by(RT.STYLES))
177
+ except KeyError:
178
+ package = self.package
179
+ assert package is not None
180
+ styles_part = StylesPart.default(package)
181
+ self.relate_to(styles_part, RT.STYLES)
182
+ 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()
@@ -0,0 +1,32 @@
1
+ """|NumberingPart| and closely related objects."""
2
+
3
+ from ..opc.part import XmlPart
4
+ from ..shared import lazyproperty
5
+
6
+
7
+ class NumberingPart(XmlPart):
8
+ """Proxy for the numbering.xml part containing numbering definitions for a document
9
+ or glossary."""
10
+
11
+ @classmethod
12
+ def new(cls) -> "NumberingPart":
13
+ """Newly created numbering part, containing only the root ``<w:numbering>`` element."""
14
+ raise NotImplementedError
15
+
16
+ @lazyproperty
17
+ def numbering_definitions(self):
18
+ """The |_NumberingDefinitions| instance containing the numbering definitions
19
+ (<w:num> element proxies) for this numbering part."""
20
+ return _NumberingDefinitions(self._element)
21
+
22
+
23
+ class _NumberingDefinitions:
24
+ """Collection of |_NumberingDefinition| instances corresponding to the ``<w:num>``
25
+ elements in a numbering part."""
26
+
27
+ def __init__(self, numbering_elm):
28
+ super(_NumberingDefinitions, self).__init__()
29
+ self._numbering = numbering_elm
30
+
31
+ def __len__(self):
32
+ 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
docx/parts/story.py ADDED
@@ -0,0 +1,95 @@
1
+ """|StoryPart| and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import IO, TYPE_CHECKING, Tuple, cast
6
+
7
+ from docx.opc.constants import RELATIONSHIP_TYPE as RT
8
+ from docx.opc.part import XmlPart
9
+ from docx.oxml.shape import CT_Inline
10
+ from docx.shared import Length, lazyproperty
11
+
12
+ if TYPE_CHECKING:
13
+ from docx.enum.style import WD_STYLE_TYPE
14
+ from docx.image.image import Image
15
+ from docx.parts.document import DocumentPart
16
+ from docx.styles.style import BaseStyle
17
+
18
+
19
+ class StoryPart(XmlPart):
20
+ """Base class for story parts.
21
+
22
+ A story part is one that can contain textual content, such as the document-part and
23
+ header or footer parts. These all share content behaviors like `.paragraphs`,
24
+ `.add_paragraph()`, `.add_table()` etc.
25
+ """
26
+
27
+ def get_or_add_image(self, image_descriptor: str | IO[bytes]) -> Tuple[str, Image]:
28
+ """Return (rId, image) pair for image identified by `image_descriptor`.
29
+
30
+ `rId` is the str key (often like "rId7") for the relationship between this story
31
+ part and the image part, reused if already present, newly created if not.
32
+ `image` is an |Image| instance providing access to the properties of the image,
33
+ such as dimensions and image type.
34
+ """
35
+ package = self._package
36
+ assert package is not None
37
+ image_part = package.get_or_add_image_part(image_descriptor)
38
+ rId = self.relate_to(image_part, RT.IMAGE)
39
+ return rId, image_part.image
40
+
41
+ def get_style(self, style_id: str | None, style_type: WD_STYLE_TYPE) -> BaseStyle:
42
+ """Return the style in this document matching `style_id`.
43
+
44
+ Returns the default style for `style_type` if `style_id` is |None| or does not
45
+ match a defined style of `style_type`.
46
+ """
47
+ return self._document_part.get_style(style_id, style_type)
48
+
49
+ def get_style_id(
50
+ self, style_or_name: BaseStyle | str | None, style_type: WD_STYLE_TYPE
51
+ ) -> str | None:
52
+ """Return str style_id for `style_or_name` of `style_type`.
53
+
54
+ Returns |None| if the style resolves to the default style for `style_type` or if
55
+ `style_or_name` is itself |None|. Raises if `style_or_name` is a style of the
56
+ wrong type or names a style not present in the document.
57
+ """
58
+ return self._document_part.get_style_id(style_or_name, style_type)
59
+
60
+ def new_pic_inline(
61
+ self,
62
+ image_descriptor: str | IO[bytes],
63
+ width: int | Length | None = None,
64
+ height: int | Length | None = None,
65
+ ) -> CT_Inline:
66
+ """Return a newly-created `w:inline` element.
67
+
68
+ The element contains the image specified by `image_descriptor` and is scaled
69
+ based on the values of `width` and `height`.
70
+ """
71
+ rId, image = self.get_or_add_image(image_descriptor)
72
+ cx, cy = image.scaled_dimensions(width, height)
73
+ shape_id, filename = self.next_id, image.filename
74
+ return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy)
75
+
76
+ @property
77
+ def next_id(self) -> int:
78
+ """Next available positive integer id value in this story XML document.
79
+
80
+ The value is determined by incrementing the maximum existing id value. Gaps in
81
+ the existing id sequence are not filled. The id attribute value is unique in the
82
+ document, without regard to the element type it appears on.
83
+ """
84
+ id_str_lst = self._element.xpath("//@id")
85
+ used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
86
+ if not used_ids:
87
+ return 1
88
+ return max(used_ids) + 1
89
+
90
+ @lazyproperty
91
+ def _document_part(self) -> DocumentPart:
92
+ """|DocumentPart| object for this package."""
93
+ package = self.package
94
+ assert package is not None
95
+ return cast("DocumentPart", package.main_document_part)