Skip to content

Feature header & Footer #581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ python:
# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -r requirements.txt
# command to run tests, e.g. python setup.py test
script: py.test
script: py.test
3 changes: 3 additions & 0 deletions docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from docx.parts.numbering import NumberingPart
from docx.parts.settings import SettingsPart
from docx.parts.styles import StylesPart
from docx.parts.header_footer import HeaderPart, FooterPart


def part_class_selector(content_type, reltype):
Expand All @@ -30,6 +31,8 @@ def part_class_selector(content_type, reltype):
PartFactory.part_type_for[CT.WML_NUMBERING] = NumberingPart
PartFactory.part_type_for[CT.WML_SETTINGS] = SettingsPart
PartFactory.part_type_for[CT.WML_STYLES] = StylesPart
PartFactory.part_type_for[CT.WML_HEADER] = HeaderPart
PartFactory.part_type_for[CT.WML_FOOTER] = FooterPart

del (
CT, CorePropertiesPart, DocumentPart, NumberingPart, PartFactory,
Expand Down
4 changes: 2 additions & 2 deletions docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def add_section(self, start_type=WD_SECTION.NEW_PAGE):
"""
new_sectPr = self._element.body.add_section_break()
new_sectPr.start_type = start_type
return Section(new_sectPr)
return Section(new_sectPr, self._part)

def add_table(self, rows, cols, style=None):
"""
Expand Down Expand Up @@ -147,7 +147,7 @@ def sections(self):
A |Sections| object providing access to each section in this
document.
"""
return Sections(self._element)
return Sections(self._element, self._part)

@property
def settings(self):
Expand Down
42 changes: 42 additions & 0 deletions docx/enum/header_footer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# encoding: utf-8

"""
Enumerations related to the main document in WordprocessingML files
"""

from __future__ import absolute_import, print_function, unicode_literals

from .base import alias, XmlEnumeration, XmlMappedEnumMember


@alias('WD_HEADER_FOOTER')
class WD_HEADER_FOOTER(XmlEnumeration):
"""
alias: **WD_HEADER_FOOTER_INDEX**

Specified header or footer in a document or section.

Example::

from docx.enum.header_footer import WD_HEADER_FOOTER

header = document.sections[-1].header
first_page_header = document.sections[-1].first_page_header
even_odd_header = document.sections[-1].even_odd_header
"""

__ms_name__ = 'WdHeaderFooterIndex'

__url__ = 'https://docs.microsoft.com/en-us/office/vba/api/word.wdheaderfooterindex'

__members__ = (
XmlMappedEnumMember(
'PRIMARY', 1, 'default', 'Header or footer on all pages other than the first page of a document or section.'
),
XmlMappedEnumMember(
'FIRST_PAGE', 2, 'first', 'First header or footer in a document or section.'
),
XmlMappedEnumMember(
'EVEN_PAGE', 3, 'even', 'Headers or footers on even-numbered pages.'
),
)
145 changes: 145 additions & 0 deletions docx/header_footer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# encoding: utf-8

"""
|Header| and |Footer| objects
"""

from __future__ import (
absolute_import, division, print_function, unicode_literals
)

from .blkcntnr import BlockItemContainer
from .shared import ElementProxy, Emu


class _HeaderFooter(ElementProxy):

__slots__ = ('_type', '__body')

def __init__(self, element, parent, hf_type):
super(_HeaderFooter, self).__init__(element, parent)
self._type = hf_type
self.__body = None

def add_paragraph(self, text='', style=None):
"""
Return a paragraph, populated
with *text* and having paragraph style *style*. *text* can contain
tab (``\\t``) characters, which are converted to the appropriate XML
form for a tab. *text* can also include newline (``\\n``) or carriage
return (``\\r``) characters, each of which is converted to a line
break.
"""
return self._body.add_paragraph(text, style)

@property
def paragraphs(self):
"""
A list of |Paragraph| instances corresponding to the paragraphs in
this object. Note that paragraphs within revision
marks such as ``<w:ins>`` or ``<w:del>`` do not appear in this list.
"""
return self._body.paragraphs

def add_table(self, rows, cols, style=None):
"""
Add a table having row and column counts of *rows* and *cols*
respectively and table style of *style*. *style* may be a paragraph
style object or a paragraph style name. If *style* is |None|, the
table inherits the default table style of the document.
"""
table = self._body.add_table(rows, cols, self._block_width)
table.style = style
return table

@property
def tables(self):
"""
A list of |Table| instances corresponding to the tables in
this object. Note that only tables appearing at the
top level of the document appear in this list; a table nested inside
a table cell does not appear. A table within revision marks such as
``<w:ins>`` or ``<w:del>`` will also not appear in the list.
"""

return self._body.tables

@property
def _block_width(self):
"""
Return a |Length| object specifying the width of available "writing"
space between the margins of the last section of this document.
"""
section = self._parent
return Emu(
section.page_width - section.left_margin - section.right_margin
)

@property
def _body(self):
if self.__body is None:
ref = self._parent.sectPr.get_header_reference_of_type(self._type)
if ref is None:
return None
part = self.part.get_related_part(ref.rId)
if part is not None:
self.__body = _HeaderFooterBody(part._element, self)

return self.__body


class Header(_HeaderFooter):

@property
def is_linked_to_previous(self):
"""

:return: True when is linked to previous header otherwise false
"""
if self._parent is None:
return True
return True if self._parent.sectPr.get_header_reference_of_type(self._type) is None else False

@is_linked_to_previous.setter
def is_linked_to_previous(self, value):
"""
- if previous value is True and value is False then need to create new header
- if previous value is False and value is True then need to remove reference
"""
if self.is_linked_to_previous is True and value is False:
rId = self.part.add_header_part()
self._parent.sectPr.add_header_reference_of_type(rId, self._type)
self.add_paragraph('')
elif self.is_linked_to_previous is False and value is True:
self._parent.sectPr.remove_header_reference(self._type)
self.__body = None


class Footer(_HeaderFooter):

@property
def is_linked_to_previous(self):
"""

:return: True when is linked otherwise false
"""
if self._parent is None:
return True
return True if self._parent.sectPr.get_footer_reference_of_type(self._type) is None else False

@is_linked_to_previous.setter
def is_linked_to_previous(self, value):
"""
- if previous value is True and value is False then need to create new footer
- if previous value is False and value is True then need to remove reference
"""
if self.is_linked_to_previous is True and value is False:
rId = self.part.add_footer_part(self._type)
self._parent.sectPr.add_footer_reference_of_type(rId, self._type)
elif self.is_linked_to_previous is False and value is True:
self._parent.sectPr.remove_footer_reference(self._type)
self.__body = None


class _HeaderFooterBody(BlockItemContainer):
pass
8 changes: 8 additions & 0 deletions docx/opc/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ def part_related_by(self, reltype):
"""
return self.rels.part_with_reltype(reltype)

def parts_by_type(self, reltype):
"""
Return parts to which this part has a relationship of *reltype*.
Raises |KeyError| if no such relationship is found and |ValueError|
Provides ability to resolve implicitly related part, such as Slide -> SlideLayout.
"""
return self.rels.parts_with_reltype(reltype)

def relate_to(self, target, reltype, is_external=False):
"""
Return rId key of relationship of *reltype* to *target*, from an
Expand Down
15 changes: 15 additions & 0 deletions docx/opc/rel.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def part_with_reltype(self, reltype):
rel = self._get_rel_of_type(reltype)
return rel.target_part

def parts_with_reltype(self, reltype):
"""
Return target parts of rel with matching *reltype*, raising |KeyError|
if not found
"""
rels = self._get_rels_of_type(reltype)
return [rel.target_part for rel in rels]

@property
def related_parts(self):
"""
Expand Down Expand Up @@ -119,6 +127,13 @@ def _get_rel_of_type(self, reltype):
raise ValueError(tmpl % reltype)
return matching[0]

def _get_rels_of_type(self, reltype):
matching = [rel for rel in self.values() if rel.reltype == reltype]
if len(matching) == 0:
tmpl = "no relationship of type '%s' in collection"
raise KeyError(tmpl % reltype)
return matching

@property
def _next_rId(self):
"""
Expand Down
12 changes: 11 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,21 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:numbering', CT_Numbering)
register_element_cls('w:startOverride', CT_DecimalNumber)

from .section import CT_PageMar, CT_PageSz, CT_SectPr, CT_SectType
from .section import CT_PageMar, CT_PageSz, CT_SectPr, CT_SectType, CT_HdrFtrRef, CT_HdrFtr
register_element_cls('w:pgMar', CT_PageMar)
register_element_cls('w:pgSz', CT_PageSz)
register_element_cls('w:sectPr', CT_SectPr)
register_element_cls('w:type', CT_SectType)
register_element_cls('w:headerReference', CT_HdrFtrRef)
register_element_cls('w:footerReference', CT_HdrFtrRef)
register_element_cls('w:hdr', CT_HdrFtr)
register_element_cls('w:ftr', CT_HdrFtr)
register_element_cls('w:titlePg', CT_OnOff)


from .settings import CT_Settings
register_element_cls('w:settings', CT_Settings)
register_element_cls('w:evenAndOddHeaders', CT_OnOff)

from .shape import (
CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
Expand Down
15 changes: 15 additions & 0 deletions docx/oxml/base_complex_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# encoding: utf-8

"""
Simple complex types classes represent base elements
"""

from .xmlchemy import BaseOxmlElement, RequiredAttribute
from .simpletypes import ST_RelationshipId


class CT_Rel(BaseOxmlElement):
"""
``<r:id>`` element, defining the rId .
"""
rId = RequiredAttribute('r:id', ST_RelationshipId)
Loading