Skip to content
63 changes: 63 additions & 0 deletions MANUAL.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,16 @@ header when requesting a document from a URL:
and Haddock output. This is useful for preventing duplicate
identifiers when generating fragments to be included in other pages.

`--endnotes-prefix=`*STRING*

: It's used with Markdown, only when the `endnotes` extension is enabled.
The Markdown reader uses it to discriminate between footnotes (normal
notes) and endnotes, which internally are coded as notes embedded in a
`Span` with the class "endnote".
It's also used by the Markdown writer as a prefix for all the endnotes'
references.
Its default value is "EN".

`-T` *STRING*, `--title-prefix=`*STRING*

: Specify *STRING* as a prefix at the beginning of the title
Expand Down Expand Up @@ -5923,6 +5933,59 @@ they cannot contain multiple paragraphs). The syntax is as follows:

Inline and regular footnotes may be mixed freely.

## Endnotes

You can use the following convention to specify endnotes: surround a
footnote with a `Span` of class "endnote", like this:

Here' and endnote[[^1]]{.endnote}.

[^1]: This is the endnote text.

When a reader or a writer does not know about this convention, those
notes are just regular footnotes, just with a transparent `Span` wrapper
around them.

### Extension: `endnotes` ###

Enabling this extension tells some readers and writers to use that
convention to distinguish endnotes from footnotes.

The markdown reader lets you specify endnotes in a lighter way:

Here' and endnote[^EN1].

[^EN1]: This is the endnote text.

because "EN" is the default prefix for endnotes; when the reader meets
a note reference starting with "EN", it wraps the note in a `Span` with
the "endnote" class.
That prefix can be set with the `--endnotes-prefix` option.

The markdown writer prepends the endnotes' references with that prefix.
Moreover, it lists all the footnotes' texts followed by the endnotes'
texts at the end of the document:

A document with an endnote[[^1]]{.endnote}, a footnote[^2],
and another endnote[[^3]]{.endnote}.

[^1]: First endnote.

[^2]: First footnote.

[^3]: Second endnote.

When you convert it with `-t markdown+endnotes` it becomes:

A document with an endnote[^EN1], a footnote[^1],
and another endnote[^EN2].

[^1]: First footnote.

[^EN1]: First endnote.

[^EN2]: Second endnote.

## Citation syntax

### Extension: `citations` ###
Expand Down
2 changes: 2 additions & 0 deletions pandoc-server/src/Text/Pandoc/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ server = convertBytes
optDefaultImageExtension opts
, readerTrackChanges = optTrackChanges opts
, readerStripComments = optStripComments opts
, readerEndnotesPrefix = optEndnotesPrefix opts
}

let writeropts = WriterOptions
Expand All @@ -326,6 +327,7 @@ server = convertBytes
, writerDpi = optDpi opts
, writerEmailObfuscation = optEmailObfuscation opts
, writerIdentifierPrefix = optIdentifierPrefix opts
, writerEndnotesPrefix = optEndnotesPrefix opts
, writerCiteMethod = optCiteMethod opts
, writerHtmlQTags = optHtmlQTags opts
, writerSlideLevel = optSlideLevel opts
Expand Down
1 change: 1 addition & 0 deletions src/Text/Pandoc/App.hs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ convertWithOpts' scriptingEngine istty datadir opts = do
, readerAbbreviations = abbrevs
, readerExtensions = readerExts
, readerStripComments = optStripComments opts
, readerEndnotesPrefix = optEndnotesPrefix opts
}

metadataFromFile <- getMetadataFromFiles readerNameBase readerOpts
Expand Down
12 changes: 9 additions & 3 deletions src/Text/Pandoc/App/CommandLineOptions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -882,12 +882,18 @@ options =
"none|javascript|references")
"" -- "Method for obfuscating email in HTML"

, Option "" ["id-prefix"]
(ReqArg
(\arg opt -> return opt { optIdentifierPrefix = T.pack arg })
, Option "" ["id-prefix"]
(ReqArg
(\arg opt -> return opt { optIdentifierPrefix = T.pack arg })
"STRING")
"" -- "Prefix to add to automatically generated HTML identifiers"

, Option "" ["endnotes-prefix"]
(ReqArg
(\arg opt -> return opt { optEndnotesPrefix = T.pack arg })
"STRING")
"" -- "Prefix to add to endnotes or to be used to discriminate between footnote and endnote refs (only with endnotes extension)"

, Option "T" ["title-prefix"]
(ReqArg
(\arg opt ->
Expand Down
7 changes: 7 additions & 0 deletions src/Text/Pandoc/App/Opt.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import Text.Pandoc.Options (TopLevelDivision (TopLevelDefault),
CaptionPosition (..),
ObfuscationMethod (NoObfuscation),
CiteMethod (Citeproc),
defaultEndnotesPrefix,
pattern DefaultHighlightingString)
import Text.Pandoc.Class (readFileStrict, fileExists, setVerbosity, report,
PandocMonad(lookupEnv), getUserDataDir)
Expand Down Expand Up @@ -156,6 +157,7 @@ data Opt = Opt
, optFilters :: [Filter] -- ^ Filters to apply
, optEmailObfuscation :: ObfuscationMethod
, optIdentifierPrefix :: Text
, optEndnotesPrefix :: Text -- ^ With endnotes extension, the endnotes refs' prefix
, optIndentedCodeClasses :: [Text] -- ^ Default classes for indented code blocks
, optDataDir :: Maybe FilePath
, optCiteMethod :: CiteMethod -- ^ Method to output cites
Expand Down Expand Up @@ -243,6 +245,7 @@ instance FromJSON Opt where
<*> o .:? "filters" .!= optFilters defaultOpts
<*> o .:? "email-obfuscation" .!= optEmailObfuscation defaultOpts
<*> o .:? "identifier-prefix" .!= optIdentifierPrefix defaultOpts
<*> o .:? "endnotes-prefix" .!= optEndnotesPrefix defaultOpts
<*> o .:? "indented-code-classes" .!= optIndentedCodeClasses defaultOpts
<*> o .:? "data-dir"
<*> o .:? "cite-method" .!= optCiteMethod defaultOpts
Expand Down Expand Up @@ -653,6 +656,9 @@ doOpt (k,v) = do
"identifier-prefix" ->
parseJSON v >>= \x ->
return (\o -> o{ optIdentifierPrefix = x })
"endnotes-prefix" ->
parseJSON v >>= \x ->
return (\o -> o{ optEndnotesPrefix = x })
"indented-code-classes" ->
parseJSON v >>= \x ->
return (\o -> o{ optIndentedCodeClasses = x })
Expand Down Expand Up @@ -820,6 +826,7 @@ defaultOpts = Opt
, optFilters = []
, optEmailObfuscation = NoObfuscation
, optIdentifierPrefix = ""
, optEndnotesPrefix = defaultEndnotesPrefix
, optIndentedCodeClasses = []
, optDataDir = Nothing
, optCiteMethod = Citeproc
Expand Down
1 change: 1 addition & 0 deletions src/Text/Pandoc/App/OutputSettings.hs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ optToOutputSettings scriptingEngine opts = do
, writerColumns = optColumns opts
, writerEmailObfuscation = optEmailObfuscation opts
, writerIdentifierPrefix = optIdentifierPrefix opts
, writerEndnotesPrefix = optEndnotesPrefix opts
, writerHtmlQTags = optHtmlQTags opts
, writerTopLevelDivision = optTopLevelDivision opts
, writerSlideLevel = optSlideLevel opts
Expand Down
2 changes: 2 additions & 0 deletions src/Text/Pandoc/Extensions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ data Extension =
| Ext_element_citations -- ^ Use element-citation elements for JATS citations
| Ext_emoji -- ^ Support emoji like :smile:
| Ext_empty_paragraphs -- ^ Allow empty paragraphs
| Ext_endnotes -- ^ Endnotes support when footnotes are embedded in a Span.endnote
| Ext_epub_html_exts -- ^ Recognise the EPUB extended version of HTML
| Ext_escaped_line_breaks -- ^ Treat a backslash at EOL as linebreak
| Ext_example_lists -- ^ Markdown-style numbered examples
Expand Down Expand Up @@ -505,6 +506,7 @@ getAllExtensions f = universalExtensions <> getAll f
, Ext_ignore_line_breaks
, Ext_east_asian_line_breaks
, Ext_emoji
, Ext_endnotes
, Ext_tex_math_single_backslash
, Ext_tex_math_double_backslash
, Ext_markdown_attribute
Expand Down
8 changes: 8 additions & 0 deletions src/Text/Pandoc/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module Text.Pandoc.Options ( module Text.Pandoc.Extensions
, CaptionPosition (..)
, def
, isEnabled
, defaultEndnotesPrefix
, defaultMathJaxURL
, defaultWebTeXURL
, defaultKaTeXURL
Expand Down Expand Up @@ -74,11 +75,15 @@ data ReaderOptions = ReaderOptions{
, readerTrackChanges :: TrackChanges -- ^ Track changes setting for docx
, readerStripComments :: Bool -- ^ Strip HTML comments instead of parsing as raw HTML
-- (only implemented in commonmark)
, readerEndnotesPrefix :: Text -- ^ Endnotes' prefix when endnotes extension is enabled
} deriving (Show, Read, Data, Typeable, Generic)

instance HasSyntaxExtensions ReaderOptions where
getExtensions opts = readerExtensions opts

defaultEndnotesPrefix :: Text
defaultEndnotesPrefix = "EN"

instance Default ReaderOptions
where def = ReaderOptions{
readerExtensions = emptyExtensions
Expand All @@ -90,6 +95,7 @@ instance Default ReaderOptions
, readerDefaultImageExtension = ""
, readerTrackChanges = AcceptChanges
, readerStripComments = False
, readerEndnotesPrefix = defaultEndnotesPrefix
}

defaultAbbrevs :: Set.Set Text
Expand Down Expand Up @@ -390,6 +396,7 @@ data WriterOptions = WriterOptions
, writerSyntaxMap :: SyntaxMap
, writerPreferAscii :: Bool -- ^ Prefer ASCII representations of characters when possible
, writerLinkImages :: Bool -- ^ Use links rather than embedding ODT images
, writerEndnotesPrefix :: Text -- ^ Prefix for endnotes refs when endnotes extension is enabled
} deriving (Show, Data, Typeable, Generic)

instance Default WriterOptions where
Expand Down Expand Up @@ -432,6 +439,7 @@ instance Default WriterOptions where
, writerSyntaxMap = defaultSyntaxMap
, writerPreferAscii = False
, writerLinkImages = False
, writerEndnotesPrefix = defaultEndnotesPrefix
}

instance HasSyntaxExtensions WriterOptions where
Expand Down
6 changes: 5 additions & 1 deletion src/Text/Pandoc/Readers/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2082,7 +2082,11 @@ note = try $ do
let adjustCite (Cite cs ils) =
Cite (map addCitationNoteNum cs) ils
adjustCite x = x
return $ B.note $ walk adjustCite contents'
let opts = stateOptions st
return $ if isEnabled Ext_endnotes opts
&& readerEndnotesPrefix opts `T.isPrefixOf` ref
then B.spanWith ("", ["endnote"], []) $ B.note $ walk adjustCite contents'
else B.note $ walk adjustCite contents'

inlineNote :: PandocMonad m => MarkdownParser m (F Inlines)
inlineNote = do
Expand Down
24 changes: 18 additions & 6 deletions src/Text/Pandoc/Writers/Markdown.hs
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,24 @@ keyToMarkdown opts (label', (src, tit), attr) = do
notesToMarkdown :: PandocMonad m => WriterOptions -> [[Block]] -> MD m (Doc Text)
notesToMarkdown opts notes = do
n <- gets stNoteNum
notes' <- zipWithM (noteToMarkdown opts) [n..] notes
notes' <- zipWithM (noteToMarkdown opts "") [n..] notes
modify $ \st -> st { stNoteNum = stNoteNum st + length notes }
return $ vsep notes'

-- | Return markdown representation of endnotes when Ext_endnotes is enabled.
endnotesToMarkdown :: PandocMonad m => WriterOptions -> [[Block]] -> MD m (Doc Text)
endnotesToMarkdown opts notes = do
n <- gets stEndnoteNum
let prefix = writerEndnotesPrefix opts
notes' <- zipWithM (noteToMarkdown opts prefix) [n..] notes
modify $ \st -> st { stEndnoteNum = stEndnoteNum st + length notes }
return $ vsep notes'

-- | Return markdown representation of a note.
noteToMarkdown :: PandocMonad m => WriterOptions -> Int -> [Block] -> MD m (Doc Text)
noteToMarkdown opts num blocks = do
noteToMarkdown :: PandocMonad m => WriterOptions -> Text -> Int -> [Block] -> MD m (Doc Text)
noteToMarkdown opts prefix num blocks = do
contents <- blockListToMarkdown opts blocks
let num' = literal $ writerIdentifierPrefix opts <> tshow num
let num' = literal $ prefix <> writerIdentifierPrefix opts <> tshow num
let marker = if isEnabled Ext_footnotes opts
then literal "[^" <> num' <> literal "]:"
else literal "[" <> num' <> literal "]"
Expand Down Expand Up @@ -337,17 +346,20 @@ notesAndRefs :: PandocMonad m => WriterOptions -> MD m (Doc Text)
notesAndRefs opts = do
notes' <- gets stNotes >>= notesToMarkdown opts . reverse
modify $ \s -> s { stNotes = [] }
notes'' <- gets stEndnotes >>= endnotesToMarkdown opts . reverse
modify $ \s -> s { stEndnotes = [] }
let allNotes = notes' <> (if isEmpty notes' then empty else blankline) <> notes''
refs' <- gets stRefs >>= refsToMarkdown opts . reverse
modify $ \s -> s { stPrevRefs = stPrevRefs s ++ stRefs s
, stRefs = []}

let endSpacing =
if | writerReferenceLocation opts == EndOfDocument -> empty
| isEmpty notes' && isEmpty refs' -> empty
| isEmpty allNotes && isEmpty refs' -> empty
| otherwise -> blankline

return $
(if isEmpty notes' then empty else blankline <> notes') <>
(if isEmpty allNotes then empty else blankline <> allNotes) <>
(if isEmpty refs' then empty else blankline <> refs') <>
endSpacing

Expand Down
36 changes: 28 additions & 8 deletions src/Text/Pandoc/Writers/Markdown/Inline.hs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,16 @@ avoidBadWraps inListItem = go . toList
toList (Concat a b) = a : toList b
toList x = [x]

-- check if the Attr of a Span makes the embedded Note an endnote
isEndnoteSpan :: WriterOptions -> Attr -> Bool
isEndnoteSpan opts (_, ["endnote"], _) = isEnabled Ext_endnotes opts
isEndnoteSpan _ _ = False

attrWithoutEndnoteClass :: WriterOptions -> Attr -> [Inline] -> Attr
attrWithoutEndnoteClass opts attr ils = case (attr, isEnabled Ext_endnotes opts, ils) of
((ident, ["endnote"], attributes), True, [Note _]) -> (ident, [], attributes)
_ -> attr

-- | Convert Pandoc inline element to markdown.
inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
Expand All @@ -347,8 +357,10 @@ inlineToMarkdown opts (Span ("",["mark"],[]) ils)
= do contents <- inlineListToMarkdown opts ils
return $ "==" <> contents <> "=="
inlineToMarkdown opts (Span attrs ils) = do
modify (\s -> s {stInEndnote = isEndnoteSpan opts attrs})
variant <- asks envVariant
contents <- inlineListToMarkdown opts ils
modify (\s -> s {stInEndnote = False})
return $ case attrs of
(_,["csl-block"],_) -> (cr <>)
(_,["csl-left-margin"],_) -> (cr <>)
Expand All @@ -357,12 +369,13 @@ inlineToMarkdown opts (Span attrs ils) = do
$ case variant of
PlainText -> contents
Markua -> "`" <> contents <> "`" <> attrsToMarkua opts attrs
_ | attrs == nullAttr -> contents
_ | nullAttr == attrWithoutEndnoteClass opts attrs ils -> contents
| isEnabled Ext_bracketed_spans opts ->
let attrs' = if attrs /= nullAttr
then attrsToMarkdown opts attrs
let attrs' = attrWithoutEndnoteClass opts attrs ils
attrs'' = if attrs' /= nullAttr
then attrsToMarkdown opts attrs'
else empty
in "[" <> contents <> "]" <> attrs'
in "[" <> contents <> "]" <> attrs''
| isEnabled Ext_raw_html opts ||
isEnabled Ext_native_spans opts ->
tagWithAttrs "span" attrs <> contents <> literal "</span>"
Expand Down Expand Up @@ -723,12 +736,19 @@ inlineToMarkdown opts img@(Image attr alternate (source, tit))
literal source <> ")" <> cr
_ -> "!" <> linkPart
inlineToMarkdown opts (Note contents) = do
modify (\st -> st{ stNotes = contents : stNotes st })
inEndnote <- stInEndnote <$> get
if inEndnote
then modify (\st -> st{ stEndnotes = contents : stEndnotes st })
else modify (\st -> st{ stNotes = contents : stNotes st })
st <- get
let ref = literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
let ref = if inEndnote
then literal $ writerEndnotesPrefix opts
<> writerIdentifierPrefix opts
<> tshow (stEndnoteNum st + length (stEndnotes st) - 1)
else literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
if isEnabled Ext_footnotes opts
then return $ "[^" <> ref <> "]"
else return $ "[" <> ref <> "]"
then return $ "[^" <> ref <> "]"
else return $ "[" <> ref <> "]"

makeMathPlainer :: [Inline] -> [Inline]
makeMathPlainer = walk go
Expand Down
Loading