RST reader: Fix nested placeholder resolution for inline elements (#11753)

Given RST like

    .. _target:

    See |sub|.

    .. |sub| replace:: `text <target_>`_

'pandoc -f rst -t html' produces

    <div id="target">
    <p>See <a href="##REF##target">text</a>.</p>
    </div>

instead of the expected

    <div id="target">
    <p>See <a href="#target">text</a>.</p>
    </div>

It formerly worked and regressed with c8fda8f4d ("RST reader: Use a new
one-pass parsing strategy."), release 3.6.

What happens is that during parsing pass 1 the `replace::` value `text
<target_>`_ is parsed to

    Link nullAttr [Str "text"] ("##REF##target", "")

and is stored in ParserState's substitution table. Separately, '|sub|'
usage is parsed to

    Link nullAttr [Str "|sub|"] ("##SUBST##|sub|", "")

and is stored in the document tree. resolveReferences then replaces the
placeholder in the document node with substitution table node during
walkM. However, the freshly substituted ##REF## placeholder was not
revisited further, and appeared unresolved in the output.

To fix it, we resolve the node recursively until the result contains no
more placeholder. We must protect from self-references to avoid
endless recursion.
This commit is contained in:
Tobias Deiminger
2026-07-12 16:35:37 +02:00
committed by GitHub
parent ce37e24c6a
commit 5dd191098d
2 changed files with 55 additions and 10 deletions
+29
View File
@@ -220,5 +220,34 @@ tests = [ "line block with blank line" =:
, "include newlines" =:
"**before\nafter**" =?>
para (strong (text "before\nafter"))
, "bare reference reusing a named target resolves correctly" =:
T.unlines
[ ".. _target:"
, ""
, "See `alias <target_>`_ and again alias_."
] =?>
divWith ("target",[],[])
(para ("See " <> link "#target" "" "alias" <> " and again " <>
link "#target" "" "alias" <> "."))
, "self-referencing named target does not loop forever" =:
"See `a <a_>`_." =?>
para ("See " <> link "##REF##a" "" "a" <> ".")
, "reference to internal target embedded in a substitution" =:
T.unlines
[ ".. _target:"
, ""
, "See |sub|."
, ""
, ".. |sub| replace:: `text <target_>`_"
] =?>
divWith ("target",[],[])
(para ("See " <> link "#target" "" "text" <> "."))
, "circular substitution does not loop forever" =:
T.unlines
[ ".. |a| replace:: |a|"
, ""
, "Test |a| here."
] =?>
para ("Test " <> spanWith ("",[],[]) "|a|" <> " here.")
]
]