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.
Files changed (132) hide show
  1. docx/__init__.py +71 -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 +285 -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 +32 -0
  19. docx/image/bmp.py +43 -0
  20. docx/image/constants.py +172 -0
  21. docx/image/emf.py +46 -0
  22. docx/image/exceptions.py +13 -0
  23. docx/image/gif.py +38 -0
  24. docx/image/helpers.py +86 -0
  25. docx/image/image.py +237 -0
  26. docx/image/jpeg.py +469 -0
  27. docx/image/png.py +253 -0
  28. docx/image/svg.py +118 -0
  29. docx/image/tiff.py +289 -0
  30. docx/opc/__init__.py +0 -0
  31. docx/opc/constants.py +307 -0
  32. docx/opc/coreprops.py +142 -0
  33. docx/opc/customprops.py +59 -0
  34. docx/opc/exceptions.py +12 -0
  35. docx/opc/oxml.py +247 -0
  36. docx/opc/package.py +254 -0
  37. docx/opc/packuri.py +109 -0
  38. docx/opc/part.py +267 -0
  39. docx/opc/parts/__init__.py +0 -0
  40. docx/opc/parts/coreprops.py +48 -0
  41. docx/opc/parts/customprops.py +61 -0
  42. docx/opc/phys_pkg.py +119 -0
  43. docx/opc/pkgreader.py +254 -0
  44. docx/opc/pkgwriter.py +115 -0
  45. docx/opc/rel.py +169 -0
  46. docx/opc/shared.py +31 -0
  47. docx/opc/spec.py +24 -0
  48. docx/oxml/__init__.py +252 -0
  49. docx/oxml/comments.py +124 -0
  50. docx/oxml/coreprops.py +298 -0
  51. docx/oxml/customprops.py +141 -0
  52. docx/oxml/dml/CT_ExtensionList.py +48 -0
  53. docx/oxml/document.py +88 -0
  54. docx/oxml/drawing.py +11 -0
  55. docx/oxml/exceptions.py +10 -0
  56. docx/oxml/ns.py +111 -0
  57. docx/oxml/numbering.py +109 -0
  58. docx/oxml/parser.py +62 -0
  59. docx/oxml/section.py +537 -0
  60. docx/oxml/settings.py +138 -0
  61. docx/oxml/shape.py +426 -0
  62. docx/oxml/shared.py +52 -0
  63. docx/oxml/simpletypes.py +434 -0
  64. docx/oxml/styles.py +320 -0
  65. docx/oxml/table.py +977 -0
  66. docx/oxml/text/__init__.py +0 -0
  67. docx/oxml/text/font.py +358 -0
  68. docx/oxml/text/hyperlink.py +45 -0
  69. docx/oxml/text/pagebreak.py +278 -0
  70. docx/oxml/text/paragraph.py +106 -0
  71. docx/oxml/text/parfmt.py +392 -0
  72. docx/oxml/text/run.py +316 -0
  73. docx/oxml/xmlchemy.py +711 -0
  74. docx/package.py +119 -0
  75. docx/parts/__init__.py +0 -0
  76. docx/parts/comments.py +51 -0
  77. docx/parts/customxml.py +67 -0
  78. docx/parts/document.py +193 -0
  79. docx/parts/hdrftr.py +53 -0
  80. docx/parts/image.py +80 -0
  81. docx/parts/numbering.py +51 -0
  82. docx/parts/settings.py +50 -0
  83. docx/parts/story.py +113 -0
  84. docx/parts/styles.py +42 -0
  85. docx/py.typed +0 -0
  86. docx/section.py +479 -0
  87. docx/settings.py +35 -0
  88. docx/shape.py +103 -0
  89. docx/shared.py +382 -0
  90. docx/styles/__init__.py +40 -0
  91. docx/styles/latent.py +198 -0
  92. docx/styles/style.py +254 -0
  93. docx/styles/styles.py +136 -0
  94. docx/table.py +537 -0
  95. docx/templates/default-comments.xml +12 -0
  96. docx/templates/default-docx-template/[Content_Types].xml +17 -0
  97. docx/templates/default-docx-template/_rels/.rels +7 -0
  98. docx/templates/default-docx-template/customXml/_rels/item1.xml.rels +4 -0
  99. docx/templates/default-docx-template/customXml/item1.xml +2 -0
  100. docx/templates/default-docx-template/customXml/itemProps1.xml +6 -0
  101. docx/templates/default-docx-template/docProps/app.xml +36 -0
  102. docx/templates/default-docx-template/docProps/core.xml +13 -0
  103. docx/templates/default-docx-template/docProps/thumbnail.jpeg +0 -0
  104. docx/templates/default-docx-template/word/_rels/document.xml.rels +11 -0
  105. docx/templates/default-docx-template/word/document.xml +11 -0
  106. docx/templates/default-docx-template/word/fontTable.xml +61 -0
  107. docx/templates/default-docx-template/word/numbering.xml +201 -0
  108. docx/templates/default-docx-template/word/settings.xml +53 -0
  109. docx/templates/default-docx-template/word/styles.xml +11844 -0
  110. docx/templates/default-docx-template/word/stylesWithEffects.xml +11800 -0
  111. docx/templates/default-docx-template/word/theme/theme1.xml +318 -0
  112. docx/templates/default-docx-template/word/webSettings.xml +5 -0
  113. docx/templates/default-footer.xml +27 -0
  114. docx/templates/default-header.xml +27 -0
  115. docx/templates/default-numbering.xml +20 -0
  116. docx/templates/default-settings.xml +26 -0
  117. docx/templates/default-styles.xml +190 -0
  118. docx/templates/default.docx +0 -0
  119. docx/text/__init__.py +0 -0
  120. docx/text/font.py +466 -0
  121. docx/text/hyperlink.py +121 -0
  122. docx/text/pagebreak.py +104 -0
  123. docx/text/paragraph.py +173 -0
  124. docx/text/parfmt.py +286 -0
  125. docx/text/run.py +272 -0
  126. docx/text/tabstops.py +123 -0
  127. docx/types.py +34 -0
  128. python_docx_oss-0.1.0.dist-info/METADATA +57 -0
  129. python_docx_oss-0.1.0.dist-info/RECORD +132 -0
  130. python_docx_oss-0.1.0.dist-info/WHEEL +5 -0
  131. python_docx_oss-0.1.0.dist-info/licenses/LICENSE +21 -0
  132. python_docx_oss-0.1.0.dist-info/top_level.txt +1 -0
docx/parts/story.py ADDED
@@ -0,0 +1,113 @@
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_Anchor, 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
+ def new_pic_anchor(
77
+ self,
78
+ image_descriptor: str | IO[bytes],
79
+ width: int | Length | None = None,
80
+ height: int | Length | None = None,
81
+ pos_x: int | Length = 0,
82
+ pos_y: int | Length = 0,
83
+ ):
84
+ """Return a newly-created `w:anchor` element.
85
+
86
+ The element contains the image specified by `image_descriptor` and is scaled
87
+ based on the values of `width` and `height`.
88
+ """
89
+ rId, image = self.get_or_add_image(image_descriptor)
90
+ cx, cy = image.scaled_dimensions(width, height)
91
+ shape_id, filename = self.next_id, image.filename
92
+ return CT_Anchor.new_pic_anchor(shape_id, rId, filename, cx, cy, pos_x, pos_y)
93
+
94
+ @property
95
+ def next_id(self) -> int:
96
+ """Next available positive integer id value in this story XML document.
97
+
98
+ The value is determined by incrementing the maximum existing id value. Gaps in
99
+ the existing id sequence are not filled. The id attribute value is unique in the
100
+ document, without regard to the element type it appears on.
101
+ """
102
+ id_str_lst = self._element.xpath("//@id")
103
+ used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
104
+ if not used_ids:
105
+ return 1
106
+ return max(used_ids) + 1
107
+
108
+ @lazyproperty
109
+ def _document_part(self) -> DocumentPart:
110
+ """|DocumentPart| object for this package."""
111
+ package = self.package
112
+ assert package is not None
113
+ return cast("DocumentPart", package.main_document_part)
docx/parts/styles.py ADDED
@@ -0,0 +1,42 @@
1
+ """Provides StylesPart and related 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.opc.packuri import PackURI
10
+ from docx.opc.part import XmlPart
11
+ from docx.oxml.parser import parse_xml
12
+ from docx.styles.styles import Styles
13
+
14
+ if TYPE_CHECKING:
15
+ from docx.opc.package import OpcPackage
16
+
17
+
18
+ class StylesPart(XmlPart):
19
+ """Proxy for the styles.xml part containing style definitions for a document or
20
+ glossary."""
21
+
22
+ @classmethod
23
+ def default(cls, package: OpcPackage) -> StylesPart:
24
+ """Return a newly created styles part, containing a default set of elements."""
25
+ partname = PackURI("/word/styles.xml")
26
+ content_type = CT.WML_STYLES
27
+ element = parse_xml(cls._default_styles_xml())
28
+ return cls(partname, content_type, element, package)
29
+
30
+ @property
31
+ def styles(self):
32
+ """The |_Styles| instance containing the styles (<w:style> element proxies) for
33
+ this styles part."""
34
+ return Styles(self.element)
35
+
36
+ @classmethod
37
+ def _default_styles_xml(cls):
38
+ """Return a bytestream containing XML for a default styles part."""
39
+ path = os.path.join(os.path.split(__file__)[0], "..", "templates", "default-styles.xml")
40
+ with open(path, "rb") as f:
41
+ xml_bytes = f.read()
42
+ return xml_bytes
docx/py.typed ADDED
File without changes
docx/section.py ADDED
@@ -0,0 +1,479 @@
1
+ """The |Section| object and related proxy classes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Iterator, List, Sequence, overload
6
+
7
+ from docx.blkcntnr import BlockItemContainer
8
+ from docx.enum.section import WD_HEADER_FOOTER
9
+ from docx.oxml.text.paragraph import CT_P
10
+ from docx.parts.hdrftr import FooterPart, HeaderPart
11
+ from docx.shared import lazyproperty
12
+ from docx.table import Table
13
+ from docx.text.paragraph import Paragraph
14
+
15
+ if TYPE_CHECKING:
16
+ from docx.enum.section import WD_ORIENTATION, WD_SECTION_START
17
+ from docx.oxml.document import CT_Document
18
+ from docx.oxml.section import CT_SectPr
19
+ from docx.parts.document import DocumentPart
20
+ from docx.parts.story import StoryPart
21
+ from docx.shared import Length
22
+
23
+
24
+ class Section:
25
+ """Document section, providing access to section and page setup settings.
26
+
27
+ Also provides access to headers and footers.
28
+ """
29
+
30
+ def __init__(self, sectPr: CT_SectPr, document_part: DocumentPart):
31
+ super(Section, self).__init__()
32
+ self._sectPr = sectPr
33
+ self._document_part = document_part
34
+
35
+ @property
36
+ def bottom_margin(self) -> Length | None:
37
+ """Read/write. Bottom margin for pages in this section, in EMU.
38
+
39
+ `None` when no bottom margin has been specified. Assigning |None| removes any
40
+ bottom-margin setting.
41
+ """
42
+ return self._sectPr.bottom_margin
43
+
44
+ @bottom_margin.setter
45
+ def bottom_margin(self, value: int | Length | None):
46
+ self._sectPr.bottom_margin = value
47
+
48
+ @property
49
+ def different_first_page_header_footer(self) -> bool:
50
+ """True if this section displays a distinct first-page header and footer.
51
+
52
+ Read/write. The definition of the first-page header and footer are accessed
53
+ using :attr:`.first_page_header` and :attr:`.first_page_footer` respectively.
54
+ """
55
+ return self._sectPr.titlePg_val
56
+
57
+ @different_first_page_header_footer.setter
58
+ def different_first_page_header_footer(self, value: bool):
59
+ self._sectPr.titlePg_val = value
60
+
61
+ @property
62
+ def even_page_footer(self) -> _Footer:
63
+ """|_Footer| object defining footer content for even pages.
64
+
65
+ The content of this footer definition is ignored unless the document setting
66
+ :attr:`~.Settings.odd_and_even_pages_header_footer` is set True.
67
+ """
68
+ return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE)
69
+
70
+ @property
71
+ def even_page_header(self) -> _Header:
72
+ """|_Header| object defining header content for even pages.
73
+
74
+ The content of this header definition is ignored unless the document setting
75
+ :attr:`~.Settings.odd_and_even_pages_header_footer` is set True.
76
+ """
77
+ return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE)
78
+
79
+ @property
80
+ def first_page_footer(self) -> _Footer:
81
+ """|_Footer| object defining footer content for the first page of this section.
82
+
83
+ The content of this footer definition is ignored unless the property
84
+ :attr:`.different_first_page_header_footer` is set True.
85
+ """
86
+ return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE)
87
+
88
+ @property
89
+ def first_page_header(self) -> _Header:
90
+ """|_Header| object defining header content for the first page of this section.
91
+
92
+ The content of this header definition is ignored unless the property
93
+ :attr:`.different_first_page_header_footer` is set True.
94
+ """
95
+ return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE)
96
+
97
+ @lazyproperty
98
+ def footer(self) -> _Footer:
99
+ """|_Footer| object representing default page footer for this section.
100
+
101
+ The default footer is used for odd-numbered pages when separate odd/even footers
102
+ are enabled. It is used for both odd and even-numbered pages otherwise.
103
+ """
104
+ return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
105
+
106
+ @property
107
+ def footer_distance(self) -> Length | None:
108
+ """Distance from bottom edge of page to bottom edge of the footer.
109
+
110
+ Read/write. |None| if no setting is present in the XML.
111
+ """
112
+ return self._sectPr.footer
113
+
114
+ @footer_distance.setter
115
+ def footer_distance(self, value: int | Length | None):
116
+ self._sectPr.footer = value
117
+
118
+ @property
119
+ def gutter(self) -> Length | None:
120
+ """|Length| object representing page gutter size in English Metric Units.
121
+
122
+ Read/write. The page gutter is extra spacing added to the `inner` margin to
123
+ ensure even margins after page binding. Generally only used in book-bound
124
+ documents with double-sided and facing pages.
125
+
126
+ This setting applies to all pages in this section.
127
+
128
+ """
129
+ return self._sectPr.gutter
130
+
131
+ @gutter.setter
132
+ def gutter(self, value: int | Length | None):
133
+ self._sectPr.gutter = value
134
+
135
+ @lazyproperty
136
+ def header(self) -> _Header:
137
+ """|_Header| object representing default page header for this section.
138
+
139
+ The default header is used for odd-numbered pages when separate odd/even headers
140
+ are enabled. It is used for both odd and even-numbered pages otherwise.
141
+ """
142
+ return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
143
+
144
+ @property
145
+ def header_distance(self) -> Length | None:
146
+ """Distance from top edge of page to top edge of header.
147
+
148
+ Read/write. |None| if no setting is present in the XML. Assigning |None| causes
149
+ default value to be used.
150
+ """
151
+ return self._sectPr.header
152
+
153
+ @header_distance.setter
154
+ def header_distance(self, value: int | Length | None):
155
+ self._sectPr.header = value
156
+
157
+ def iter_inner_content(self) -> Iterator[Paragraph | Table]:
158
+ """Generate each Paragraph or Table object in this `section`.
159
+
160
+ Items appear in document order.
161
+ """
162
+ for element in self._sectPr.iter_inner_content():
163
+ yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
164
+
165
+ @property
166
+ def left_margin(self) -> Length | None:
167
+ """|Length| object representing the left margin for all pages in this section in
168
+ English Metric Units."""
169
+ return self._sectPr.left_margin
170
+
171
+ @left_margin.setter
172
+ def left_margin(self, value: int | Length | None):
173
+ self._sectPr.left_margin = value
174
+
175
+ @property
176
+ def orientation(self) -> WD_ORIENTATION:
177
+ """:ref:`WdOrientation` member specifying page orientation for this section.
178
+
179
+ One of ``WD_ORIENT.PORTRAIT`` or ``WD_ORIENT.LANDSCAPE``.
180
+ """
181
+ return self._sectPr.orientation
182
+
183
+ @orientation.setter
184
+ def orientation(self, value: WD_ORIENTATION | None):
185
+ self._sectPr.orientation = value
186
+
187
+ @property
188
+ def page_height(self) -> Length | None:
189
+ """Total page height used for this section.
190
+
191
+ This value is inclusive of all edge spacing values such as margins.
192
+
193
+ Page orientation is taken into account, so for example, its expected value
194
+ would be ``Inches(8.5)`` for letter-sized paper when orientation is landscape.
195
+ """
196
+ return self._sectPr.page_height
197
+
198
+ @page_height.setter
199
+ def page_height(self, value: Length | None):
200
+ self._sectPr.page_height = value
201
+
202
+ @property
203
+ def page_width(self) -> Length | None:
204
+ """Total page width used for this section.
205
+
206
+ This value is like "paper size" and includes all edge spacing values such as
207
+ margins.
208
+
209
+ Page orientation is taken into account, so for example, its expected value
210
+ would be ``Inches(11)`` for letter-sized paper when orientation is landscape.
211
+ """
212
+ return self._sectPr.page_width
213
+
214
+ @page_width.setter
215
+ def page_width(self, value: Length | None):
216
+ self._sectPr.page_width = value
217
+
218
+ @property
219
+ def part(self) -> StoryPart:
220
+ return self._document_part
221
+
222
+ @property
223
+ def right_margin(self) -> Length | None:
224
+ """|Length| object representing the right margin for all pages in this section
225
+ in English Metric Units."""
226
+ return self._sectPr.right_margin
227
+
228
+ @right_margin.setter
229
+ def right_margin(self, value: Length | None):
230
+ self._sectPr.right_margin = value
231
+
232
+ @property
233
+ def start_type(self) -> WD_SECTION_START:
234
+ """Type of page-break (if any) inserted at the start of this section.
235
+
236
+ For exmple, ``WD_SECTION_START.ODD_PAGE`` if the section should begin on the
237
+ next odd page, possibly inserting two page-breaks instead of one.
238
+ """
239
+ return self._sectPr.start_type
240
+
241
+ @start_type.setter
242
+ def start_type(self, value: WD_SECTION_START | None):
243
+ self._sectPr.start_type = value
244
+
245
+ @property
246
+ def top_margin(self) -> Length | None:
247
+ """|Length| object representing the top margin for all pages in this section in
248
+ English Metric Units."""
249
+ return self._sectPr.top_margin
250
+
251
+ @top_margin.setter
252
+ def top_margin(self, value: Length | None):
253
+ self._sectPr.top_margin = value
254
+
255
+
256
+ class Sections(Sequence[Section]):
257
+ """Sequence of |Section| objects corresponding to the sections in the document.
258
+
259
+ Supports ``len()``, iteration, and indexed access.
260
+ """
261
+
262
+ def __init__(self, document_elm: CT_Document, document_part: DocumentPart):
263
+ super(Sections, self).__init__()
264
+ self._document_elm = document_elm
265
+ self._document_part = document_part
266
+
267
+ @overload
268
+ def __getitem__(self, key: int) -> Section: ...
269
+
270
+ @overload
271
+ def __getitem__(self, key: slice) -> List[Section]: ...
272
+
273
+ def __getitem__(self, key: int | slice) -> Section | List[Section]:
274
+ if isinstance(key, slice):
275
+ return [
276
+ Section(sectPr, self._document_part)
277
+ for sectPr in self._document_elm.sectPr_lst[key]
278
+ ]
279
+ return Section(self._document_elm.sectPr_lst[key], self._document_part)
280
+
281
+ def __iter__(self) -> Iterator[Section]:
282
+ for sectPr in self._document_elm.sectPr_lst:
283
+ yield Section(sectPr, self._document_part)
284
+
285
+ def __len__(self) -> int:
286
+ return len(self._document_elm.sectPr_lst)
287
+
288
+
289
+ class _BaseHeaderFooter(BlockItemContainer):
290
+ """Base class for header and footer classes."""
291
+
292
+ def __init__(
293
+ self,
294
+ sectPr: CT_SectPr,
295
+ document_part: DocumentPart,
296
+ header_footer_index: WD_HEADER_FOOTER,
297
+ ):
298
+ self._sectPr = sectPr
299
+ self._document_part = document_part
300
+ self._hdrftr_index = header_footer_index
301
+
302
+ @property
303
+ def is_linked_to_previous(self) -> bool:
304
+ """``True`` if this header/footer uses the definition from the prior section.
305
+
306
+ ``False`` if this header/footer has an explicit definition.
307
+
308
+ Assigning ``True`` to this property removes the header/footer definition for
309
+ this section, causing it to "inherit" the corresponding definition of the prior
310
+ section. Assigning ``False`` causes a new, empty definition to be added for this
311
+ section, but only if no definition is already present.
312
+ """
313
+ # ---absence of a header/footer part indicates "linked" behavior---
314
+ return not self._has_definition
315
+
316
+ @is_linked_to_previous.setter
317
+ def is_linked_to_previous(self, value: bool) -> None:
318
+ new_state = bool(value)
319
+ # ---do nothing when value is not being changed---
320
+ if new_state == self.is_linked_to_previous:
321
+ return
322
+ if new_state is True:
323
+ self._drop_definition()
324
+ else:
325
+ self._add_definition()
326
+
327
+ @property
328
+ def part(self) -> HeaderPart | FooterPart:
329
+ """The |HeaderPart| or |FooterPart| for this header/footer.
330
+
331
+ This overrides `BlockItemContainer.part` and is required to support image
332
+ insertion and perhaps other content like hyperlinks.
333
+ """
334
+ # ---should not appear in documentation;
335
+ # ---not an interface property, even though public
336
+ return self._get_or_add_definition()
337
+
338
+ def _add_definition(self) -> HeaderPart | FooterPart:
339
+ """Return newly-added header/footer part."""
340
+ raise NotImplementedError("must be implemented by each subclass")
341
+
342
+ @property
343
+ def _definition(self) -> HeaderPart | FooterPart:
344
+ """|HeaderPart| or |FooterPart| object containing header/footer content."""
345
+ raise NotImplementedError("must be implemented by each subclass")
346
+
347
+ def _drop_definition(self) -> None:
348
+ """Remove header/footer part containing the definition of this header/footer."""
349
+ raise NotImplementedError("must be implemented by each subclass")
350
+
351
+ @property
352
+ def _element(self):
353
+ """`w:hdr` or `w:ftr` element, root of header/footer part."""
354
+ return self._get_or_add_definition().element
355
+
356
+ def _get_or_add_definition(self) -> HeaderPart | FooterPart:
357
+ """Return HeaderPart or FooterPart object for this section.
358
+
359
+ If this header/footer inherits its content, the part for the prior header/footer
360
+ is returned; this process continue recursively until a definition is found. If
361
+ the definition cannot be inherited (because the header/footer belongs to the
362
+ first section), a new definition is added for that first section and then
363
+ returned.
364
+ """
365
+ # ---note this method is called recursively to access inherited definitions---
366
+ # ---case-1: definition is not inherited---
367
+ if self._has_definition:
368
+ return self._definition
369
+ # ---case-2: definition is inherited and belongs to second-or-later section---
370
+ prior_headerfooter = self._prior_headerfooter
371
+ if prior_headerfooter:
372
+ return prior_headerfooter._get_or_add_definition()
373
+ # ---case-3: definition is inherited, but belongs to first section---
374
+ return self._add_definition()
375
+
376
+ @property
377
+ def _has_definition(self) -> bool:
378
+ """True if this header/footer has a related part containing its definition."""
379
+ raise NotImplementedError("must be implemented by each subclass")
380
+
381
+ @property
382
+ def _prior_headerfooter(self) -> _Header | _Footer | None:
383
+ """|_Header| or |_Footer| proxy on prior sectPr element.
384
+
385
+ Returns None if this is first section.
386
+ """
387
+ raise NotImplementedError("must be implemented by each subclass")
388
+
389
+
390
+ class _Footer(_BaseHeaderFooter):
391
+ """Page footer, used for all three types (default, even-page, and first-page).
392
+
393
+ Note that, like a document or table cell, a footer must contain a minimum of one
394
+ paragraph and a new or otherwise "empty" footer contains a single empty paragraph.
395
+ This first paragraph can be accessed as `footer.paragraphs[0]` for purposes of
396
+ adding content to it. Using :meth:`add_paragraph()` by itself to add content will
397
+ leave an empty paragraph above the newly added one.
398
+ """
399
+
400
+ def _add_definition(self) -> FooterPart:
401
+ """Return newly-added footer part."""
402
+ footer_part, rId = self._document_part.add_footer_part()
403
+ self._sectPr.add_footerReference(self._hdrftr_index, rId)
404
+ return footer_part
405
+
406
+ @property
407
+ def _definition(self):
408
+ """|FooterPart| object containing content of this footer."""
409
+ footerReference = self._sectPr.get_footerReference(self._hdrftr_index)
410
+ # -- currently this is never called when `._has_definition` evaluates False --
411
+ assert footerReference is not None
412
+ return self._document_part.footer_part(footerReference.rId)
413
+
414
+ def _drop_definition(self):
415
+ """Remove footer definition (footer part) associated with this section."""
416
+ rId = self._sectPr.remove_footerReference(self._hdrftr_index)
417
+ self._document_part.drop_rel(rId)
418
+
419
+ @property
420
+ def _has_definition(self) -> bool:
421
+ """True if a footer is defined for this section."""
422
+ footerReference = self._sectPr.get_footerReference(self._hdrftr_index)
423
+ return footerReference is not None
424
+
425
+ @property
426
+ def _prior_headerfooter(self):
427
+ """|_Footer| proxy on prior sectPr element or None if this is first section."""
428
+ preceding_sectPr = self._sectPr.preceding_sectPr
429
+ return (
430
+ None
431
+ if preceding_sectPr is None
432
+ else _Footer(preceding_sectPr, self._document_part, self._hdrftr_index)
433
+ )
434
+
435
+
436
+ class _Header(_BaseHeaderFooter):
437
+ """Page header, used for all three types (default, even-page, and first-page).
438
+
439
+ Note that, like a document or table cell, a header must contain a minimum of one
440
+ paragraph and a new or otherwise "empty" header contains a single empty paragraph.
441
+ This first paragraph can be accessed as `header.paragraphs[0]` for purposes of
442
+ adding content to it. Using :meth:`add_paragraph()` by itself to add content will
443
+ leave an empty paragraph above the newly added one.
444
+ """
445
+
446
+ def _add_definition(self):
447
+ """Return newly-added header part."""
448
+ header_part, rId = self._document_part.add_header_part()
449
+ self._sectPr.add_headerReference(self._hdrftr_index, rId)
450
+ return header_part
451
+
452
+ @property
453
+ def _definition(self):
454
+ """|HeaderPart| object containing content of this header."""
455
+ headerReference = self._sectPr.get_headerReference(self._hdrftr_index)
456
+ # -- currently this is never called when `._has_definition` evaluates False --
457
+ assert headerReference is not None
458
+ return self._document_part.header_part(headerReference.rId)
459
+
460
+ def _drop_definition(self):
461
+ """Remove header definition associated with this section."""
462
+ rId = self._sectPr.remove_headerReference(self._hdrftr_index)
463
+ self._document_part.drop_header_part(rId)
464
+
465
+ @property
466
+ def _has_definition(self) -> bool:
467
+ """True if a header is explicitly defined for this section."""
468
+ headerReference = self._sectPr.get_headerReference(self._hdrftr_index)
469
+ return headerReference is not None
470
+
471
+ @property
472
+ def _prior_headerfooter(self):
473
+ """|_Header| proxy on prior sectPr element or None if this is first section."""
474
+ preceding_sectPr = self._sectPr.preceding_sectPr
475
+ return (
476
+ None
477
+ if preceding_sectPr is None
478
+ else _Header(preceding_sectPr, self._document_part, self._hdrftr_index)
479
+ )
docx/settings.py ADDED
@@ -0,0 +1,35 @@
1
+ """Settings object, providing access to document-level settings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, cast
6
+
7
+ from docx.shared import ElementProxy
8
+
9
+ if TYPE_CHECKING:
10
+ import docx.types as t
11
+ from docx.oxml.settings import CT_Settings
12
+ from docx.oxml.xmlchemy import BaseOxmlElement
13
+
14
+
15
+ class Settings(ElementProxy):
16
+ """Provides access to document-level settings for a document.
17
+
18
+ Accessed using the :attr:`.Document.settings` property.
19
+ """
20
+
21
+ def __init__(self, element: BaseOxmlElement, parent: t.ProvidesXmlPart | None = None):
22
+ super().__init__(element, parent)
23
+ self._settings = cast("CT_Settings", element)
24
+
25
+ @property
26
+ def odd_and_even_pages_header_footer(self) -> bool:
27
+ """True if this document has distinct odd and even page headers and footers.
28
+
29
+ Read/write.
30
+ """
31
+ return self._settings.evenAndOddHeaders_val
32
+
33
+ @odd_and_even_pages_header_footer.setter
34
+ def odd_and_even_pages_header_footer(self, value: bool):
35
+ self._settings.evenAndOddHeaders_val = value