mirror of
https://github.com/jgm/pandoc.git
synced 2026-08-28 17:20:47 +08:00
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:
@@ -25,6 +25,7 @@ import Data.List (deleteFirstsBy, elemIndex, nub, partition, sort, transpose)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (fromMaybe, maybeToList, isJust, isNothing, catMaybes)
|
||||
import Data.Sequence (ViewR (..), viewr)
|
||||
import qualified Data.Set as Set
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Text.Printf (printf)
|
||||
@@ -186,7 +187,10 @@ resolveBlockSubstitutions (Para [Link _attr ils (s,_)])
|
||||
resolveBlockSubstitutions x = return x
|
||||
|
||||
resolveReferences :: PandocMonad m => Inline -> RSTParser m Inline
|
||||
resolveReferences x@(Link _ ils (s,_))
|
||||
resolveReferences = resolveReferences' Set.empty
|
||||
|
||||
resolveReferences' :: PandocMonad m => Set.Set Key -> Inline -> RSTParser m Inline
|
||||
resolveReferences' seen x@(Link _ ils (s,_))
|
||||
| Just ref <- T.stripPrefix "##REF##" s = do
|
||||
let isAnonKey (Key (T.uncons -> Just ('_',_))) = True
|
||||
isAnonKey _ = False
|
||||
@@ -199,11 +203,17 @@ resolveReferences x@(Link _ ils (s,_))
|
||||
[] -> mzero -- TODO log?
|
||||
(k:_) -> return k
|
||||
else return $ toKey ref
|
||||
((src,tit), attr) <- lookupKey [] key
|
||||
-- if anonymous link, remove key so it won't be used again
|
||||
when (isAnonKey key) $ updateState $ \st ->
|
||||
st{ stateKeys = M.delete key keyTable }
|
||||
return $ Link attr ils (src, tit)
|
||||
if key `Set.member` seen
|
||||
then do
|
||||
pos <- getPosition
|
||||
let Key key' = key
|
||||
logMessage $ CircularReference key' pos
|
||||
return x
|
||||
else do
|
||||
((src,tit), attr) <- lookupKey [] key
|
||||
when (isAnonKey key) $ updateState $ \st ->
|
||||
st{ stateKeys = M.delete key keyTable }
|
||||
resolveReferences' (Set.insert key seen) (Link attr ils (src, tit))
|
||||
| Just ref <- T.stripPrefix "##NOTE##" s = do
|
||||
state <- getState
|
||||
let notes = stateNotes state
|
||||
@@ -229,18 +239,24 @@ resolveReferences x@(Link _ ils (s,_))
|
||||
| Just ref <- T.stripPrefix "##SUBST##" s = do
|
||||
substTable <- stateSubstitutions <$> getState
|
||||
let key@(Key key') = toKey $ stripFirstAndLast ref
|
||||
case M.lookup key substTable of
|
||||
if key `Set.member` seen
|
||||
then do
|
||||
pos <- getPosition
|
||||
logMessage $ CircularReference key' pos
|
||||
return $ Span ("",[],[]) ils
|
||||
else case M.lookup key substTable of
|
||||
Nothing -> do
|
||||
pos <- getPosition
|
||||
logMessage $ ReferenceNotFound (tshow key') pos
|
||||
return $ Span ("",[],[]) ils
|
||||
Just target -> case
|
||||
B.toList target of
|
||||
Just target -> do
|
||||
resolved <- case B.toList target of
|
||||
[Para [t]] -> return t
|
||||
[Para xs] -> return $ Span nullAttr xs
|
||||
bls -> return $ Span nullAttr $ blocksToInlines bls
|
||||
resolveReferences' (Set.insert key seen) resolved
|
||||
| otherwise = return x
|
||||
resolveReferences x = return x
|
||||
resolveReferences' _ x = return x
|
||||
|
||||
parseCitation :: PandocMonad m
|
||||
=> (Text, Text) -> RSTParser m (Inlines, [Blocks])
|
||||
|
||||
@@ -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.")
|
||||
]
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user