3333FN_BACKLINK_TEXT = util .STX + "zz1337820767766393qq" + util .ETX
3434NBSP_PLACEHOLDER = util .STX + "qq3936677670287331zz" + util .ETX
3535RE_REF_ID = re .compile (r'(fnref)(\d+)' )
36+ RE_REFERENCE = re .compile (r'(?<!!)\[\^([^\]]*)\](?!\s*:)' )
3637
3738
3839class 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
216240class 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