33
33
FN_BACKLINK_TEXT = util .STX + "zz1337820767766393qq" + util .ETX
34
34
NBSP_PLACEHOLDER = util .STX + "qq3936677670287331zz" + util .ETX
35
35
RE_REF_ID = re .compile (r'(fnref)(\d+)' )
36
+ RE_REFERENCE = re .compile (r'(?<!!)\[\^([^\]]*)\](?!\s*:)' )
36
37
37
38
38
39
class FootnoteExtension (Extension ):
@@ -100,6 +101,7 @@ def extendMarkdown(self, md):
100
101
101
102
def reset (self ) -> None :
102
103
""" Clear footnotes on reset, and prepare for distinct document. """
104
+ self .footnote_order : list [str ] = []
103
105
self .footnotes : OrderedDict [str , str ] = OrderedDict ()
104
106
self .unique_prefix += 1
105
107
self .found_refs = {}
@@ -150,6 +152,11 @@ def setFootnote(self, id: str, text: str) -> None:
150
152
""" Store a footnote for later retrieval. """
151
153
self .footnotes [id ] = text
152
154
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
+
153
160
def get_separator (self ) -> str :
154
161
""" Get the footnote separator. """
155
162
return self .getConfig ("SEPARATOR" )
@@ -174,6 +181,8 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
174
181
if not list (self .footnotes .keys ()):
175
182
return None
176
183
184
+ self .reorderFootnoteDict ()
185
+
177
186
div = etree .Element ("div" )
178
187
div .set ('class' , 'footnote' )
179
188
etree .SubElement (div , "hr" )
@@ -212,9 +221,24 @@ def makeFootnotesDiv(self, root: etree.Element) -> etree.Element | None:
212
221
p .append (backlink )
213
222
return div
214
223
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
+
215
239
216
240
class FootnoteBlockProcessor (BlockProcessor ):
217
- """ Find all footnote references and store for later use. """
241
+ """ Find footnote definitions and references, storing both for later use. """
218
242
219
243
RE = re .compile (r'^[ ]{0,3}\[\^([^\]]*)\]:[ ]*(.*)$' , re .MULTILINE )
220
244
@@ -226,8 +250,14 @@ def test(self, parent: etree.Element, block: str) -> bool:
226
250
return True
227
251
228
252
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. """
230
254
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
+
231
261
m = self .RE .search (block )
232
262
if m :
233
263
id = m .group (1 )
0 commit comments