10000 section: Section.iter_inner_content() · python-openxml/python-docx@9c9106f · GitHub
[go: up one dir, main page]

Skip to content

Commit 9c9106f

Browse files
committed
section: Section.iter_inner_content()
* clean up section (more needs doing in feature/steps and tests) * acceptance test * unit test
1 parent 12a6bb8 commit 9c9106f

File tree

11 files changed

+295
-48
lines changed

11 files changed

+295
-48
lines changed

features/sct-section.feature

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ Feature: Access and change section properties
5757
Then section.header is a _Header object
5858

5959

60+
Scenario: Section.iter_inner_content()
61+
Given a Section object of a multi-section document as section
62+
Then section.iter_inner_content() produces the paragraphs and tables in section
63+
64+
6065
Scenario Outline: Get section start type
6166
Given a section having start type <start-type>
6267
Then the reported section start type is <start-type>

features/steps/section.py

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Step implementations for section-related features."""
22

33
from behave import given, then, when
4+
from behave.runner import Context
45

56
from docx import Document
67
from docx.enum.section import WD_ORIENT, WD_SECTION
@@ -13,36 +14,43 @@
1314

1415

1516
@given("a Section object as section")
16-
def given_a_Section_object_as_section(context):
17+
def given_a_Section_object_as_section(context: Context):
1718
context.section = Document(test_docx("sct-section-props")).sections[-1]
1819

1920

21+
@given("a Section object of a multi-section document as section")
22+
def given_a_Section_object_of_a_multi_section_document_as_section(context: Context):
23+
context.section = Document(test_docx("sct-inner-content")).sections[1]
24+
25+
2026
@given("a Section object {with_or_without} a distinct first-page header as section")
21-
def given_a_Section_object_with_or_without_first_page_header(context, with_or_without):
27+
def given_a_Section_object_with_or_without_first_page_header(
28+
context: Context, with_or_without: str
29+
):
2230
section_idx = {"with": 1, "without": 0}[with_or_without]
2331
context.section = Document(test_docx("sct-first-page-hdrftr")).sections[section_idx]
2432

2533

2634
@given("a section collection containing 3 sections")
27-
def given_a_section_collection_containing_3_sections(context):
35+
def given_a_section_collection_containing_3_sections(context: Context):
2836
document = Document(test_docx("doc-access-sections"))
2937
context.sections = document.sections
3038

3139

3240
@given("a section having known page dimension")
33-
def given_a_section_having_known_page_dimension(context):
41+
def given_a_section_having_known_page_dimension(context: Context):
3442
document = Document(test_docx("sct-section-props"))
3543
context.section = document.sections[-1]
3644

3745

3846
@given("a section having known page margins")
39-
def given_a_section_having_known_page_margins(context):
47+
def given_a_section_having_known_page_margins(context: Context):
4048
document = Document(test_docx("sct-section-props"))
4149
context.section = document.sections[0]
4250

4351

4452
@given("a section having start type {start_type}")
45-
def given_a_section_having_start_type(context, start_type):
53+
def given_a_section_having_start_type(context: Context, start_type: str):
4654
section_idx = {
4755
"CONTINUOUS": 0,
4856
"NEW_PAGE": 1,
@@ -55,7 +63,7 @@ def given_a_section_having_start_type(context, start_type):
5563

5664

5765
@given("a section known to have {orientation} orientation")
58-
def given_a_section_having_known_orientation(context, orientation):
66+
def given_a_section_having_known_orientation(context: Context, orientation: str):
5967
section_idx = {"landscape": 0, "portrait": 1}[orientation]
6068
document = Document(test_docx("sct-section-props"))
6169
context.section = document.sections[section_idx]
@@ -65,12 +73,14 @@ def given_a_section_having_known_orientation(context, orientation):
6573

6674

6775
@when("I assign {bool_val} to section.different_first_page_header_footer")
68-
def when_I_assign_value_to_section_different_first_page_hdrftr(context, bool_val):
76+
def when_I_assign_value_to_section_different_first_page_hdrftr(
77+
context: Context, bool_val: str
78+
):
6979
context.section.different_first_page_header_footer = eval(bool_val)
7080

7181

7282
@when("I set the {margin_side} margin to {inches} inches")
73-
def when_I_set_the_margin_side_length(context, margin_side, inches):
83+
def when_I_set_the_margin_side_length(context: Context, margin_side: str, inches: str):
7484
prop_name = {
7585
"left": "left_margin",
7686
"right": "right_margin",
@@ -85,7 +95,7 @@ def when_I_set_the_margin_side_length(context, margin_side, inches):
8595

8696

8797
@when("I set the section orientation to {orientation}")
88-
10000 def when_I_set_the_section_orientation(context, orientation):
98+
def when_I_set_the_section_orientation(context: Context, orientation: str):
8999
new_orientation = {
90100
"WD_ORIENT.PORTRAIT": WD_ORIENT.PORTRAIT,
91101
"WD_ORIENT.LANDSCAPE": WD_ORIENT.LANDSCAPE,
@@ -95,17 +105,17 @@ def when_I_set_the_section_orientation(context, orientation):
95105

96106

97107
@when("I set the section page height to {y} inches")
98-
def when_I_set_the_section_page_height_to_y_inches(context, y):
108+
def when_I_set_the_section_page_height_to_y_inches(context: Context, y: str):
99109
context.section.page_height = Inches(float(y))
100110

101111

102112
@when("I set the section page width to {x} inches")
103-
def when_I_set_the_section_page_width_to_x_inches(context, x):
113+
def when_I_set_the_section_page_width_to_x_inches(context: Context, x: str):
104114
context.section.page_width = Inches(float(x))
105115

106116

107117
@when("I set the section start type to {start_type}")
108-
def when_I_set_the_section_start_type_to_start_type(context, start_type):
118+
def when_I_set_the_section_start_type_to_start_type(context: Context, start_type: str):
109119
new_start_type = {
110120
"None": None,
111121
"CONTINUOUS": WD_SECTION.CONTINUOUS,
@@ -121,15 +131,15 @@ def when_I_set_the_section_start_type_to_start_type(context, start_type):
121131

122132

123133
@then("I can access a section by index")
124-
def then_I_can_access_a_section_by_index(context):
134+
def then_I_can_access_a_section_by_index(context: Context):
125135
sections = context.sections
126136
for idx in range(3):
127137
section = sections[idx]
128138
assert isinstance(section, Section)
129139

130140

131141
@then("I can iterate over the sections")
132-
def then_I_can_iterate_over_the_sections(context):
142+
def then_I_can_iterate_over_the_sections(context: Context):
133143
sections = context.sections
134144
actual_count = 0
135145
for section in sections:
@@ -139,13 +149,13 @@ def then_I_can_iterate_over_the_sections(context):
139149

140150

141151
@then("len(sections) is 3")
142-
def then_len_sections_is_3(context):
152+
def then_len_sections_is_3(context: Context):
143153
sections = context.sections
144154
assert len(sections) == 3, "expected len(sections) of 3, got %s" % len(sections)
145155

146156

147157
@then("section.different_first_page_header_footer is {bool_val}")
148-
def then_section_different_first_page_header_footer_is(context, bool_val):
158+
def then_section_different_first_page_header_footer_is(context: Context, bool_val: str):
149159
actual = context.section.different_first_page_header_footer
150160
expected = eval(bool_val)
151161
assert actual == expected, (
@@ -154,49 +164,58 @@ def then_section_different_first_page_header_footer_is(context, bool_val):
154164

155165

156166
@then("section.even_page_footer is a _Footer object")
157-
def then_section_even_page_footer_is_a_Footer_object(context):
167+
def then_section_even_page_footer_is_a_Footer_object(context: Context):
158168
actual = type(context.section.even_page_footer).__name__
159169
expected = "_Footer"
160170
assert actual == expected, "section.even_page_footer is a %s object" % actual
161171

162172

163173
@then("section.even_page_header is a _Header object")
164-
def then_section_even_page_header_is_a_Header_object(context):
174+
def then_section_even_page_header_is_a_Header_object(context: Context):
165175
actual = type(context.section.even_page_header).__name__
166176
expected = "_Header"
167177
assert actual == expected, "section.even_page_header is a %s object" % actual
168178

169179

170180
@then("section.first_page_footer is a _Footer object")
171-
def then_section_first_page_footer_is_a_Footer_object(context):
181+
def then_section_first_page_footer_is_a_Footer_object(context: Context):
172182
actual = type(context.section.first_page_footer).__name__
173183
expected = "_Footer"
174184
assert actual == expected, "section.first_page_footer is a %s object" % actual
175185

176186

177187
@then("section.first_page_header is a _Header object")
178-
def then_section_first_page_header_is_a_Header_object(context):
188+
def then_section_first_page_header_is_a_Header_object(context: Context):
179189
actual = type(context.section.first_page_header).__name__
180190
expected = "_Header"
181191
assert actual == expected, "section.first_page_header is a %s object" % actual
182192

183193

184194
@then("section.footer is a _Footer object")
185-
def then_section_footer_is_a_Footer_object(context):
195+
def then_section_footer_is_a_Footer_object(context: Context):
186196
actual = type(context.section.footer).__name__
187197
expected = "_Footer"
188198
assert actual == expected, "section.footer is a %s object" % actual
189199

190200

191201
@then("section.header is a _Header object")
192-
def then_section_header_is_a_Header_object(context):
202+
def then_section_header_is_a_Header_object(context: Context):
193203
actual = type(context.section.header).__name__
194204
expected = "_Header"
195205
assert actual == expected, "section.header is a %s object" % actual
196206

197207

208+
@then("section.iter_inner_content() produces the paragraphs and tables in section")
209+
def step_impl(context: Context):
210+
actual = [type(item).__name__ for item in context.section.iter_inner_content()]
211+
expected = ["Table", "Paragraph", "Paragraph"]
212+
assert actual == expected, f"expected: {expected}, got: {actual}"
213+
214+
198215
@then("section.{propname}.is_linked_to_previous is True")
199-
def then_section_hdrftr_prop_is_linked_to_previous_is_True(context, propname):
216+
def then_section_hdrftr_prop_is_linked_to_previous_is_True(
217+
context: Context, propname: str
218+
):
200219
actual = getattr(context.section, propname).is_linked_to_previous
201220
expected = True
202221
assert actual == expected, "section.%s.is_linked_to_previous is %s" % (
@@ -206,7 +225,7 @@ def then_section_hdrftr_prop_is_linked_to_previous_is_True(context, propname):
206225

207226

208227
@then("the reported {margin_side} margin is {inches} inches")
209-
def then_the_reported_margin_is_inches(context, margin_side, inches):
228+
def then_the_reported_margin_is_inches(context: Context, margin_side: str, inches: str):
210229
prop_name = {
211230
"left": "left_margin",
212231
"right": "right_margin",
@@ -222,7 +241,9 @@ def then_the_reported_margin_is_inches(context, margin_side, inches):
222241

223242

224243
@then("the reported page orientation is {orientation}")
225-
def then_the_reported_page_orientation_is_orientation(context, orientation):
244+
def then_the_reported_page_orientation_is_orientation(
245+
context: Context, orientation: str
246+
):
226247
expected_value = {
227248
"WD_ORIENT.LANDSCAPE": WD_ORIENT.LANDSCAPE,
228249
"WD_ORIENT.PORTRAIT": WD_ORIENT.PORTRAIT,
@@ -231,17 +252,17 @@ def then_the_reported_page_orientation_is_orientation(context, orientation):
231252

232253

233254
@then("the reported page width is {x} inches")
234-
def then_the_reported_page_width_is_width(context, x):
255+
def then_the_reported_page_width_is_width(context: Context, x: str):
235256
assert context.section.page_width == Inches(float(x))
236257

237258

238259
@then("the reported page height is {y} inches")
239-
def then_the_reported_page_height_is_11_inches(context, y):
260+
def then_the_reported_page_height_is_11_inches(context: Context, y: str):
240261
assert context.section.page_height == Inches(float(y))
241262

242263

243264
@then("the reported section start type is {start_type}")
244-
def then_the_reported_section_start_type_is_type(context, start_type):
265+
def then_the_reported_section_start_type_is_type(context: Context, start_type: str):
245266
expected_start_type = {
246267
"CONTINUOUS": WD_SECTION.CONTINUOUS,
247268
"EVEN_PAGE": WD_SECTION.EVEN_PAGE,
11.8 KB
Binary file not shown.

pyrightconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
"reportUnnecessaryTypeIgnoreComment": true,
1717
"stubPath": "./typings",
1818
"typeCheckingMode": "strict",
19-
"useLibraryCodeForTypes": false,
19+
"useLibraryCodeForTypes": true,
2020
"verboseOutput": true
2121
}

src/docx/oxml/document.py

Lines changed: 17 additions & 4 deletions
6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
"""Custom element classes that correspond to the document part, e.g. <w:document>."""
22

3+
from __future__ import annotations
4+
5+
from typing import List
+
7+
from docx.oxml.section import CT_SectPr
38
from docx.oxml.xmlchemy import BaseOxmlElement, ZeroOrMore, ZeroOrOne
49

510

@@ -9,10 +14,18 @@ class CT_Document(BaseOxmlElement):
914
body = ZeroOrOne("w:body")
1015

1116
@property
12-
def sectPr_lst(self):
13-
"""Return a list containing a reference to each ``<w:sectPr>`` element in the
14-
document, in the order encountered."""
15-
return self.xpath(".//w:sectPr")
17+
def sectPr_lst(self) -> List[CT_SectPr]:
18+
"""All `w:sectPr` elements directly accessible from document element.
19+
20+
Note this does not include a `sectPr` child in a paragraphs wrapped in
21+
revision marks or other intervening layer, perhaps `w:sdt` or customXml
22+
elements.
23+
24+
`w:sectPr` elements appear in document order. The last one is always
25+
`w:body/w:sectPr`, all preceding are `w:p/w:pPr/w:sectPr`.
26+
"""
27+
xpath = "./w:body/w:p/w:pPr/w:sectPr | ./w:body/w:sectPr"
28+
return self.xpath(xpath)
1629

1730

1831
class CT_Body(BaseOxmlElement):

0 commit comments

Comments
 (0)
0