forked from python-openxml/python-docx
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsection.py
443 lines (358 loc) · 15.8 KB
/
section.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# encoding: utf-8
"""The |Section| object and related proxy classes"""
from __future__ import absolute_import, division, print_function, unicode_literals
from docx.blkcntnr import BlockItemContainer
from docx.compat import Sequence
from docx.enum.section import WD_HEADER_FOOTER
from docx.shared import lazyproperty
class Sections(Sequence):
"""Sequence of |Section| objects corresponding to the sections in the document.
Supports ``len()``, iteration, and indexed access.
"""
def __init__(self, document_elm, document_part):
super(Sections, self).__init__()
self._document_elm = document_elm
self._document_part = document_part
def __getitem__(self, key):
if isinstance(key, slice):
return [
Section(sectPr, self._document_part)
for sectPr in self._document_elm.sectPr_lst[key]
]
return Section(self._document_elm.sectPr_lst[key], self._document_part)
def __iter__(self):
for sectPr in self._document_elm.sectPr_lst:
yield Section(sectPr, self._document_part)
def __len__(self):
return len(self._document_elm.sectPr_lst)
class Section(object):
"""Document section, providing access to section and page setup settings.
Also provides access to headers and footers.
"""
def __init__(self, sectPr, document_part):
super(Section, self).__init__()
self._sectPr = sectPr
self._document_part = document_part
@property
def bottom_margin(self):
"""
|Length| object representing the bottom margin for all pages in this
section in English Metric Units.
"""
return self._sectPr.bottom_margin
@bottom_margin.setter
def bottom_margin(self, value):
self._sectPr.bottom_margin = value
@property
def different_first_page_header_footer(self):
"""True if this section displays a distinct first-page header and footer.
Read/write. The definition of the first-page header and footer are accessed
using :attr:`.first_page_header` and :attr:`.first_page_footer` respectively.
"""
return self._sectPr.titlePg_val
@different_first_page_header_footer.setter
def different_first_page_header_footer(self, value):
self._sectPr.titlePg_val = value
@property
def even_page_footer(self):
"""|_Footer| object defining footer content for even pages.
The content of this footer definition is ignored unless the document setting
:attr:`~.Settings.odd_and_even_pages_header_footer` is set True.
"""
return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE)
@property
def even_page_header(self):
"""|_Header| object defining header content for even pages.
The content of this header definition is ignored unless the document setting
:attr:`~.Settings.odd_and_even_pages_header_footer` is set True.
"""
return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.EVEN_PAGE)
@property
def first_page_footer(self):
"""|_Footer| object defining footer content for the first page of this section.
The content of this footer definition is ignored unless the property
:attr:`.different_first_page_header_footer` is set True.
"""
return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE)
@property
def first_page_header(self):
"""|_Header| object defining header content for the first page of this section.
The content of this header definition is ignored unless the property
:attr:`.different_first_page_header_footer` is set True.
"""
return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.FIRST_PAGE)
@lazyproperty
def footer(self):
"""|_Footer| object representing default page footer for this section.
The default footer is used for odd-numbered pages when separate odd/even footers
are enabled. It is used for both odd and even-numbered pages otherwise.
"""
return _Footer(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
@property
def footer_distance(self):
"""
|Length| object representing the distance from the bottom edge of the
page to the bottom edge of the footer. |None| if no setting is present
in the XML.
"""
return self._sectPr.footer
@footer_distance.setter
def footer_distance(self, value):
self._sectPr.footer = value
@property
def gutter(self):
"""
|Length| object representing the page gutter size in English Metric
Units for all pages in this section. The page gutter is extra spacing
added to the *inner* margin to ensure even margins after page
binding.
"""
return self._sectPr.gutter
@gutter.setter
def gutter(self, value):
self._sectPr.gutter = value
@lazyproperty
def header(self):
"""|_Header| object representing default page header for this section.
The default header is used for odd-numbered pages when separate odd/even headers
are enabled. It is used for both odd and even-numbered pages otherwise.
"""
return _Header(self._sectPr, self._document_part, WD_HEADER_FOOTER.PRIMARY)
@property
def header_distance(self):
"""
|Length| object representing the distance from the top edge of the
page to the top edge of the header. |None| if no setting is present
in the XML.
"""
return self._sectPr.header
@header_distance.setter
def header_distance(self, value):
self._sectPr.header = value
@property
def left_margin(self):
"""
|Length| object representing the left margin for all pages in this
section in English Metric Units.
"""
return self._sectPr.left_margin
@left_margin.setter
def left_margin(self, value):
self._sectPr.left_margin = value
@property
def orientation(self):
"""
Member of the :ref:`WdOrientation` enumeration specifying the page
orientation for this section, one of ``WD_ORIENT.PORTRAIT`` or
``WD_ORIENT.LANDSCAPE``.
"""
return self._sectPr.orientation
@orientation.setter
def orientation(self, value):
self._sectPr.orientation = value
@property
def page_height(self):
"""
Total page height used for this section, inclusive of all edge spacing
values such as margins. Page orientation is taken into account, so
for example, its expected value would be ``Inches(8.5)`` for
letter-sized paper when orientation is landscape.
"""
return self._sectPr.page_height
@page_height.setter
def page_height(self, value):
self._sectPr.page_height = value
@property
def page_width(self):
"""
Total page width used for this section, inclusive of all edge spacing
values such as margins. Page orientation is taken into account, so
for example, its expected value would be ``Inches(11)`` for
letter-sized paper when orientation is landscape.
"""
return self._sectPr.page_width
@page_width.setter
def page_width(self, value):
self._sectPr.page_width = value
@property
def right_margin(self):
"""
|Length| object representing the right margin for all pages in this
section in English Metric Units.
"""
return self._sectPr.right_margin
@right_margin.setter
def right_margin(self, value):
self._sectPr.right_margin = value
@property
def start_type(self):
"""
The member of the :ref:`WdSectionStart` enumeration corresponding to
the initial break behavior of this section, e.g.
``WD_SECTION.ODD_PAGE`` if the section should begin on the next odd
page.
"""
return self._sectPr.start_type
@start_type.setter
def start_type(self, value):
self._sectPr.start_type = value
@property
def top_margin(self):
"""
|Length| object representing the top margin for all pages in this
section in English Metric Units.
"""
return self._sectPr.top_margin
@top_margin.setter
def top_margin(self, value):
self._sectPr.top_margin = value
class _BaseHeaderFooter(BlockItemContainer):
"""Base class for header and footer classes"""
def __init__(self, sectPr, document_part, header_footer_index):
self._sectPr = sectPr
self._document_part = document_part
self._hdrftr_index = header_footer_index
@property
def is_linked_to_previous(self):
"""``True`` if this header/footer uses the definition from the prior section.
``False`` if this header/footer has an explicit definition.
Assigning ``True`` to this property removes the header/footer definition for
this section, causing it to "inherit" the corresponding definition of the prior
section. Assigning ``False`` causes a new, empty definition to be added for this
section, but only if no definition is already present.
"""
# ---absence of a header/footer part indicates "linked" behavior---
return not self._has_definition
@is_linked_to_previous.setter
def is_linked_to_previous(self, value):
new_state = bool(value)
# ---do nothing when value is not being changed---
if new_state == self.is_linked_to_previous:
return
if new_state is True:
self._drop_definition()
else:
self._add_definition()
@property
def part(self):
"""The |HeaderPart| or |FooterPart| for this header/footer.
This overrides `BlockItemContainer.part` and is required to support image
insertion and perhaps other content like hyperlinks.
"""
# ---should not appear in documentation;
# ---not an interface property, even though public
return self._get_or_add_definition()
def _add_definition(self):
"""Return newly-added header/footer part."""
raise NotImplementedError("must be implemented by each subclass")
@property
def _definition(self):
"""|HeaderPart| or |FooterPart| object containing header/footer content."""
raise NotImplementedError("must be implemented by each subclass")
def _drop_definition(self):
"""Remove header/footer part containing the definition of this header/footer."""
raise NotImplementedError("must be implemented by each subclass")
@property
def _element(self):
"""`w:hdr` or `w:ftr` element, root of header/footer part."""
return self._get_or_add_definition().element
def _get_or_add_definition(self):
"""Return HeaderPart or FooterPart object for this section.
If this header/footer inherits its content, the part for the prior header/footer
is returned; this process continue recursively until a definition is found. If
the definition cannot be inherited (because the header/footer belongs to the
first section), a new definition is added for that first section and then
returned.
"""
# ---note this method is called recursively to access inherited definitions---
# ---case-1: definition is not inherited---
if self._has_definition:
return self._definition
# ---case-2: definition is inherited and belongs to second-or-later section---
prior_headerfooter = self._prior_headerfooter
if prior_headerfooter:
return prior_headerfooter._get_or_add_definition()
# ---case-3: definition is inherited, but belongs to first section---
return self._add_definition()
@property
def _has_definition(self):
"""True if this header/footer has a related part containing its definition."""
raise NotImplementedError("must be implemented by each subclass")
@property
def _prior_headerfooter(self):
"""|_Header| or |_Footer| proxy on prior sectPr element.
Returns None if this is first section.
"""
raise NotImplementedError("must be implemented by each subclass")
class _Footer(_BaseHeaderFooter):
"""Page footer, used for all three types (default, even-page, and first-page).
Note that, like a document or table cell, a footer must contain a minimum of one
paragraph and a new or otherwise "empty" footer contains a single empty paragraph.
This first paragraph can be accessed as `footer.paragraphs[0]` for purposes of
adding content to it. Using :meth:`add_paragraph()` by itself to add content will
leave an empty paragraph above the newly added one.
"""
def _add_definition(self):
"""Return newly-added footer part."""
footer_part, rId = self._document_part.add_footer_part()
self._sectPr.add_footerReference(self._hdrftr_index, rId)
return footer_part
@property
def _definition(self):
"""|FooterPart| object containing content of this footer."""
footerReference = self._sectPr.get_footerReference(self._hdrftr_index)
return self._document_part.footer_part(footerReference.rId)
def _drop_definition(self):
"""Remove footer definition (footer part) associated with this section."""
rId = self._sectPr.remove_footerReference(self._hdrftr_index)
self._document_part.drop_rel(rId)
@property
def _has_definition(self):
"""True if a footer is defined for this section."""
footerReference = self._sectPr.get_footerReference(self._hdrftr_index)
return False if footerReference is None else True
@property
def _prior_headerfooter(self):
"""|_Footer| proxy on prior sectPr element or None if this is first section."""
preceding_sectPr = self._sectPr.preceding_sectPr
return (
None
if preceding_sectPr is None
else _Footer(preceding_sectPr, self._document_part, self._hdrftr_index)
)
class _Header(_BaseHeaderFooter):
"""Page header, used for all three types (default, even-page, and first-page).
Note that, like a document or table cell, a header must contain a minimum of one
paragraph and a new or otherwise "empty" header contains a single empty paragraph.
This first paragraph can be accessed as `header.paragraphs[0]` for purposes of
adding content to it. Using :meth:`add_paragraph()` by itself to add content will
leave an empty paragraph above the newly added one.
"""
def _add_definition(self):
"""Return newly-added header part."""
header_part, rId = self._document_part.add_header_part()
self._sectPr.add_headerReference(self._hdrftr_index, rId)
return header_part
@property
def _definition(self):
"""|HeaderPart| object containing content of this header."""
headerReference = self._sectPr.get_headerReference(self._hdrftr_index)
return self._document_part.header_part(headerReference.rId)
def _drop_definition(self):
"""Remove header definition associated with this section."""
rId = self._sectPr.remove_headerReference(self._hdrftr_index)
self._document_part.drop_header_part(rId)
@property
def _has_definition(self):
"""True if a header is explicitly defined for this section."""
headerReference = self._sectPr.get_headerReference(self._hdrftr_index)
return False if headerReference is None else True
@property
def _prior_headerfooter(self):
"""|_Header| proxy on prior sectPr element or None if this is first section."""
preceding_sectPr = self._sectPr.preceding_sectPr
return (
None
if preceding_sectPr is None
else _Header(preceding_sectPr, self._document_part, self._hdrftr_index)
)