Skip to content

Commit 5093714

Browse files
committed
make header / footer rels and styles play nicely
1 parent 07566b5 commit 5093714

File tree

4 files changed

+167
-21
lines changed

4 files changed

+167
-21
lines changed

docx/document.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
)
1010

1111
from .oxml import OxmlElement
12-
from .oxml.header import CT_Hdr
12+
from .oxml.header import CT_Hdr, CT_Ftr
1313
from .oxml.ns import qn, nsmap
1414
from .opc.constants import RELATIONSHIP_TYPE as RT, CONTENT_TYPE as CT
1515
from .opc.packuri import PackURI
16-
from .opc.part import XmlPart
16+
from .parts.header import HeaderPart, FooterPart
17+
from .header import Header, Footer
1718
from .blkcntnr import BlockItemContainer
1819
from .enum.section import WD_SECTION
1920
from .enum.text import WD_BREAK
@@ -231,7 +232,6 @@ def __init__(self, body_elm, parent):
231232

232233
def add_header(self):
233234
rel_id = self._parent.part.rels._next_rId
234-
target = 'header1.xml'
235235

236236
# make header_ref_elm
237237
header_ref_elm_tag = 'w:headerReference'
@@ -244,24 +244,23 @@ def add_header(self):
244244
# make header_elm
245245
header_elm = CT_Hdr.new()
246246

247-
# make header instance (wrapper around elm)
248-
header = BlockItemContainer(header_elm, self)
249-
250247
# make target part
251248
partname = PackURI('/word/header1.xml')
252249
content_type = CT.WML_HEADER
253-
target = XmlPart(partname, content_type, header_elm, self._parent._part.package)
250+
header_part = HeaderPart(partname, content_type, header_elm, self._parent._part.package)
251+
252+
# make header instance (wrapper around elm)
253+
header = Header(header_elm, self._parent, header_part)
254254

255255
reltype = nsmap['r'] + '/header'
256-
self._parent.part.rels.add_relationship(reltype, target, rel_id)
256+
self._parent.part.rels.add_relationship(reltype, header_part, rel_id)
257257

258258
sentinel_sectPr = self._body.get_or_add_sectPr()
259259
sentinel_sectPr.append(header_ref_elm)
260260
return header
261261

262262
def add_footer(self):
263263
rel_id = self._parent.part.rels._next_rId
264-
target = 'footer1.xml'
265264

266265
# make footer_ref_elm
267266
footer_ref_elm_tag = 'w:footerReference'
@@ -272,18 +271,18 @@ def add_footer(self):
272271
footer_ref_elm = OxmlElement(footer_ref_elm_tag, attrs=footer_attrs)
273272

274273
# make footer_elm
275-
footer_elm = CT_Hdr.new()
276-
277-
# make footer instance (wrapper around elm)
278-
footer = BlockItemContainer(footer_elm, self)
274+
footer_elm = CT_Ftr.new()
279275

280276
# make target part
281277
partname = PackURI('/word/footer1.xml')
282278
content_type = CT.WML_FOOTER
283-
target = XmlPart(partname, content_type, footer_elm, self._parent._part.package)
279+
footer_part = FooterPart(partname, content_type, footer_elm, self._parent._part.package)
280+
281+
# make footer instance (wrapper around elm)
282+
footer = Footer(footer_elm, self, footer_part)
284283

285284
reltype = nsmap['r'] + '/footer'
286-
self._parent.part.rels.add_relationship(reltype, target, rel_id)
285+
self._parent.part.rels.add_relationship(reltype, footer_part, rel_id)
287286

288287
sentinel_sectPr = self._body.get_or_add_sectPr()
289288
sentinel_sectPr.append(footer_ref_elm)

docx/header.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
from .blkcntnr import BlockItemContainer
22

33

4-
# TODO figure out if this needed?
5-
6-
74
class Header(BlockItemContainer):
85
"""
96
Proxy object wrapping ``<w:p>`` element.
107
"""
11-
def __init__(self, header_elm, parent):
8+
def __init__(self, header_elm, parent, part):
129
super(Header, self).__init__(header_elm, parent)
10+
self._part = part
11+
12+
@property
13+
def part(self):
14+
return self._part
15+
16+
@property
17+
def styles(self):
18+
"""
19+
A |Styles| object providing access to the styles in this document.
20+
"""
21+
return self._part.styles
1322

14-
# add paragraph inherited from parent
15-
# add image needs to be added I think?
23+
@property
24+
def inline_shapes(self):
25+
"""
26+
An |InlineShapes| object providing access to the inline shapes in
27+
this document. An inline shape is a graphical object, such as
28+
a picture, contained in a run of text and behaving like a character
29+
glyph, being flowed like other text in a paragraph.
30+
"""
31+
return self._part.inline_shapes
32+
33+
34+
class Footer(Header):
35+
"""
36+
Same as header atm
37+
"""
38+
pass

docx/oxml/header.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,15 @@ class CT_Hdr(BaseOxmlElement):
1212
def new(cls):
1313
header_elm = OxmlElement('w:hdr')
1414
return header_elm
15+
16+
17+
class CT_Ftr(BaseOxmlElement):
18+
"""
19+
``<w:hdr>``, the container element for the header content
20+
"""
21+
p = ZeroOrMore('w:p', successors=())
22+
23+
@classmethod
24+
def new(cls):
25+
header_elm = OxmlElement('w:ftr')
26+
return header_elm

docx/parts/header.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
from ..oxml.shape import CT_Inline
2+
from ..opc.constants import RELATIONSHIP_TYPE as RT
3+
from ..opc.part import XmlPart
4+
from ..shape import InlineShapes
5+
from ..shared import lazyproperty
6+
from .styles import StylesPart
7+
8+
9+
class HeaderPart(XmlPart):
10+
# COPYPASTA FROM DOCUMENT PART BELOW THIS POINT
11+
# TODO ABSTRACT?
12+
13+
# @lazyproperty
14+
# def inline_shapes(self):
15+
# """
16+
# The |InlineShapes| instance containing the inline shapes in the
17+
# document.
18+
# """
19+
# return InlineShapes(self._element.body, self)
20+
21+
@property
22+
def next_id(self):
23+
"""
24+
The next available positive integer id value in this document. Gaps
25+
in id sequence are filled. The id attribute value is unique in the
26+
document, without regard to the element type it appears on.
27+
"""
28+
id_str_lst = self._element.xpath('//@id')
29+
used_ids = [int(id_str) for id_str in id_str_lst if id_str.isdigit()]
30+
for n in range(1, len(used_ids)+2):
31+
if n not in used_ids:
32+
return n
33+
34+
def get_or_add_image(self, image_descriptor):
35+
"""
36+
Return an (rId, image) 2-tuple for the image identified by
37+
*image_descriptor*. *image* is an |Image| instance providing access
38+
to the properties of the image, such as dimensions and image type.
39+
*rId* is the key for the relationship between this document part and
40+
the image part, reused if already present, newly created if not.
41+
"""
42+
image_part = self._package.image_parts.get_or_add_image_part(
43+
image_descriptor
44+
)
45+
rId = self.relate_to(image_part, RT.IMAGE)
46+
return rId, image_part.image
47+
48+
def new_pic_inline(self, image_descriptor, width, height):
49+
"""
50+
Return a newly-created `w:inline` element containing the image
51+
specified by *image_descriptor* and scaled based on the values of
52+
*width* and *height*.
53+
"""
54+
rId, image = self.get_or_add_image(image_descriptor)
55+
cx, cy = image.scaled_dimensions(width, height)
56+
shape_id, filename = self.next_id, image.filename
57+
return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy)
58+
59+
def get_style(self, style_id, style_type):
60+
"""
61+
Return the style in this document matching *style_id*. Returns the
62+
default style for *style_type* if *style_id* is |None| or does not
63+
match a defined style of *style_type*.
64+
"""
65+
return self.styles.get_by_id(style_id, style_type)
66+
67+
def get_style_id(self, style_or_name, style_type):
68+
"""
69+
Return the style_id (|str|) of the style of *style_type* matching
70+
*style_or_name*. Returns |None| if the style resolves to the default
71+
style for *style_type* or if *style_or_name* is itself |None|. Raises
72+
if *style_or_name* is a style of the wrong type or names a style not
73+
present in the document.
74+
"""
75+
return self.styles.get_style_id(style_or_name, style_type)
76+
77+
@lazyproperty
78+
def inline_shapes(self):
79+
"""
80+
The |InlineShapes| instance containing the inline shapes in the
81+
document.
82+
"""
83+
return InlineShapes(self._element.body, self)
84+
85+
@property
86+
def styles(self):
87+
"""
88+
A |Styles| object providing access to the styles in the styles part
89+
of this document.
90+
"""
91+
return self._styles_part.styles
92+
93+
@property
94+
def _styles_part(self):
95+
"""
96+
Instance of |StylesPart| for this document. Creates an empty styles
97+
part if one is not present.
98+
"""
99+
# HACK
100+
# one styles to rule them all
101+
document = self.package.main_document_part
102+
try:
103+
return document.part_related_by(RT.STYLES)
104+
except KeyError:
105+
styles_part = StylesPart.default(self.package)
106+
document.relate_to(styles_part, RT.STYLES)
107+
return styles_part
108+
109+
110+
class FooterPart(HeaderPart):
111+
# identical to HeaderPart for now
112+
pass

0 commit comments

Comments
 (0)