Skip to content

Commit d7203c9

Browse files
committed
Fix footnote ordering so footnotes are listed in the order in which their references appear
Fixes #1367
1 parent 4669a09 commit d7203c9

File tree

1 file changed

+32
-2
lines changed

1 file changed

+32
-2
lines changed

markdown/extensions/footnotes.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
FN_BACKLINK_TEXT = util.STX + "zz1337820767766393qq" + util.ETX
3434
NBSP_PLACEHOLDER = util.STX + "qq3936677670287331zz" + util.ETX
3535
RE_REF_ID = re.compile(r'(fnref)(\d+)')
36+
RE_REFERENCE = re.compile(r'(?<!!)\[\^([^\]]*)\](?!\s*:)')
3637

3738

3839
class FootnoteExtension(Extension):
@@ -100,6 +101,7 @@ def extendMarkdown(self, md):
100101

101102
def reset(self) -> None:
102103
""" Clear footnotes on reset, and prepare for distinct document. """
104+
self.footnote_order: list[str] = []
103105
self.footnotes: OrderedDict[str, str] = OrderedDict()
104106
self.unique_prefix += 1
105107
self.found_refs = {}
@@ -150,6 +152,11 @@ def setFootnote(self, id: str, text: str) -> None:
150152
""" Store a footnote for later retrieval. """
151153
self.footnotes[id] = text
152154

155+
def addFootnoteRef(self, id: str) -> None:
156+
""" Store a footnote reference id in order of appearance. """
157+
if id not in self.footnote_order:
158+
self.footnote_order.append(id)
159+
153160
def get_separator(self) -> str:
154161
""" Get the footnote separator. """
155162
return self.getConfig("SEPARATOR")
@@ -174,6 +181,8 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
174181
if not list(self.footnotes.keys()):
175182
return None
176183

184+
self.reorderFootnoteDict()
185+
177186
div = etree.Element("div")
178187
div.set('class', 'footnote')
179188
etree.SubElement(div, "hr")
@@ -212,9 +221,24 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
212221
p.append(backlink)
213222
return div
214223

224+
def reorderFootnoteDict(self) -> None:
225+
""" Reorder the footnotes dict based on the order of references found. """
226+
ordered_footnotes = OrderedDict()
227+
228+
for ref in self.footnote_order:
229+
if ref in self.footnotes:
230+
ordered_footnotes[ref] = self.footnotes[ref]
231+
232+
# Add back any footnotes that were defined but not referenced.
233+
for id, text in self.footnotes.items():
234+
if id not in ordered_footnotes:
235+
ordered_footnotes[id] = text
236+
237+
self.footnotes = ordered_footnotes
238+
215239

216240
class FootnoteBlockProcessor(BlockProcessor):
217-
""" Find all footnote references and store for later use. """
241+
""" Find footnote definitions and references, storing both for later use. """
218242

219243
RE = re.compile(r'^[ ]{0,3}\[\^([^\]]*)\]:[ ]*(.*)$', re.MULTILINE)
220244

@@ -226,8 +250,14 @@ def test(self, parent: etree.Element, block: str) -> bool:
226250
return True
227251

228252
def run(self, parent: etree.Element, blocks: list[str]) -> bool:
229-
""" Find, set, and remove footnote definitions. """
253+
""" Find, set, and remove footnote definitions. Find footnote references."""
230254
block = blocks.pop(0)
255+
256+
# Find any footnote references in the block to determine order.
257+
for match in RE_REFERENCE.finditer(block):
258+
ref_id = match.group(1)
259+
self.footnotes.addFootnoteRef(ref_id)
260+
231261
m = self.RE.search(block)
232262
if m:
233263
id = m.group(1)

0 commit comments

Comments
 (0)