Skip to content

Commit 5d7f8ca

Browse files
committed
Refactor reordering to depend on inline processing
- Remove parts of the previous implementation - Add FootnoteReorderingProcessor which reorders the footnotes if necessary - Move backlink title compatability trick to main class init method to avoid repetition
1 parent ce45459 commit 5d7f8ca

File tree

1 file changed

+50
-27
lines changed

1 file changed

+50
-27
lines changed

markdown/extensions/footnotes.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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

240228
class 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+
434457
class FootnotePostprocessor(Postprocessor):
435458
""" Replace placeholders with html entities. """
436459
def __init__(self, footnotes: FootnoteExtension):

0 commit comments

Comments
 (0)