Skip to content

Commit ed7ff42

Browse files
committed
Add config option to support previous behavior
- Don't register FootnoteReorderingProcessor if USE_DEFINITION_ORDER - Footnote reference numbering follows config - Test added - Config option added to docs with note on behavior change in v3.9.0 - Update changelog
1 parent d8ecae6 commit ed7ff42

File tree

4 files changed

+60
-6
lines changed

4 files changed

+60
-6
lines changed

docs/changelog.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ See the [Contributing Guide](contributing.md) for details.
1212

1313
## [Unreleased]
1414

15+
### Changed
16+
17+
* Footnotes are now ordered by the occurrence of their references in the document.
18+
A new config option for `footnotes`, `USE_DEFINITION_ORDER`, has been added to support
19+
restoring the previous behavior of ordering footnotes by the occurrence of definitions.
20+
1521
### Fixed
1622

17-
* Fix handling of incomplete HTML tags in code spans in Python 3.14.
18-
* Fix issue with footnote ordering (#1367).
1923
* Ensure inline processing iterates through elements in document order.
24+
* Fix handling of incomplete HTML tags in code spans in Python 3.14.
2025

2126
## [3.8.2] - 2025-06-19
2227

docs/extensions/footnotes.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ The following options are provided to configure the output:
9898
* **`SEPARATOR`**:
9999
The text string used to set the footnote separator. Defaults to `:`.
100100

101+
* **`USE_DEFINITION_ORDER`**:
102+
Whether to order footnotes by the occurence of footnote definitions
103+
in the document. Defaults to `False`.
104+
105+
Introduced in version 3.9.0, this option allows footnotes to be ordered
106+
by the occurence of their definitions in the document, rather than by the
107+
order of their references in the text. This was the behavior of
108+
previous versions of the extension.
109+
101110
A trivial example:
102111

103112
```python

markdown/extensions/footnotes.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ def __init__(self, **kwargs):
6262
],
6363
'SEPARATOR': [
6464
':', 'Footnote separator.'
65+
],
66+
'USE_DEFINITION_ORDER': [
67+
False, 'Whether to order footnotes by footnote content rather than by footnote label.'
6568
]
6669
}
6770
""" Default configuration options. """
@@ -96,7 +99,8 @@ def extendMarkdown(self, md):
9699
# Insert a tree-processor to reorder the footnotes if necessary. This must be after
97100
# `inline` tree-processor so it can access the footnote reference order
98101
# (`self.footnote_order`) that gets populated by the `FootnoteInlineProcessor`.
99-
md.treeprocessors.register(FootnoteReorderingProcessor(self), 'footnote-reorder', 19)
102+
if not self.getConfig("USE_DEFINITION_ORDER"):
103+
md.treeprocessors.register(FootnoteReorderingProcessor(self), 'footnote-reorder', 19)
100104

101105
# Insert a tree-processor that will run after inline is done.
102106
# In this tree-processor we want to check our duplicate footnote tracker
@@ -327,14 +331,19 @@ def handleMatch(self, m: re.Match[str], data: str) -> tuple[etree.Element | None
327331
if id in self.footnotes.footnotes.keys():
328332
self.footnotes.addFootnoteRef(id)
329333

334+
if not self.footnotes.getConfig("USE_DEFINITION_ORDER"):
335+
# Order by reference
336+
footnote_num = self.footnotes.footnote_order.index(id) + 1
337+
else:
338+
# Order by definition
339+
footnote_num = list(self.footnotes.footnotes.keys()).index(id) + 1
340+
330341
sup = etree.Element("sup")
331342
a = etree.SubElement(sup, "a")
332343
sup.set('id', self.footnotes.makeFootnoteRefId(id, found=True))
333344
a.set('href', '#' + self.footnotes.makeFootnoteId(id))
334345
a.set('class', 'footnote-ref')
335-
a.text = self.footnotes.getConfig("SUPERSCRIPT_TEXT").format(
336-
self.footnotes.footnote_order.index(id) + 1
337-
)
346+
a.text = self.footnotes.getConfig("SUPERSCRIPT_TEXT").format(footnote_num)
338347
return sup, m.start(0), m.end(0)
339348
else:
340349
return None, None, None

tests/test_syntax/extensions/test_footnotes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,37 @@ def test_footnote_order_tricky(self):
401401
'</div>'
402402
)
403403

404+
def test_footnote_order_by_definition(self):
405+
"""Test that footnotes occur in order of definition occurence when so configured."""
406+
407+
self.assertMarkdownRenders(
408+
self.dedent(
409+
"""
410+
First footnote reference[^last_def]. Second footnote reference[^first_def].
411+
412+
[^first_def]: First footnote.
413+
[^last_def]: Second footnote.
414+
"""
415+
),
416+
'<p>First footnote reference<sup id="fnref:last_def"><a class="footnote-ref" '
417+
'href="#fn:last_def">2</a></sup>. Second footnote reference<sup id="fnref:first_def">'
418+
'<a class="footnote-ref" href="#fn:first_def">1</a></sup>.</p>\n'
419+
'<div class="footnote">\n'
420+
'<hr />\n'
421+
'<ol>\n'
422+
'<li id="fn:first_def">\n'
423+
'<p>First footnote.&#160;<a class="footnote-backref" href="#fnref:first_def" '
424+
'title="Jump back to footnote 1 in the text">&#8617;</a></p>\n'
425+
'</li>\n'
426+
'<li id="fn:last_def">\n'
427+
'<p>Second footnote.&#160;<a class="footnote-backref" href="#fnref:last_def" '
428+
'title="Jump back to footnote 2 in the text">&#8617;</a></p>\n'
429+
'</li>\n'
430+
'</ol>\n'
431+
'</div>',
432+
extension_configs={'footnotes': {'USE_DEFINITION_ORDER': True}}
433+
)
434+
404435
def test_footnote_reference_within_code_span(self):
405436
"""Test footnote reference within a code span."""
406437

0 commit comments

Comments
 (0)