Skip to content

Commit 1e1e814

Browse files
tiberlas=
authored and
=
committedMay 13, 2024
rfctr: Blacken footnote support
1 parent f46762c commit 1e1e814

15 files changed

+304
-203
lines changed
 

‎features/steps/footnotes.py

+46-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Step implementations for footnote-related features."""
22

3-
from behave import given, when, then
3+
from behave import given, then, when
44
from behave.runner import Context
55

66
from docx import Document
@@ -44,7 +44,9 @@ def given_a_paragraph_in_a_document_without_footnotes(context: Context):
4444
context.footnotes = document.footnotes
4545

4646

47-
@given("a document with paragraphs[0] containing one, paragraphs[1] containing none, and paragraphs[2] containing two footnotes")
47+
@given(
48+
"a document with paragraphs[0] containing one, paragraphs[1] containing none, and paragraphs[2] containing two footnotes"
49+
)
4850
def given_a_document_with_3_footnotes(context: Context):
4951
document = Document(test_docx("footnotes"))
5052
context.paragraphs = document.paragraphs
@@ -64,14 +66,18 @@ def when_I_try_to_access_a_footnote_with_invalid_reference_id(context: Context):
6466

6567

6668
@when("I add a footnote to the paragraphs[{parId}] with text '{footnoteText}'")
67-
def when_I_add_a_footnote_to_the_paragraph_with_text_text(context: Context, parId: str, footnoteText: str):
69+
def when_I_add_a_footnote_to_the_paragraph_with_text_text(
70+
context: Context, parId: str, footnoteText: str
71+
):
6872
par = context.paragraphs[int(parId)]
6973
new_footnote = par.add_footnote()
7074
new_footnote.add_paragraph(footnoteText)
7175

7276

7377
@when("I change footnote property {propName} to {value}")
74-
def when_I_change_footnote_property_propName_to_value(context: Context, propName: str, value: str):
78+
def when_I_change_footnote_property_propName_to_value(
79+
context: Context, propName: str, value: str
80+
):
7581
context.section.__setattr__(propName, eval(value))
7682

7783

@@ -81,7 +87,9 @@ def when_I_change_footnote_property_propName_to_value(context: Context, propName
8187
@then("len(footnotes) is {expectedLen}")
8288
def then_len_footnotes_is_len(context: Context, expectedLen: str):
8389
footnotes = context.footnotes
84-
assert len(footnotes) == int(expectedLen), f"expected len(footnotes) of {expectedLen}, got {len(footnotes)}"
90+
assert len(footnotes) == int(
91+
expectedLen
92+
), f"expected len(footnotes) of {expectedLen}, got {len(footnotes)}"
8593

8694

8795
@then("I can access a footnote by footnote reference id")
@@ -107,35 +115,57 @@ def then_it_trows_an_IndexError(context: Context, exceptionType: str):
107115

108116

109117
@then("I can access footnote property {propName} with value {value}")
110-
def then_I_can_access_footnote_propery_name_with_value_value(context: Context, propName: str, value: str):
118+
def then_I_can_access_footnote_propery_name_with_value_value(
119+
context: Context, propName: str, value: str
120+
):
111121
actual_value = context.section.__getattribute__(propName)
112122
expected = eval(value)
113-
assert actual_value == expected, f"expected section.{propName} {value}, got {expected}"
123+
assert (
124+
actual_value == expected
125+
), f"expected section.{propName} {value}, got {expected}"
114126

115127

116-
@then("the document contains a footnote with footnote reference id of {refId} with text '{footnoteText}'")
117-
def then_the_document_contains_a_footnote_with_footnote_reference_id_of_refId_with_text_text(context: Context, refId: str, footnoteText: str):
128+
@then(
129+
"the document contains a footnote with footnote reference id of {refId} with text '{footnoteText}'"
130+
)
131+
def then_the_document_contains_a_footnote_with_footnote_reference_id_of_refId_with_text_text(
132+
context: Context, refId: str, footnoteText: str
133+
):
118134
par = context.paragraphs[1]
119135
f = par.footnotes[0]
120136
assert f.id == int(refId), f"expected {refId}, got {f.id}"
121-
assert f.paragraphs[0].text == footnoteText, f"expected {footnoteText}, got {f.paragraphs[0].text}"
137+
assert (
138+
f.paragraphs[0].text == footnoteText
139+
), f"expected {footnoteText}, got {f.paragraphs[0].text}"
122140

123141

124-
@then("paragraphs[{parId}] has footnote reference ids of {refIds}, with footnote text {fText}")
125-
def then_paragraph_has_footnote_reference_ids_of_refIds_with_footnote_text_text(context: Context, parId: str, refIds: str, fText: str):
142+
@then(
143+
"paragraphs[{parId}] has footnote reference ids of {refIds}, with footnote text {fText}"
144+
)
145+
def then_paragraph_has_footnote_reference_ids_of_refIds_with_footnote_text_text(
146+
context: Context, parId: str, refIds: str, fText: str
147+
):
126148
par = context.paragraphs[int(parId)]
127149
refIds = eval(refIds)
128150
fText = eval(fText)
129151
if refIds is not None:
130152
if type(refIds) is list:
131153
for i in range(len(refIds)):
132154
f = par.footnotes[i]
133-
assert isinstance(f, Footnote), f"expected to be instance of Footnote, got {type(f)}"
155+
assert isinstance(
156+
f, Footnote
157+
), f"expected to be instance of Footnote, got {type(f)}"
134158
assert f.id == refIds[i], f"expected {refIds[i]}, got {f.id}"
135-
assert f.paragraphs[0].text == fText[i], f"expected '{fText[i]}', got '{f.paragraphs[0].text}'"
159+
assert (
160+
f.paragraphs[0].text == fText[i]
161+
), f"expected '{fText[i]}', got '{f.paragraphs[0].text}'"
136162
else:
137163
f = par.footnotes[0]
138164
assert f.id == int(refIds), f"expected {refIds}, got {f.id}"
139-
assert f.paragraphs[0].text == fText, f"expected '{fText}', got '{f.paragraphs[0].text}'"
165+
assert (
166+
f.paragraphs[0].text == fText
167+
), f"expected '{fText}', got '{f.paragraphs[0].text}'"
140168
else:
141-
assert len(par.footnotes) == 0, f"expected an empty list, got {len(par.footnotes)} elements"
169+
assert (
170+
len(par.footnotes) == 0
171+
), f"expected an empty list, got {len(par.footnotes)} elements"

‎src/docx/document.py

+6-8
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
if TYPE_CHECKING:
1717
import docx.types as t
1818
from docx.oxml.document import CT_Body, CT_Document
19+
from docx.oxml.footnote import CT_Footnotes, CT_FtnEnd
20+
from docx.oxml.text.paragraph import CT_P
1921
from docx.parts.document import DocumentPart
2022
from docx.settings import Settings
2123
from docx.shared import Length
2224
from docx.styles.style import ParagraphStyle, _TableStyle
2325
from docx.table import Table
2426
from docx.text.paragraph import Paragraph
25-
from docx.oxml.footnote import CT_Footnotes, CT_FtnEnd
26-
from docx.oxml.text.paragraph import CT_P
2727

2828

2929
class Document(ElementProxy):
@@ -199,11 +199,9 @@ def _body(self) -> _Body:
199199
return self.__body
200200

201201
def _calculate_next_footnote_reference_id(self, p: CT_P) -> int:
202-
"""
203-
Return the appropriate footnote reference id number for
204-
a new footnote added at the end of paragraph `p`.
205-
"""
206-
# When adding a footnote it can be inserted
202+
"""Return the appropriate footnote reference id number for
203+
a new footnote added at the end of paragraph `p`."""
204+
# When adding a footnote it can be inserted
207205
# in front of some other footnotes, so
208206
# we need to sort footnotes by `footnote_reference_id`
209207
# in |Footnotes| and in |Paragraph|
@@ -234,7 +232,7 @@ def _calculate_next_footnote_reference_id(self, p: CT_P) -> int:
234232
else:
235233
# This is the last footnote before the new footnote, so we use its
236234
# value to determent the value of the new footnote.
237-
new_fr_id = max(self.paragraphs[p_i]._p.footnote_reference_ids)+1
235+
new_fr_id = max(self.paragraphs[p_i]._p.footnote_reference_ids) + 1
238236
break
239237
return new_fr_id
240238

‎src/docx/footnotes.py

+15-19
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,18 @@
99

1010
if TYPE_CHECKING:
1111
from docx import types as t
12-
from docx.oxml.footnote import CT_FtnEnd, CT_Footnotes
12+
from docx.oxml.footnote import CT_Footnotes, CT_FtnEnd
13+
1314

1415
class Footnotes(Parented):
15-
"""
16-
Proxy object wrapping ``<w:footnotes>`` element.
17-
"""
16+
"""Proxy object wrapping ``<w:footnotes>`` element."""
17+
1818
def __init__(self, footnotes: CT_Footnotes, parent: t.ProvidesStoryPart):
1919
super(Footnotes, self).__init__(parent)
2020
self._element = self._footnotes = footnotes
2121

2222
def __getitem__(self, reference_id: int) -> Footnote:
23-
"""
24-
A |Footnote| for a specific footnote of reference id, defined with ``w:id`` argument of ``<w:footnoteReference>``.
25-
If reference id is invalid raises an |IndexError|
26-
"""
23+
"""A |Footnote| for a specific footnote of reference id, defined with ``w:id`` argument of ``<w:footnoteReference>``. If reference id is invalid raises an |IndexError|"""
2724
footnote = self._element.get_by_id(reference_id)
2825
if footnote is None:
2926
raise IndexError
@@ -33,15 +30,13 @@ def __len__(self) -> int:
3330
return len(self._element)
3431

3532
def add_footnote(self, footnote_reference_id: int) -> Footnote:
36-
"""
37-
Return a newly created |Footnote|, the new footnote will
33+
"""Return a newly created |Footnote|, the new footnote will
3834
be inserted in the correct spot by `footnote_reference_id`.
39-
The footnotes are kept in order by `footnote_reference_id`.
40-
"""
41-
elements = self._element # for easy access
35+
The footnotes are kept in order by `footnote_reference_id`."""
36+
elements = self._element # for easy access
4237
new_footnote = None
4338
if elements.get_by_id(footnote_reference_id) is not None:
44-
# When adding a footnote it can be inserted
39+
# When adding a footnote it can be inserted
4540
# in front of some other footnotes, so
4641
# we need to sort footnotes by `footnote_reference_id`
4742
# in |Footnotes| and in |Paragraph|
@@ -54,7 +49,9 @@ def add_footnote(self, footnote_reference_id: int) -> Footnote:
5449
for index in reversed(range(len(elements))):
5550
if elements[index].id == footnote_reference_id:
5651
elements[index].id += 1
57-
new_footnote = elements[index].add_footnote_before(footnote_reference_id)
52+
new_footnote = elements[index].add_footnote_before(
53+
footnote_reference_id
54+
)
5855
break
5956
else:
6057
elements[index].id += 1
@@ -65,10 +62,9 @@ def add_footnote(self, footnote_reference_id: int) -> Footnote:
6562

6663

6764
class Footnote(BlockItemContainer):
68-
"""
69-
Proxy object wrapping ``<w:footnote>`` element.
70-
"""
71-
def __init__(self, f: CT_FtnEnd, parent: t.ProvidesStoryPart):
65+
"""Proxy object wrapping ``<w:footnote>`` element."""
66+
67+
def __init__(self, f: CT_FtnEnd, parent: t.ProvidesStoryPart):
7268
super(Footnote, self).__init__(f, parent)
7369
self._f = self._element = f
7470

‎src/docx/oxml/__init__.py

+10-12
Original file line numberDiff line numberDiff line change
@@ -117,17 +117,17 @@
117117
CT_SectType,
118118
)
119119

120-
register_element_cls('w:footnotePr', CT_FtnProps)
120+
register_element_cls("w:footnotePr", CT_FtnProps)
121121
register_element_cls("w:footerReference", CT_HdrFtrRef)
122122
register_element_cls("w:ftr", CT_HdrFtr)
123123
register_element_cls("w:hdr", CT_HdrFtr)
124124
register_element_cls("w:headerReference", CT_HdrFtrRef)
125-
register_element_cls('w:numFmt', CT_NumFmt)
126-
register_element_cls('w:numStart', CT_DecimalNumber)
127-
register_element_cls('w:numRestart', CT_NumRestart)
125+
register_element_cls("w:numFmt", CT_NumFmt)
126+
register_element_cls("w:numStart", CT_DecimalNumber)
127+
register_element_cls("w:numRestart", CT_NumRestart)
128128
register_element_cls("w:pgMar", CT_PageMar)
129129
register_element_cls("w:pgSz", CT_PageSz)
130-
register_element_cls('w:pos', CT_FtnPos)
130+
register_element_cls("w:pos", CT_FtnPos)
131131
register_element_cls("w:sectPr", CT_SectPr)
132132
register_element_cls("w:type", CT_SectType)
133133

@@ -254,11 +254,9 @@
254254
# ---------------------------------------------------------------------------
255255
# footnote-related mappings
256256

257-
from .footnote import (
258-
CT_FtnEnd,
259-
CT_Footnotes
260-
)
257+
from .footnote import CT_Footnotes, CT_FtnEnd
261258
from .text.footnote_reference import CT_FtnEdnRef
262-
register_element_cls('w:footnoteReference', CT_FtnEdnRef)
263-
register_element_cls('w:footnote', CT_FtnEnd)
264-
register_element_cls('w:footnotes', CT_Footnotes)
259+
260+
register_element_cls("w:footnoteReference", CT_FtnEdnRef)
261+
register_element_cls("w:footnote", CT_FtnEnd)
262+
register_element_cls("w:footnotes", CT_Footnotes)

‎src/docx/oxml/footnote.py

+14-24
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,23 @@
66

77
from docx.oxml.ns import qn
88
from docx.oxml.parser import OxmlElement
9-
from docx.oxml.xmlchemy import (
10-
BaseOxmlElement, RequiredAttribute, ZeroOrMore, OneOrMore
11-
)
12-
from docx.oxml.simpletypes import (
13-
ST_DecimalNumber
14-
)
9+
from docx.oxml.simpletypes import ST_DecimalNumber
10+
from docx.oxml.xmlchemy import BaseOxmlElement, OneOrMore, RequiredAttribute, ZeroOrMore
1511

1612
if TYPE_CHECKING:
1713
from docx.oxml.text.paragraph import CT_P
1814

1915

2016
class CT_FtnEnd(BaseOxmlElement):
21-
"""
22-
``<w:footnote>`` element, containing the properties for a specific footnote
23-
"""
24-
id = RequiredAttribute('w:id', ST_DecimalNumber)
25-
p = ZeroOrMore('w:p')
17+
"""``<w:footnote>`` element, containing the properties for a specific footnote"""
18+
19+
id = RequiredAttribute("w:id", ST_DecimalNumber)
20+
p = ZeroOrMore("w:p")
2621

2722
def add_footnote_before(self, footnote_reference_id: int) -> CT_FtnEnd:
28-
"""
29-
Create a ``<w:footnote>`` element with `footnote_reference_id`
30-
and insert it before the current element.
31-
"""
32-
new_footnote = OxmlElement('w:footnote')
23+
"""Create a ``<w:footnote>`` element with `footnote_reference_id`
24+
and insert it before the current element."""
25+
new_footnote = OxmlElement("w:footnote")
3326
new_footnote.id = footnote_reference_id
3427
self.addprevious(new_footnote)
3528
return new_footnote
@@ -40,23 +33,20 @@ def paragraphs(self) -> List[CT_P]:
4033

4134
paragraphs = []
4235
for child in self:
43-
if child.tag == qn('w:p'):
36+
if child.tag == qn("w:p"):
4437
paragraphs.append(child)
4538
return paragraphs
4639

4740

4841
class CT_Footnotes(BaseOxmlElement):
49-
"""
50-
``<w:footnotes>`` element, containing a sequence of footnote (w:footnote) elements
51-
"""
42+
"""``<w:footnotes>`` element, containing a sequence of footnote (w:footnote) elements"""
43+
5244
add_footnote_sequence: Callable[[], CT_FtnEnd]
5345

54-
footnote_sequence = OneOrMore('w:footnote')
46+
footnote_sequence = OneOrMore("w:footnote")
5547

5648
def add_footnote(self, footnote_reference_id: int) -> CT_FtnEnd:
57-
"""
58-
Create a ``<w:footnote>`` element with `footnote_reference_id`.
59-
"""
49+
"""Create a ``<w:footnote>`` element with `footnote_reference_id`."""
6050
new_f = self.add_footnote_sequence()
6151
new_f.id = footnote_reference_id
6252
return new_f

0 commit comments

Comments
 (0)
Please sign in to comment.