Skip to content

WIP: Feature Header & Footer #571

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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
_scratch/
Session.vim
/.tox/
/venv
/.idea
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
6 changes: 6 additions & 0 deletions docx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from docx.opc.parts.coreprops import CorePropertiesPart

from docx.parts.document import DocumentPart
from docx.parts.header import HeaderPart
from docx.parts.footer import FooterPart
from docx.parts.image import ImagePart
from docx.parts.numbering import NumberingPart
from docx.parts.settings import SettingsPart
Expand All @@ -21,6 +23,10 @@
def part_class_selector(content_type, reltype):
if reltype == RT.IMAGE:
return ImagePart
elif reltype == RT.HEADER:
return HeaderPart
elif reltype == RT.FOOTER:
return FooterPart
return None


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
58 changes: 58 additions & 0 deletions docx/footer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# encoding: utf-8

"""
|Footer| and closely related objects
"""

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

from docx.blkcntnr import BlockItemContainer


class Footer(BlockItemContainer):
"""
WordprocessingML (WML) footer. Not intended to be constructed directly.
"""

__slots__ = ('_part', '__body')

def __init__(self, element, part, is_linked_to_previous=False):
super(Footer, self).__init__(element, part)
self._part = part
self.__body = None
self._is_linked_to_previous = is_linked_to_previous

@property
def core_properties(self):
"""
A |CoreProperties| object providing read/write access to the core
properties of this footer.
"""
return self._part.core_properties

@property
def styles(self):
"""
A |Styles| object providing access to the styles in this footer.
"""
return self._part.styles

@property
def inline_shapes(self):
"""
An |InlineShapes| object providing access to the inline shapes in
this footer. An inline shape is a graphical object, such as
a picture, contained in a run of text and behaving like a character
glyph, being flowed like other text in a paragraph.
"""
return self._part.inline_shapes

@property
def is_linked_to_previous(self):
return self._is_linked_to_previous

@is_linked_to_previous.setter
def is_linked_to_previous(self, value):
self._is_linked_to_previous = value
58 changes: 58 additions & 0 deletions docx/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# encoding: utf-8

"""
|Header| and closely related objects
"""

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

from docx.blkcntnr import BlockItemContainer


class Header(BlockItemContainer):
"""
WordprocessingML (WML) header. Not intended to be constructed directly.
"""

__slots__ = ('_part', '__body')

def __init__(self, element=None, part=None, is_linked_to_previous=False):
super(Header, self).__init__(element, part)
self._part = part
self.__body = None
self._is_linked_to_previous = is_linked_to_previous

@property
def core_properties(self):
"""
A |CoreProperties| object providing read/write access to the core
properties of this header.
"""
return self._part.core_properties

@property
def styles(self):
"""
A |Styles| object providing access to the styles in this header.
"""
return self._part.styles

@property
def inline_shapes(self):
"""
An |InlineShapes| object providing access to the inline shapes in
this header. An inline shape is a graphical object, such as
a picture, contained in a run of text and behaving like a character
glyph, being flowed like other text in a paragraph.
"""
return self._part.inline_shapes

@property
def is_linked_to_previous(self):
return self._is_linked_to_previous

@is_linked_to_previous.setter
def is_linked_to_previous(self, value):
self._is_linked_to_previous = value
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_related_by_rids(self, rIds):
"""
Return parts to which this part has a relationship of *reltype*.
Raises |KeyError| if no such relationship is found and Provides ability to
resolve implicitly related part, such as Slide -> SlideLayout.
"""
return self.rels.parts_with_rids(rIds)

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

def parts_with_rids(self, rIds):
"""
Return target part of rel with matching *reltype*, raising |KeyError|
if not found and |ValueError| if more than one matching relationship
is found.
"""
if isinstance(rIds, list):
rels = self._get_rels_of_rids(rIds)
return [rel.target_part for rel in rels]
else:
raise ValueError('No expected type of argument rIds. Parameter rIds must be of type \'list\'')

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

def _get_rels_of_rids(self, rIds):
"""
Return relationships of *rIds* from the collection.
Raises |KeyError| if no matching relationship is found.
"""
matching = []
for rId in rIds:
match = False
for rel in self.values():
if rel.rId == rId:
match = True
matching.append(rel)
if not match:
tmpl = "no relationship of rid '%s' in collection"
raise KeyError(tmpl % str(rId))
return matching

@property
def _next_rId(self):
"""
Expand Down
14 changes: 13 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:body', CT_Body)
register_element_cls('w:document', CT_Document)

from .header import CT_Header
from .footer import CT_Footer
register_element_cls('w:hdr', CT_Header)
register_element_cls('w:ftr', CT_Footer)

from .numbering import (
CT_Num, CT_Numbering, CT_NumLvl, CT_NumPr
)
Expand All @@ -86,11 +91,14 @@ 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_HeaderReference, CT_FooterReference
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:titlePg', CT_OnOff)
register_element_cls('w:headerReference', CT_HeaderReference)
register_element_cls('w:footerReference', CT_FooterReference)

from .shape import (
CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
Expand Down Expand Up @@ -203,3 +211,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:br', CT_Br)
register_element_cls('w:r', CT_R)
register_element_cls('w:t', CT_Text)

from .settings import CT_Settings
register_element_cls('w:settings', CT_Settings)
register_element_cls('w:evenAndOddHeaders', CT_OnOff)
23 changes: 23 additions & 0 deletions docx/oxml/footer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# encoding: utf-8

"""
Custom element classes that correspond to the footer part, e.g.
<w:hdr>.
"""

from . import OxmlElement
from .xmlchemy import BaseOxmlElement, ZeroOrMore


class CT_Footer(BaseOxmlElement):
"""
``<w:ftr>``, the container element for the footer part.
"""

p = ZeroOrMore('w:p', successors=())
tbl = ZeroOrMore('w:tbl', successors=('w:sectPr',))

@classmethod
def new(cls):
footer_elm = OxmlElement('w:ftr')
return footer_elm
22 changes: 22 additions & 0 deletions docx/oxml/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# encoding: utf-8

"""
Custom element classes that correspond to the header part, e.g.
<w:hdr>.
"""

from . import OxmlElement
from .xmlchemy import BaseOxmlElement, ZeroOrMore


class CT_Header(BaseOxmlElement):
"""
``<w:hdr>``, the container element for the header part.
"""
p = ZeroOrMore('w:p', successors=())
tbl = ZeroOrMore('w:tbl', successors=('w:sectPr',))

@classmethod
def new(cls):
header_elm = OxmlElement('w:hdr')
return header_elm
Loading