Skip to content

Commit b960fbc

Browse files
tiberlas=
authored andcommitted
footnotes: add footnote properties
1 parent afdb63a commit b960fbc

File tree

4 files changed

+202
-1
lines changed

4 files changed

+202
-1
lines changed

src/docx/oxml/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,29 @@
105105
register_element_cls("w:startOverride", CT_DecimalNumber)
106106

107107
from .section import ( # noqa
108+
CT_FtnProps,
109+
CT_FtnPos,
108110
CT_HdrFtr,
109111
CT_HdrFtrRef,
110112
CT_PageMar,
111113
CT_PageSz,
114+
CT_NumFmt,
115+
CT_NumRestart,
112116
CT_SectPr,
113117
CT_SectType,
114118
)
115119

120+
register_element_cls('w:footnotePr', CT_FtnProps)
116121
register_element_cls("w:footerReference", CT_HdrFtrRef)
117122
register_element_cls("w:ftr", CT_HdrFtr)
118123
register_element_cls("w:hdr", CT_HdrFtr)
119124
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)
120128
register_element_cls("w:pgMar", CT_PageMar)
121129
register_element_cls("w:pgSz", CT_PageSz)
130+
register_element_cls('w:pos', CT_FtnPos)
122131
register_element_cls("w:sectPr", CT_SectPr)
123132
register_element_cls("w:type", CT_SectType)
124133

src/docx/oxml/section.py

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from warnings import warn
6+
57
from copy import deepcopy
68
from typing import Callable, Iterator, List, Sequence, cast
79

@@ -11,7 +13,7 @@
1113
from docx.enum.section import WD_HEADER_FOOTER, WD_ORIENTATION, WD_SECTION_START
1214
from docx.oxml.ns import nsmap
1315
from docx.oxml.shared import CT_OnOff
14-
from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure, XsdString
16+
from docx.oxml.simpletypes import ST_SignedTwipsMeasure, ST_TwipsMeasure, XsdString, ST_FtnPos, ST_NumberFormat, ST_RestartNumber
1517
from docx.oxml.table import CT_Tbl
1618
from docx.oxml.text.paragraph import CT_P
1719
from docx.oxml.xmlchemy import (
@@ -26,6 +28,22 @@
2628
BlockElement: TypeAlias = "CT_P | CT_Tbl"
2729

2830

31+
class CT_FtnPos(BaseOxmlElement):
32+
"""``<w:pos>`` element, footnote placement"""
33+
val = RequiredAttribute('w:val', ST_FtnPos)
34+
35+
36+
class CT_FtnProps(BaseOxmlElement):
37+
"""``<w:footnotePr>`` element, section wide footnote properties"""
38+
_tag_seq = (
39+
'w:pos', 'w:numFmt', 'w:numStart', 'w:numRestart'
40+
)
41+
pos = ZeroOrOne('w:pos', successors=_tag_seq)
42+
numFmt = ZeroOrOne('w:numFmt', successors=_tag_seq[1:])
43+
numStart = ZeroOrOne('w:numStart', successors=_tag_seq[2:])
44+
numRestart = ZeroOrOne('w:numRestart', successors=_tag_seq[3:])
45+
46+
2947
class CT_HdrFtr(BaseOxmlElement):
3048
"""`w:hdr` and `w:ftr`, the root element for header and footer part respectively."""
3149

@@ -57,6 +75,16 @@ class CT_HdrFtrRef(BaseOxmlElement):
5775
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
5876

5977

78+
class CT_NumFmt(BaseOxmlElement):
79+
"""``<w:numFmt>`` element, footnote numbering format"""
80+
val = RequiredAttribute('w:val', ST_NumberFormat)
81+
82+
83+
class CT_NumRestart(BaseOxmlElement):
84+
"""``<w:numStart>`` element, footnote numbering restart location"""
85+
val = RequiredAttribute('w:val', ST_RestartNumber)
86+
87+
6088
class CT_PageMar(BaseOxmlElement):
6189
"""``<w:pgMar>`` element, defining page margins."""
6290

@@ -145,6 +173,7 @@ class CT_SectPr(BaseOxmlElement):
145173
titlePg: CT_OnOff | None = ZeroOrOne( # pyright: ignore[reportAssignmentType]
146174
"w:titlePg", successors=_tag_seq[14:]
147175
)
176+
footnotePr = ZeroOrOne("w:footnotePr", successors=_tag_seq[1:])
148177
del _tag_seq
149178

150179
def add_footerReference(self, type_: WD_HEADER_FOOTER, rId: str) -> CT_HdrFtrRef:
@@ -211,6 +240,92 @@ def footer(self, value: int | Length | None):
211240
pgMar = self.get_or_add_pgMar()
212241
pgMar.footer = value if value is None or isinstance(value, Length) else Length(value)
213242

243+
@property
244+
def footnote_number_format(self):
245+
"""
246+
The value of the ``w:val`` attribute in the ``<w:numFmt>`` child
247+
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
248+
attribute is not present.
249+
"""
250+
fPr = self.footnotePr
251+
if fPr is None or fPr.numFmt:
252+
return None
253+
return fPr.numFmt.val
254+
255+
@footnote_number_format.setter
256+
def footnote_number_format(self, value):
257+
fPr = self.get_or_add_footnotePr()
258+
numFmt = fPr.get_or_add_numFmt()
259+
numFmt.val = value
260+
261+
@property
262+
def footnote_numbering_restart_location(self):
263+
"""
264+
The value of the ``w:val`` attribute in the ``<w:numRestart>`` child
265+
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
266+
attribute is not present.
267+
"""
268+
fPr = self.footnotePr
269+
if fPr is None or fPr.numRestart:
270+
return None
271+
return fPr.numRestart.val
272+
273+
@footnote_numbering_restart_location.setter
274+
def footnote_numbering_restart_location(self, value):
275+
fPr = self.get_or_add_footnotePr()
276+
numStart = fPr.get_or_add_numStart()
277+
numRestart = fPr.get_or_add_numRestart()
278+
numRestart.val = value
279+
if numStart is None or len(numStart.values()) == 0:
280+
numStart.val = 1
281+
elif value != 'continuous':
282+
numStart.val = 1
283+
msg = "When ``<w:numRestart> is not 'continuous', then ``<w:numStart>`` must be 1."
284+
warn(msg, UserWarning, stacklevel=2)
285+
286+
@property
287+
def footnote_numbering_start_value(self):
288+
"""
289+
The value of the ``w:val`` attribute in the ``<w:numStart>`` child
290+
element of ``<w:footnotePr>`` element, as a |Number|, or |None| if either the element or the
291+
attribute is not present.
292+
"""
293+
fPr = self.footnotePr
294+
if fPr is None or fPr.numStart:
295+
return None
296+
return fPr.numStart.val
297+
298+
@footnote_numbering_start_value.setter
299+
def footnote_numbering_start_value(self, value):
300+
fPr = self.get_or_add_footnotePr()
301+
numStart = fPr.get_or_add_numStart()
302+
numRestart = fPr.get_or_add_numRestart()
303+
numStart.val = value
304+
if numRestart is None or len(numRestart.values()) == 0:
305+
numRestart.val = 'continuous'
306+
elif value != 1:
307+
numRestart.val = 'continuous'
308+
msg = "When ``<w:numStart> is not 1, then ``<w:numRestart>`` must be 'continuous'."
309+
warn(msg, UserWarning, stacklevel=2)
310+
311+
@property
312+
def footnote_position(self):
313+
"""
314+
The value of the ``w:val`` attribute in the ``<w:pos>`` child
315+
element of ``<w:footnotePr>`` element, as a |String|, or |None| if either the element or the
316+
attribute is not present.
317+
"""
318+
fPr = self.footnotePr
319+
if fPr is None or fPr.pos is None:
320+
return None
321+
return fPr.pos.val
322+
323+
@footnote_position.setter
324+
def footnote_position(self, value):
325+
fPr = self.get_or_add_footnotePr()
326+
pos = fPr.get_or_add_pos()
327+
pos.val = value
328+
214329
def get_footerReference(self, type_: WD_HEADER_FOOTER) -> CT_HdrFtrRef | None:
215330
"""Return footerReference element of `type_` or None if not present."""
216331
path = "./w:footerReference[@w:type='%s']" % WD_HEADER_FOOTER.to_xml(type_)

src/docx/oxml/simpletypes.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,18 @@ class ST_DrawingElementId(XsdUnsignedInt):
221221
pass
222222

223223

224+
class ST_FtnPos(XsdString):
225+
226+
@classmethod
227+
def validate(cls, value):
228+
cls.validate_string(value)
229+
valid_values = ('pageBottom', 'beneathText', 'sectEnd', 'docEnd')
230+
if value not in valid_values:
231+
raise ValueError(
232+
"must be one of %s, got '%s'" % (valid_values, value)
233+
)
234+
235+
224236
class ST_HexColor(BaseStringType):
225237
@classmethod
226238
def convert_from_xml( # pyright: ignore[reportIncompatibleMethodOverride]
@@ -271,6 +283,10 @@ def convert_to_xml(cls, value: int | Length) -> str:
271283
return str(half_points)
272284

273285

286+
class ST_NumberFormat(XsdString):
287+
pass
288+
289+
274290
class ST_Merge(XsdStringEnumeration):
275291
"""Valid values for <w:xMerge val=""> attribute."""
276292

@@ -305,6 +321,17 @@ class ST_RelationshipId(XsdString):
305321
pass
306322

307323

324+
class ST_RestartNumber(XsdString):
325+
326+
@classmethod
327+
def validate(cls, value):
328+
cls.validate_string(value)
329+
valid_values = ('continuous', 'eachSect', 'eachPage')
330+
if value not in valid_values:
331+
raise ValueError(
332+
"must be one of %s, got '%s'" % (valid_values, value)
333+
)
334+
308335
class ST_SignedTwipsMeasure(XsdInt):
309336
@classmethod
310337
def convert_from_xml(cls, str_value: str) -> Length:

src/docx/section.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,56 @@ def footer_distance(self) -> Length | None:
115115
def footer_distance(self, value: int | Length | None):
116116
self._sectPr.footer = value
117117

118+
@property
119+
def footnote_number_format(self):
120+
"""The number format property for the |Footnotes|.
121+
122+
Read/write. |None| if no setting is present in the XML.
123+
"""
124+
return self._sectPr.footnote_number_format
125+
126+
@footnote_number_format.setter
127+
def footnote_number_format(self, value):
128+
self._sectPr.footnote_number_format = value
129+
130+
@property
131+
def footnote_numbering_restart_location(self):
132+
"""The number restart location property for the |Footnotes|.
133+
134+
If the value is not |continuous| then the footnote number start value property is set to |1|.
135+
Read/write. |None| if no setting is present in the XML.
136+
"""
137+
return self._sectPr.footnote_numbering_restart_location
138+
139+
@footnote_numbering_restart_location.setter
140+
def footnote_numbering_restart_location(self, value):
141+
self._sectPr.footnote_numbering_restart_location = value
142+
143+
@property
144+
def footnote_numbering_start_value(self):
145+
"""The number start value property for the |Footnotes|.
146+
147+
If the value is not |1| then footnote number restart position property is set to |continuous|.
148+
Read/write. |None| if no setting is present in the XML.
149+
"""
150+
return self._sectPr.footnote_numbering_start_value
151+
152+
@footnote_numbering_start_value.setter
153+
def footnote_numbering_start_value(self, value):
154+
self._sectPr.footnote_numbering_start_value = value
155+
156+
@property
157+
def footnote_position(self):
158+
"""The position property for the |Footnotes|.
159+
160+
Read/write. |None| if no setting is present in the XML.
161+
"""
162+
return self._sectPr.footnote_position
163+
164+
@footnote_position.setter
165+
def footnote_position(self, value):
166+
self._sectPr.footnote_position = value
167+
118168
@property
119169
def gutter(self) -> Length | None:
120170
"""|Length| object representing page gutter size in English Metric Units.

0 commit comments

Comments
 (0)