Skip to content

Footnotes: indented nested footnote definition is silently dropped and produces a dead #fn: anchor #1681

Open

Description

@rahul05ranjan

Summary

An indented (i.e. within the multi-line content of another footnote) footnote definition is silently dropped from the output. If the nested footnote is also referenced anywhere, the rendered reference links to a #fn: anchor that never exists in the document (a dead internal link). Un-indented nested definitions (in the same block as the parent) work correctly and are tested, so only the indented/multi-line variant is affected.

Repro

import markdown

text = """\
Main[^1].

[^1]: First

    [^2]: Second
"""

print(markdown.markdown(text, extensions=['footnotes']))

Expected (per the documented nested-footnote behavior, see the existing test_footnote_reference_within_footnote_definition which nests the definition un-indented): [^2] should render as its own footnote item (<li id="fn:2">).

Actual (Python-Markdown 3.10): the [^2]: Second line disappears completely — neither footnote content nor a fn:2 anchor is emitted:

<p>Main<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:1">
<p>First&#160;<a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
</li>
</ol>
</div>

If the parent footnote also references it ([^1]: First [^2]), the output contains href="#fn:2" and id="fnref:2" but no id="fn:2" target — a broken link plus silent data loss of the definition text.

Root cause

FootnoteBlockProcessor.detectTabbed() (markdown/extensions/footnotes.py) collects each 4-space-indented block as content of the parent footnote, but it runs self.RE.search(block) on the still-indented block. The definition regex ^[ ]{0,3}\[\^([^\]]*)\]: therefore never matches an indented [^2]: ... line, so the whole line is appended to the parent footnote's content. Later, when the parent footnote's content is re-rendered internally, FootnoteBlockProcessor.run() strips the indent via detab() and does match the line as a definition — it is removed from the content and stored in the footnote registry at a point where the footnote <div>/<ol> structure has already been generated, so the nested definition is never rendered. Content silently lost; anchor never created.

Environment

  • Python-Markdown: main branch (3.10)
  • Python: 3.13

Activity

  1. waylan commented on Oct 30, 2026

    @waylan
    Member

    Our footnote implementation is a clone of PHP Markdown Extra. As far as I can tell, footnote definitions are not recognized within a footnote definition.

    There seems to be a few differences between the PHP implementation and ours and I'm not sure where or when the divergence happened. Consider this example:

    Main[^1].
    
    [^1]: First
    [^2]: Second

    PHP's output is as follows:

    <p>Main<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
    
    <div class="footnotes" role="doc-endnotes">
    <hr />
    <ol>
    
    <li id="fn:1" role="doc-endnote">
    <p>First&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#8617;&#xFE0E;</a></p>
    </li>
    
    </ol>
    </div>

    Note that the "Second" footnote is completing missing. This makes sense as the footnote is not reference in the document and I assume is intentional. Interestingly, our behavior is different:

    <p>Main<sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup>.</p>
    <div class="footnote">
    <hr />
    <ol>
    <li id="fn:1">
    <p>First&#160;<a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">&#8617;</a></p>
    </li>
    <li id="fn:2">
    <p>Second&#160;<a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">&#8617;</a></p>
    </li>
    </ol>
    </div>

    We show the "second" footnote even though it is not reference in the document. This could be viewed as a bug. At the very least it suggests that the original report is not necessarily a bug. But, lets look at that... Using the following input:

    Main[^1].
    
    [^1]: First
    
        [^2]: Second

    PHP's output is

    <p>Main<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
    
    <div class="footnotes" role="doc-endnotes">
    <hr />
    <ol>
    
    <li id="fn:1" role="doc-endnote">
    <p>First</p>
    
    <p>[^2]: Second&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#8617;&#xFE0E;</a></p>
    </li>
    
    </ol>
    </div>

    Note that [^2]: Second is not recognized as a footnote definition and simply treated as a paragraph of text. Therefore, if we have a bug of any kind, it is that we recognize that as a footnote definition at all.

    As far as I can tell, the PHP implementation only recognizes footnote definitions which are defined at the root level of the document (no indentation). But when it recognizes them, they are removed from the document whether they are referenced or not. Finally, only referenced footnotes are included in the footnotes displayed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions