@@ -72,6 +72,9 @@ def __init__(self, **kwargs):
7272 self .found_refs : dict [str , int ] = {}
7373 self .used_refs : set [str ] = set ()
7474
75+ # Backward compatibility with old '%d' placeholder
76+ self .setConfig ('BACKLINK_TITLE' , self .getConfig ("BACKLINK_TITLE" ).replace ("%d" , "{}" ))
77+
7578 self .reset ()
7679
7780 def extendMarkdown (self , md ):
@@ -90,6 +93,11 @@ def extendMarkdown(self, md):
9093 # `codehilite`) so they can run on the the contents of the div.
9194 md .treeprocessors .register (FootnoteTreeprocessor (self ), 'footnote' , 50 )
9295
96+ # Insert a tree-processor to reorder the footnotes if necessary. This must be after
97+ # `inline` tree-processor so it can access the footnote reference order
98+ # (self.footnote_order) that gets populated by the FootnoteInlineProcessor.
99+ md .treeprocessors .register (FootnoteReorderingProcessor (self ), 'footnote-reorder' , 19 )
100+
93101 # Insert a tree-processor that will run after inline is done.
94102 # In this tree-processor we want to check our duplicate footnote tracker
95103 # And add additional `backrefs` to the footnote pointing back to the
@@ -181,17 +189,12 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
181189 if not list (self .footnotes .keys ()):
182190 return None
183191
184- self .reorderFootnoteDict ()
185-
186192 div = etree .Element ("div" )
187193 div .set ('class' , 'footnote' )
188194 etree .SubElement (div , "hr" )
189195 ol = etree .SubElement (div , "ol" )
190196 surrogate_parent = etree .Element ("div" )
191197
192- # Backward compatibility with old '%d' placeholder
193- backlink_title = self .getConfig ("BACKLINK_TITLE" ).replace ("%d" , "{}" )
194-
195198 for index , id in enumerate (self .footnotes .keys (), start = 1 ):
196199 li = etree .SubElement (ol , "li" )
197200 li .set ("id" , self .makeFootnoteId (id ))
@@ -207,7 +210,7 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
207210 backlink .set ("class" , "footnote-backref" )
208211 backlink .set (
209212 "title" ,
210- backlink_title .format (index )
213+ self . getConfig ( 'BACKLINK_TITLE' ) .format (index )
211214 )
212215 backlink .text = FN_BACKLINK_TEXT
213216
@@ -221,21 +224,6 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
221224 p .append (backlink )
222225 return div
223226
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-
239227
240228class FootnoteBlockProcessor (BlockProcessor ):
241229 """ Find footnote definitions and references, storing both for later use. """
@@ -253,11 +241,6 @@ def run(self, parent: etree.Element, blocks: list[str]) -> bool:
253241 """ Find, set, and remove footnote definitions. Find footnote references."""
254242 block = blocks .pop (0 )
255243
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-
261244 m = self .RE .search (block )
262245 if m :
263246 id = m .group (1 )
@@ -342,13 +325,15 @@ def __init__(self, pattern: str, footnotes: FootnoteExtension):
342325 def handleMatch (self , m : re .Match [str ], data : str ) -> tuple [etree .Element | None , int | None , int | None ]:
343326 id = m .group (1 )
344327 if id in self .footnotes .footnotes .keys ():
328+ self .footnotes .addFootnoteRef (id )
329+
345330 sup = etree .Element ("sup" )
346331 a = etree .SubElement (sup , "a" )
347332 sup .set ('id' , self .footnotes .makeFootnoteRefId (id , found = True ))
348333 a .set ('href' , '#' + self .footnotes .makeFootnoteId (id ))
349334 a .set ('class' , 'footnote-ref' )
350335 a .text = self .footnotes .getConfig ("SUPERSCRIPT_TEXT" ).format (
351- list ( self .footnotes .footnotes . keys ()) .index (id ) + 1
336+ self .footnotes .footnote_order .index (id ) + 1
352337 )
353338 return sup , m .start (0 ), m .end (0 )
354339 else :
@@ -431,6 +416,44 @@ def run(self, root: etree.Element) -> None:
431416 root .append (footnotesDiv )
432417
433418
419+ class FootnoteReorderingProcessor (Treeprocessor ):
420+ """ Reorder list items in the footnotes div. """
421+
422+ def __init__ (self , footnotes : FootnoteExtension ):
423+ self .footnotes = footnotes
424+
425+ def run (self , root : etree .Element ) -> None :
426+ if not self .footnotes .footnotes :
427+ return
428+ if self .footnotes .footnote_order != list (self .footnotes .footnotes .keys ()):
429+ for div in root .iter ('div' ):
430+ if div .attrib .get ('class' , '' ) == 'footnote' :
431+ self .reorder_footnotes (div )
432+ break
433+
434+ def reorder_footnotes (self , parent : etree .Element ) -> None :
435+ old_list = parent .find ('ol' )
436+ parent .remove (old_list )
437+ items = old_list .findall ('li' )
438+
439+ def order_by_id (li ) -> int :
440+ id = li .attrib .get ('id' , '' ).split (self .footnotes .get_separator (), 1 )[- 1 ]
441+ return (
442+ self .footnotes .footnote_order .index (id )
443+ if id in self .footnotes .footnote_order
444+ else len (self .footnotes .footnotes )
445+ )
446+
447+ items = sorted (items , key = order_by_id )
448+
449+ new_list = etree .SubElement (parent , 'ol' )
450+
451+ for index , item in enumerate (items , start = 1 ):
452+ backlink = item .find ('.//a[@class="footnote-backref"]' )
453+ backlink .set ("title" , self .footnotes .getConfig ("BACKLINK_TITLE" ).format (index ))
454+ new_list .append (item )
455+
456+
434457class FootnotePostprocessor (Postprocessor ):
435458 """ Replace placeholders with html entities. """
436459 def __init__ (self , footnotes : FootnoteExtension ):
0 commit comments