Skip to content

Commit 445d5d2

Browse files
author
Steve Canny
committed
sect: add Section margin getters
1 parent 4e56ce0 commit 445d5d2

File tree

7 files changed

+253
-4
lines changed

7 files changed

+253
-4
lines changed

docx/oxml/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
107107
register_element_cls('w:style', CT_Style)
108108
register_element_cls('w:styles', CT_Styles)
109109

110-
from docx.oxml.section import CT_PageSz, CT_SectPr, CT_SectType
110+
from docx.oxml.section import CT_PageMar, CT_PageSz, CT_SectPr, CT_SectType
111+
register_element_cls('w:pgMar', CT_PageMar)
111112
register_element_cls('w:pgSz', CT_PageSz)
112113
register_element_cls('w:sectPr', CT_SectPr)
113114
register_element_cls('w:type', CT_SectType)

docx/oxml/section.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,23 @@
55
"""
66

77
from ..enum.section import WD_ORIENTATION, WD_SECTION_START
8-
from .simpletypes import ST_TwipsMeasure
8+
from .simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure
99
from .xmlchemy import BaseOxmlElement, OptionalAttribute, ZeroOrOne
1010

1111

12+
class CT_PageMar(BaseOxmlElement):
13+
"""
14+
``<w:pgMar>`` element, defining page margins.
15+
"""
16+
top = OptionalAttribute('w:top', ST_SignedTwipsMeasure)
17+
right = OptionalAttribute('w:right', ST_TwipsMeasure)
18+
bottom = OptionalAttribute('w:bottom', ST_SignedTwipsMeasure)
19+
left = OptionalAttribute('w:left', ST_TwipsMeasure)
20+
header = OptionalAttribute('w:header', ST_TwipsMeasure)
21+
footer = OptionalAttribute('w:footer', ST_TwipsMeasure)
22+
gutter = OptionalAttribute('w:gutter', ST_TwipsMeasure)
23+
24+
1225
class CT_PageSz(BaseOxmlElement):
1326
"""
1427
``<w:pgSz>`` element, defining page dimensions and orientation.
@@ -37,6 +50,81 @@ class CT_SectPr(BaseOxmlElement):
3750
pgSz = ZeroOrOne('w:pgSz', successors=(
3851
__child_sequence__[__child_sequence__.index('w:pgSz')+1:]
3952
))
53+
pgMar = ZeroOrOne('w:pgMar', successors=(
54+
__child_sequence__[__child_sequence__.index('w:pgMar')+1:]
55+
))
56+
57+
@property
58+
def bottom_margin(self):
59+
"""
60+
The value of the ``w:bottom`` attribute in the ``<w:pgMar>`` child
61+
element, as a |Length| object, or |None| if either the element or the
62+
attribute is not present.
63+
"""
64+
pgMar = self.pgMar
65+
if pgMar is None:
66+
return None
67+
return pgMar.bottom
68+
69+
@property
70+
def footer(self):
71+
"""
72+
The value of the ``w:footer`` attribute in the ``<w:pgMar>`` child
73+
element, as a |Length| object, or |None| if either the element or the
74+
attribute is not present.
75+
"""
76+
pgMar = self.pgMar
77+
if pgMar is None:
78+
return None
79+
return pgMar.footer
80+
81+
@property
82+
def gutter(self):
83+
"""
84+
The value of the ``w:gutter`` attribute in the ``<w:pgMar>`` child
85+
element, as a |Length| object, or |None| if either the element or the
86+
attribute is not present.
87+
"""
88+
pgMar = self.pgMar
89+
if pgMar is None:
90+
return None
91+
return pgMar.gutter
92+
93+
@property
94+
def header(self):
95+
"""
96+
The value of the ``w:header`` attribute in the ``<w:pgMar>`` child
97+
element, as a |Length| object, or |None| if either the element or the
98+
attribute is not present.
99+
"""
100+
pgMar = self.pgMar
101+
if pgMar is None:
102+
return None
103+
return pgMar.header
104+
105+
@property
106+
def left_margin(self):
107+
"""
108+
The value of the ``w:left`` attribute in the ``<w:pgMar>`` child
109+
element, as a |Length| object, or |None| if either the element or the
110+
attribute is not present.
111+
"""
112+
pgMar = self.pgMar
113+
if pgMar is None:
114+
return None
115+
return pgMar.left
116+
117+
@property
118+
def right_margin(self):
119+
"""
120+
The value of the ``w:right`` attribute in the ``<w:pgMar>`` child
121+
element, as a |Length| object, or |None| if either the element or the
122+
attribute is not present.
123+
"""
124+
pgMar = self.pgMar
125+
if pgMar is None:
126+
return None
127+
return pgMar.right
40128

41129
@property
42130
def orientation(self):
@@ -107,6 +195,18 @@ def start_type(self, value):
107195
type = self.get_or_add_type()
108196
type.val = value
109197

198+
@property
199+
def top_margin(self):
200+
"""
201+
The value of the ``w:top`` attribute in the ``<w:pgMar>`` child
202+
element, as a |Length| object, or |None| if either the element or the
203+
attribute is not present.
204+
"""
205+
pgMar = self.pgMar
206+
if pgMar is None:
207+
return None
208+
return pgMar.top
209+
110210

111211
class CT_SectType(BaseOxmlElement):
112212
"""

docx/oxml/simpletypes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,21 @@ class ST_RelationshipId(XsdString):
234234
pass
235235

236236

237+
class ST_SignedTwipsMeasure(XsdInt):
238+
239+
@classmethod
240+
def convert_from_xml(cls, str_value):
241+
if 'i' in str_value or 'm' in str_value or 'p' in str_value:
242+
return ST_UniversalMeasure.convert_from_xml(str_value)
243+
return Twips(int(str_value))
244+
245+
@classmethod
246+
def convert_to_xml(cls, value):
247+
emu = Emu(value)
248+
twips = emu.twips
249+
return str(twips)
250+
251+
237252
class ST_String(XsdString):
238253
pass
239254

docx/section.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,50 @@ def __init__(self, sectPr):
1515
super(Section, self).__init__()
1616
self._sectPr = sectPr
1717

18+
@property
19+
def bottom_margin(self):
20+
"""
21+
|Length| object representing the bottom margin for all pages in this
22+
section in English Metric Units.
23+
"""
24+
return self._sectPr.bottom_margin
25+
26+
@property
27+
def footer_distance(self):
28+
"""
29+
|Length| object representing the distance from the bottom edge of the
30+
page to the bottom edge of the footer. |None| if no setting is present
31+
in the XML.
32+
"""
33+
return self._sectPr.footer
34+
35+
@property
36+
def gutter(self):
37+
"""
38+
|Length| object representing the page gutter size in English Metric
39+
Units for all pages in this section. The page gutter is extra spacing
40+
added to the *inner* margin to ensure even margins after page
41+
binding.
42+
"""
43+
return self._sectPr.gutter
44+
45+
@property
46+
def header_distance(self):
47+
"""
48+
|Length| object representing the distance from the top edge of the
49+
page to the top edge of the header. |None| if no setting is present
50+
in the XML.
51+
"""
52+
return self._sectPr.header
53+
54+
@property
55+
def left_margin(self):
56+
"""
57+
|Length| object representing the left margin for all pages in this
58+
section in English Metric Units.
59+
"""
60+
return self._sectPr.left_margin
61+
1862
@property
1963
def orientation(self):
2064
"""
@@ -55,6 +99,14 @@ def page_width(self):
5599
def page_width(self, value):
56100
self._sectPr.page_width = value
57101

102+
@property
103+
def right_margin(self):
104+
"""
105+
|Length| object representing the right margin for all pages in this
106+
section in English Metric Units.
107+
"""
108+
return self._sectPr.right_margin
109+
58110
@property
59111
def start_type(self):
60112
"""
@@ -67,3 +119,11 @@ def start_type(self):
67119
@start_type.setter
68120
def start_type(self, value):
69121
self._sectPr.start_type = value
122+
123+
@property
124+
def top_margin(self):
125+
"""
126+
|Length| object representing the top margin for all pages in this
127+
section in English Metric Units.
128+
"""
129+
return self._sectPr.top_margin

features/sct-section-props.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ Feature: Access and change section properties
6565
| landscape | None | WD_ORIENT.PORTRAIT |
6666

6767

68-
@wip
6968
Scenario: Get section page margins
7069
Given a section having known page margins
7170
Then the reported left margin is 1.0 inches

tests/oxml/unitdata/section.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
from ...unitdata import BaseBuilder
88

99

10+
class CT_PageMarBuilder(BaseBuilder):
11+
__tag__ = 'w:pgMar'
12+
__nspfxs__ = ('w',)
13+
__attrs__ = (
14+
'w:top', 'w:right', 'w:bottom', 'w:left', 'w:header', 'w:footer',
15+
'w:gutter'
16+
)
17+
18+
1019
class CT_PageSzBuilder(BaseBuilder):
1120
__tag__ = 'w:pgSz'
1221
__nspfxs__ = ('w',)
@@ -25,6 +34,10 @@ class CT_SectTypeBuilder(BaseBuilder):
2534
__attrs__ = ('w:val',)
2635

2736

37+
def a_pgMar():
38+
return CT_PageMarBuilder()
39+
40+
2841
def a_pgSz():
2942
return CT_PageSzBuilder()
3043

tests/test_section.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from docx.section import Section
1313
from docx.shared import Inches
1414

15-
from .oxml.unitdata.section import a_pgSz, a_sectPr, a_type
15+
from .oxml.unitdata.section import a_pgMar, a_pgSz, a_sectPr, a_type
1616

1717

1818
class DescribeSection(object):
@@ -52,8 +52,47 @@ def it_can_change_its_orientation(self, orientation_set_fixture):
5252
section.orientation = new_orientation
5353
assert section._sectPr.xml == expected_xml
5454

55+
def it_knows_its_page_margins(self, margins_get_fixture):
56+
section, left, right, top, bottom, gutter, header, footer = (
57+
margins_get_fixture
58+
)
59+
assert section.left_margin == left
60+
assert section.right_margin == right
61+
assert section.top_margin == top
62+
assert section.bottom_margin == bottom
63+
assert section.gutter == gutter
64+
assert section.header_distance == header
65+
assert section.footer_distance == footer
66+
5567
# fixtures -------------------------------------------------------
5668

69+
@pytest.fixture(params=[
70+
(True, 720, 720, 720, 720, 720, 720, 720),
71+
(True, None, 360, None, 360, None, 360, None),
72+
(False, None, None, None, None, None, None, None),
73+
])
74+
def margins_get_fixture(self, request):
75+
(has_pgMar_child, left, right, top, bottom, gutter, header,
76+
footer) = request.param
77+
pgMar_bldr = self.pgMar_bldr(
78+
has_pgMar_child, left=left, right=right, top=top, bottom=bottom,
79+
gutter=gutter, header=header, footer=footer
80+
)
81+
sectPr = self.sectPr_bldr(pgMar_bldr).element
82+
section = Section(sectPr)
83+
expected_left = left * 635 if left else None
84+
expected_right = right * 635 if right else None
85+
expected_top = top * 635 if top else None
86+
expected_bottom = bottom * 635 if bottom else None
87+
expected_gutter = gutter * 635 if gutter else None
88+
expected_header = header * 635 if header else None
89+
expected_footer = footer * 635 if footer else None
90+
return (
91+
section, expected_left, expected_right, expected_top,
92+
expected_bottom, expected_gutter, expected_header,
93+
expected_footer
94+
)
95+
5796
@pytest.fixture(params=[
5897
(True, 'landscape', WD_ORIENT.LANDSCAPE),
5998
(True, 'portrait', WD_ORIENT.PORTRAIT),
@@ -172,6 +211,28 @@ def start_type_set_fixture(self, request):
172211

173212
# fixture components ---------------------------------------------
174213

214+
def pgMar_bldr(
215+
self, has_pgMar=True, left=None, right=None, top=None,
216+
bottom=None, header=None, footer=None, gutter=None):
217+
if not has_pgMar:
218+
return None
219+
pgMar_bldr = a_pgMar()
220+
if left is not None:
221+
pgMar_bldr.with_left(left)
222+
if right is not None:
223+
pgMar_bldr.with_right(right)
224+
if top is not None:
225+
pgMar_bldr.with_top(top)
226+
if bottom is not None:
227+
pgMar_bldr.with_bottom(bottom)
228+
if header is not None:
229+
pgMar_bldr.with_header(header)
230+
if footer is not None:
231+
pgMar_bldr.with_footer(footer)
232+
if gutter is not None:
233+
pgMar_bldr.with_gutter(gutter)
234+
return pgMar_bldr
235+
175236
def pgSz_bldr(self, has_pgSz=True, w=None, h=None, orient=None):
176237
if not has_pgSz:
177238
return None

0 commit comments

Comments
 (0)