"""The |Section| object and related proxy classes."""
from __future__ import annotations
from typing import TYPE_CHECKING, Iterator, List, Sequence, overload
from docx.blkcntnr import BlockItemContainer
from docx.enum.section import WD_HEADER_FOOTER
from docx.oxml.text.paragraph import CT_P
from docx.parts.hdrftr import FooterPart, HeaderPart
from docx.shared import lazyproperty
from docx.table import Table
from docx.text.paragraph import Paragraph
if TYPE_CHECKING:
from docx.enum.section import WD_ORIENTATION, WD_SECTION_START
from docx.oxml.document import CT_Document
from docx.oxml.section import CT_SectPr
from docx.parts.document import DocumentPart
from docx.parts.story import StoryPart
from docx.shared import Length
[文档]
class Section:
"""Document section, providing access to section and page setup settings.
Also provides access to headers and footers.
"""
def __init__(self, sectPr: CT_SectPr, document_part: DocumentPart):
super(Section, self).__init__()
self._sectPr = sectPr
self._document_part = document_part
@property
def bottom_margin(self) -> Length | None:
"""Read/write. Bottom margin for pages in this section, in EMU.
`None` when no bottom margin has been specified. Assigning |None| removes any
bottom-margin setting.
"""
return self._sectPr.bottom_margin
@bottom_margin.setter
def bottom_margin(self, value: int | Length | None):
self._sectPr.bottom_margin = value
@property
def different_first_page_header_footer(self) -> bool:
"""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: bool):
self._sectPr.titlePg_val = value
@property
def even_page_footer(self) -> _Footer:
"""|_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:
"""|_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:
"""|_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:
"""|_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)
@property
def footer_distance(self) -> Length | None:
"""Distance from bottom edge of page to bottom edge of the footer.
Read/write. |None| if no setting is present in the XML.
"""
return self._sectPr.footer
@footer_distance.setter
def footer_distance(self, value: int | Length | None):
self._sectPr.footer = value
@property
def gutter(self) -> Length | None:
"""|Length| object representing page gutter size in English Metric Units.
Read/write. The page gutter is extra spacing added to the `inner` margin to
ensure even margins after page binding. Generally only used in book-bound
documents with double-sided and facing pages.
This setting applies to all pages in this section.
"""
return self._sectPr.gutter
@gutter.setter
def gutter(self, value: int | Length | None):
self._sectPr.gutter = value
@property
def header_distance(self) -> Length | None:
"""Distance from top edge of page to top edge of header.
Read/write. |None| if no setting is present in the XML. Assigning |None| causes
default value to be used.
"""
return self._sectPr.header
@header_distance.setter
def header_distance(self, value: int | Length | None):
self._sectPr.header = value
[文档]
def iter_inner_content(self) -> Iterator[Paragraph | Table]:
"""Generate each Paragraph or Table object in this `section`.
Items appear in document order.
"""
for element in self._sectPr.iter_inner_content():
yield (Paragraph(element, self) if isinstance(element, CT_P) else Table(element, self))
@property
def left_margin(self) -> Length | None:
"""|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: int | Length | None):
self._sectPr.left_margin = value
@property
def orientation(self) -> WD_ORIENTATION:
""":ref:`WdOrientation` member specifying page orientation for this section.
One of ``WD_ORIENT.PORTRAIT`` or ``WD_ORIENT.LANDSCAPE``.
"""
return self._sectPr.orientation
@orientation.setter
def orientation(self, value: WD_ORIENTATION | None):
self._sectPr.orientation = value
@property
def page_height(self) -> Length | None:
"""Total page height used for this section.
This value is 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: Length | None):
self._sectPr.page_height = value
@property
def page_width(self) -> Length | None:
"""Total page width used for this section.
This value is like "paper size" and includes 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: Length | None):
self._sectPr.page_width = value
@property
def part(self) -> StoryPart:
return self._document_part
@property
def right_margin(self) -> Length | None:
"""|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: Length | None):
self._sectPr.right_margin = value
@property
def start_type(self) -> WD_SECTION_START:
"""Type of page-break (if any) inserted at the start of this section.
For exmple, ``WD_SECTION_START.ODD_PAGE`` if the section should begin on the
next odd page, possibly inserting two page-breaks instead of one.
"""
return self._sectPr.start_type
@start_type.setter
def start_type(self, value: WD_SECTION_START | None):
self._sectPr.start_type = value
@property
def top_margin(self) -> Length | None:
"""|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: Length | None):
self._sectPr.top_margin = value
[文档]
class Sections(Sequence[Section]):
"""Sequence of |Section| objects corresponding to the sections in the document.
Supports ``len()``, iteration, and indexed access.
"""
def __init__(self, document_elm: CT_Document, document_part: DocumentPart):
super(Sections, self).__init__()
self._document_elm = document_elm
self._document_part = document_part
@overload
def __getitem__(self, key: int) -> Section: ...
@overload
def __getitem__(self, key: slice) -> List[Section]: ...
def __getitem__(self, key: int | slice) -> Section | List[Section]:
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) -> Iterator[Section]:
for sectPr in self._document_elm.sectPr_lst:
yield Section(sectPr, self._document_part)
def __len__(self) -> int:
return len(self._document_elm.sectPr_lst)
class _BaseHeaderFooter(BlockItemContainer):
"""Base class for header and footer classes."""
def __init__(
self,
sectPr: CT_SectPr,
document_part: DocumentPart,
header_footer_index: WD_HEADER_FOOTER,
):
self._sectPr = sectPr
self._document_part = document_part
self._hdrftr_index = header_footer_index
@property
def is_linked_to_previous(self) -> bool:
"""``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: bool) -> None:
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) -> HeaderPart | FooterPart:
"""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) -> HeaderPart | FooterPart:
"""Return newly-added header/footer part."""
raise NotImplementedError("must be implemented by each subclass")
@property
def _definition(self) -> HeaderPart | FooterPart:
"""|HeaderPart| or |FooterPart| object containing header/footer content."""
raise NotImplementedError("must be implemented by each subclass")
def _drop_definition(self) -> None:
"""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) -> HeaderPart | FooterPart:
"""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) -> bool:
"""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 | _Footer | None:
"""|_Header| or |_Footer| proxy on prior sectPr element.
Returns None if this is first section.
"""
raise NotImplementedError("must be implemented by each subclass")