mirror of
https://github.com/jgm/pandoc.git
synced 2026-08-29 01:41:20 +08:00
LaTeX reader: support \footnotemark and \footnotetext (#11450).
These commands allow separating the footnote mark from its content, useful in tables, minipages, and other contexts where \footnote cannot be used directly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> Closes #11450.
This commit is contained in:
@@ -110,6 +110,7 @@ parseLaTeX = do
|
||||
(if bottomLevel < 1
|
||||
then walk (adjustHeaders (1 - bottomLevel))
|
||||
else id) $
|
||||
walk (resolveFootnoteMarks (sFootnoteTexts st)) $
|
||||
walk (resolveRefs (sLabels st)) doc'
|
||||
return $ Pandoc meta bs'
|
||||
|
||||
@@ -124,6 +125,18 @@ resolveRefs labels x@(Link (ident,classes,kvs) _ _) =
|
||||
_ -> x
|
||||
resolveRefs _ x = x
|
||||
|
||||
-- | Resolve footnote marks (\footnotemark) to actual notes using
|
||||
-- the footnote texts collected from \footnotetext commands.
|
||||
resolveFootnoteMarks :: M.Map Int Blocks -> Inline -> Inline
|
||||
resolveFootnoteMarks fnTexts (Span (_, classes, kvs) _)
|
||||
| "footnote-mark" `elem` classes
|
||||
, Just numText <- lookup "note-num" kvs
|
||||
, [(n, "")] <- reads (T.unpack numText)
|
||||
= case M.lookup n fnTexts of
|
||||
Just contents -> Note (toList contents)
|
||||
Nothing -> Str "" -- No matching footnotetext found
|
||||
resolveFootnoteMarks _ x = x
|
||||
|
||||
|
||||
-- testParser :: LP PandocIO a -> Text -> IO a
|
||||
-- testParser p t = do
|
||||
@@ -407,6 +420,8 @@ inlineCommands = M.unions
|
||||
, ("lowercase", makeLowercase <$> tok)
|
||||
, ("thanks", skipopts >> note <$> grouped block)
|
||||
, ("footnote", skipopts >> footnote)
|
||||
, ("footnotemark", footnotemark)
|
||||
, ("footnotetext", footnotetext)
|
||||
, ("newline", pure B.linebreak)
|
||||
, ("passthrough", fixPassthroughEscapes <$> tok)
|
||||
-- \passthrough macro used by latex writer
|
||||
@@ -477,6 +492,39 @@ footnote = do
|
||||
contents <- grouped block >>= walkM resolveNoteLabel
|
||||
return $ note contents
|
||||
|
||||
-- | Parse \footnotemark[n]. If n is not given, increment the counter.
|
||||
-- Returns a span marker that will be resolved to a Note later.
|
||||
footnotemark :: PandocMonad m => LP m Inlines
|
||||
footnotemark = do
|
||||
mbNum <- optionalFootnoteNum
|
||||
noteNum <- case mbNum of
|
||||
Just n -> return n
|
||||
Nothing -> do
|
||||
updateState $ \st -> st{ sLastNoteNum = sLastNoteNum st + 1 }
|
||||
sLastNoteNum <$> getState
|
||||
return $ B.spanWith ("", ["footnote-mark"], [("note-num", tshow noteNum)]) mempty
|
||||
|
||||
-- | Parse \footnotetext[n]{text}. If n is not given, use current counter.
|
||||
-- Stores the text in state to be resolved later.
|
||||
footnotetext :: PandocMonad m => LP m Inlines
|
||||
footnotetext = do
|
||||
mbNum <- optionalFootnoteNum
|
||||
noteNum <- case mbNum of
|
||||
Just n -> return n
|
||||
Nothing -> sLastNoteNum <$> getState
|
||||
contents <- grouped block >>= walkM resolveNoteLabel
|
||||
updateState $ \st -> st{
|
||||
sFootnoteTexts = M.insert noteNum contents (sFootnoteTexts st) }
|
||||
return mempty
|
||||
|
||||
-- | Parse optional footnote number argument [n]
|
||||
optionalFootnoteNum :: PandocMonad m => LP m (Maybe Int)
|
||||
optionalFootnoteNum = option Nothing $ do
|
||||
t <- bracketedToks
|
||||
case reads (T.unpack $ untokenize t) of
|
||||
[(n, "")] -> return $ Just n
|
||||
_ -> return Nothing
|
||||
|
||||
resolveNoteLabel :: PandocMonad m => Inline -> LP m Inline
|
||||
resolveNoteLabel (Span (_,cls,kvs) _)
|
||||
| Just lab <- lookup "label" kvs = do
|
||||
|
||||
@@ -165,6 +165,7 @@ data LaTeXState = LaTeXState{ sOptions :: ReaderOptions
|
||||
, sLastFigureNum :: DottedNum
|
||||
, sLastTableNum :: DottedNum
|
||||
, sLastNoteNum :: Int
|
||||
, sFootnoteTexts :: M.Map Int Blocks
|
||||
, sTheoremMap :: M.Map Text TheoremSpec
|
||||
, sLastTheoremStyle :: TheoremStyle
|
||||
, sLastLabel :: Maybe Text
|
||||
@@ -195,6 +196,7 @@ defaultLaTeXState = LaTeXState{ sOptions = def
|
||||
, sLastFigureNum = DottedNum []
|
||||
, sLastTableNum = DottedNum []
|
||||
, sLastNoteNum = 0
|
||||
, sFootnoteTexts = M.empty
|
||||
, sTheoremMap = M.empty
|
||||
, sLastTheoremStyle = PlainStyle
|
||||
, sLastLabel = Nothing
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
Test for \footnotemark and \footnotetext (issue #11450)
|
||||
|
||||
```
|
||||
% pandoc -f latex -t native
|
||||
Text\footnotemark{}.
|
||||
\footnotetext{The footnote content.}
|
||||
^D
|
||||
[ Para
|
||||
[ Str "Text"
|
||||
, Note
|
||||
[ Para
|
||||
[ Str "The"
|
||||
, Space
|
||||
, Str "footnote"
|
||||
, Space
|
||||
, Str "content."
|
||||
]
|
||||
]
|
||||
, Str "."
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
With explicit footnote numbers:
|
||||
|
||||
```
|
||||
% pandoc -f latex -t native
|
||||
First\footnotemark[1] and second\footnotemark[2].
|
||||
\footnotetext[1]{First note.}
|
||||
\footnotetext[2]{Second note.}
|
||||
^D
|
||||
[ Para
|
||||
[ Str "First"
|
||||
, Note [ Para [ Str "First" , Space , Str "note." ] ]
|
||||
, Space
|
||||
, Str "and"
|
||||
, Space
|
||||
, Str "second"
|
||||
, Note [ Para [ Str "Second" , Space , Str "note." ] ]
|
||||
, Str "."
|
||||
]
|
||||
]
|
||||
```
|
||||
|
||||
Mixed with regular footnotes:
|
||||
|
||||
```
|
||||
% pandoc -f latex -t native
|
||||
Text\footnotemark[1] and more\footnote{Regular footnote.}
|
||||
\footnotetext[1]{Marked footnote.}
|
||||
^D
|
||||
[ Para
|
||||
[ Str "Text"
|
||||
, Note [ Para [ Str "Marked" , Space , Str "footnote." ] ]
|
||||
, Space
|
||||
, Str "and"
|
||||
, Space
|
||||
, Str "more"
|
||||
, Note [ Para [ Str "Regular" , Space , Str "footnote." ] ]
|
||||
]
|
||||
]
|
||||
```
|
||||
Reference in New Issue
Block a user