Skip to content
27 changes: 27 additions & 0 deletions MANUAL.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5923,6 +5923,33 @@ 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.

This extension is supported by the docx and odt readers:
when you convert with `-f docx+endnotes` or `-f odt+endnotes`,
the endnotes of the docx or odt file will become
notes embedded in a span with class "endnote".

It is also supported by the docx and odt writers: when you convert
with `-t docx+endnotes` or `-t odt+endnotes`, all the notes embedded
in a span of class "endnote" will be endnotes in the docx or odt file.

## Citation syntax

### Extension: `citations` ###
Expand Down
21 changes: 21 additions & 0 deletions data/docx/word/endnotes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<w:endnotes xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing">
<w:endnote w:type="continuationSeparator" w:id="0"><w:p><w:r><w:continuationSeparator /></w:r></w:p></w:endnote>
<w:endnote w:type="separator" w:id="-1"><w:p><w:r><w:separator /></w:r></w:p></w:endnote>
<w:endnote w:id="32">
<w:p>
<w:pPr><w:pStyle w:val="EndnoteText" /></w:pPr>
<w:r>
<w:rPr>
<w:rStyle w:val="EndnoteReference" />
</w:rPr>
<w:endnoteRef />
</w:r>
<w:r><w:t>Endnote Text.</w:t></w:r>
</w:p>
<w:p>
<w:pPr><w:pStyle w:val="EndnoteBlockText"/></w:pPr>
<w:r><w:t>Endnote Block Text</w:t></w:r>
</w:p>
</w:endnote>
</w:endnotes>
4 changes: 4 additions & 0 deletions data/docx/word/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<w:footnote w:id="-1" />
<w:footnote w:id="0" />
</w:footnotePr>
<w:endnotePr>
<w:endnote w:id="-1" />
<w:endnote w:id="0" />
</w:endnotePr>
<w:rsids>
</w:rsids>
<m:mathPr>
Expand Down
1 change: 1 addition & 0 deletions pandoc-lua-engine/test/lua/module/pandoc-format.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ return {
'citations',
'east_asian_line_breaks',
'empty_paragraphs',
'endnotes',
'gfm_auto_identifiers',
'native_numbering',
'styles',
Expand Down
3 changes: 3 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 @@ -532,12 +533,14 @@ getAllExtensions f = universalExtensions <> getAll f
[ Ext_raw_markdown ]
getAll "docx" = autoIdExtensions <> extensionsFromList
[ Ext_empty_paragraphs
, Ext_endnotes
, Ext_native_numbering
, Ext_styles
, Ext_citations
]
getAll "opendocument" = extensionsFromList
[ Ext_empty_paragraphs
, Ext_endnotes
, Ext_native_numbering
, Ext_xrefs_name
, Ext_xrefs_number
Expand Down
11 changes: 9 additions & 2 deletions src/Text/Pandoc/Readers/Docx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ implemented, [-] means partially implemented):
- [X] Link (links to an arbitrary bookmark create a span with the target as
id and "anchor" class)
- [X] Image
- [X] Note (Footnotes and Endnotes are silently combined.)
- [X] Note (Footnotes and Endnotes are silently combined,
unless Ext_endnotes is enabled: in that case a Note is embedded
in a Span with class "endnote")
-}

module Text.Pandoc.Readers.Docx
Expand Down Expand Up @@ -342,7 +344,12 @@ runToInlines (Run rs runElems)
transform <- runStyleToTransform rPr
return $ transform ils
runToInlines (Footnote bps) = note . smushBlocks <$> mapM bodyPartToBlocks bps
runToInlines (Endnote bps) = note . smushBlocks <$> mapM bodyPartToBlocks bps
runToInlines (Endnote bps) = do
isEndnotesExtEnabled <- asks (isEnabled Ext_endnotes . docxOptions)
noteInlines <- note . smushBlocks <$> mapM bodyPartToBlocks bps
return $ if isEndnotesExtEnabled
then spanWith ("", ["endnote"], []) noteInlines
else noteInlines
runToInlines (InlineDrawing fp title alt bs ext) = do
(lift . lift) $ P.insertMedia fp Nothing bs
return $ imageWith (extentToAttr ext) (T.pack fp) title $ text alt
Expand Down
14 changes: 7 additions & 7 deletions src/Text/Pandoc/Readers/ODT.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,20 @@ makeFigure x = x
readODT' :: ReaderOptions
-> B.ByteString
-> Either PandocError (Pandoc, MediaBag)
readODT' _ bytes = bytesToODT bytes-- of
readODT' opts bytes = bytesToODT opts bytes-- of
-- Right (pandoc, mediaBag) -> Right (pandoc , mediaBag)
-- Left err -> Left err

--
bytesToODT :: B.ByteString -> Either PandocError (Pandoc, MediaBag)
bytesToODT bytes = case toArchiveOrFail bytes of
Right archive -> archiveToODT archive
bytesToODT :: ReaderOptions -> B.ByteString -> Either PandocError (Pandoc, MediaBag)
bytesToODT opts bytes = case toArchiveOrFail bytes of
Right archive -> archiveToODT opts archive
Left err -> Left $ PandocParseError
$ "Could not unzip ODT: " <> T.pack err

--
archiveToODT :: Archive -> Either PandocError (Pandoc, MediaBag)
archiveToODT archive = do
archiveToODT :: ReaderOptions -> Archive -> Either PandocError (Pandoc, MediaBag)
archiveToODT opts archive = do
let onFailure msg Nothing = Left $ PandocParseError msg
onFailure _ (Just x) = Right x
contentEntry <- onFailure "Could not find content.xml"
Expand All @@ -101,7 +101,7 @@ archiveToODT archive = do
let (dir, name) = splitFileName fp
in (dir == "Pictures/") || (dir /= "./" && name == "content.xml")
let media = filteredFilesFromArchive archive filePathIsODTMedia
let startState = readerState styles media
let startState = readerState opts styles media
either (\_ -> Left $ PandocParseError "Could not convert opendocument") Right
(runConverter' read_body startState contentElem)

Expand Down
21 changes: 17 additions & 4 deletions src/Text/Pandoc/Readers/ODT/ContentReader.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Text.Pandoc.Builder hiding (underline)
import Text.Pandoc.MediaBag (MediaBag, insertMedia)
import Text.Pandoc.Shared
import Text.Pandoc.Extensions (extensionsFromList, Extension(..))
import Text.Pandoc.Options (isEnabled, ReaderOptions)
import qualified Text.Pandoc.UTF8 as UTF8

import Text.Pandoc.Readers.Docx.Combine (combineBlocks)
Expand Down Expand Up @@ -96,11 +97,13 @@ data ReaderState
, envMedia :: Media
-- | Hold binary resources used in the document
, odtMediaBag :: MediaBag
-- | Read endnotes as Note inside a Span of class "endnote"
, readEndnotes :: Bool
}
deriving ( Show )

readerState :: Styles -> Media -> ReaderState
readerState styles media = ReaderState styles [] 0 M.empty Nothing M.empty media mempty
readerState :: ReaderOptions -> Styles -> Media -> ReaderState
readerState opts styles media = ReaderState styles [] 0 M.empty Nothing M.empty media mempty (isEnabled Ext_endnotes opts)

--
pushStyle' :: Style -> ReaderState -> ReaderState
Expand Down Expand Up @@ -788,13 +791,23 @@ fixRelativeLink uri =
_ -> uri

-------------------------
-- Footnotes
-- Footnotes and Endnotes
-------------------------

endnote :: Blocks -> Inlines
endnote blocks = spanWith ("", ["endnote"], []) $ note blocks

whichnote :: Bool -> T.Text -> (Blocks -> Inlines)
whichnote True "endnote" = endnote
whichnote _ _ = note

read_note :: InlineMatcher
read_note = matchingElement NsText "note"
$ liftA note
$ liftA3 whichnote readingEndnotes noteClass
$ matchChildContent' [ read_note_body ]
where
noteClass = findAttrTextWithDefault NsText "note-class" ""
readingEndnotes = getExtraState >>^ readEndnotes

read_note_body :: BlockMatcher
read_note_body = matchingElement NsText "note-body"
Expand Down
60 changes: 47 additions & 13 deletions src/Text/Pandoc/Writers/Docx.hs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ writeDocx opts doc = do
}

-- Phase 5: Relationship extraction
(baserels, headers, footers, newMaxRelId) <- extractRelationships refArchive distArchive
(baserels, headers, footers, newMaxRelId) <- extractRelationships opts refArchive distArchive

let initialSt = defaultWriterState {
stStyleMaps = styleMaps
Expand All @@ -123,7 +123,7 @@ writeDocx opts doc = do
[ mknode "w:numRestart" [("w:val","eachSect")] () ]
]

((contents, footnotes, comments), st) <- runStateT
((contents, footnotes, endnotes, comments), st) <- runStateT
(runReaderT
(writeOpenXML opts{ writerWrapText = WrapNone }
doc')
Expand All @@ -137,12 +137,14 @@ writeDocx opts doc = do
-- because Word sometimes changes these files when a reference.docx is modified,
-- e.g. deleting the reference to footnotes.xml or removing default entries
-- for image content types.
let contentTypesEntry = mkContentTypesEntry epochtime imgs headers footers refArchive
let contentTypesEntry = mkContentTypesEntry epochtime imgs headers footers endnotes refArchive
let relEntry = mkDocumentRelsEntry epochtime baserels imgs (stExternalLinks st)
let contentEntry = toEntry "word/document.xml" epochtime
(BL.fromStrict $ UTF8.fromText contents)
let footnotesEntry = mkFootnotesEntry epochtime footnotes
let footnoteRelEntry = mkFootnoteRelsEntry epochtime (stExternalLinks st)
let endnotesEntry = mkEndnotesEntry epochtime endnotes
let endnoteRelEntry = mkEndnoteRelsEntry epochtime (stExternalLinks st)
let commentsEntry = mkCommentsEntry epochtime comments
let styleEntry = mkStylesEntry epochtime styledoc styleMaps st opts
numEntry <- mkNumberingEntry refArchive distArchive epochtime (stLists st)
Expand All @@ -165,10 +167,12 @@ writeDocx opts doc = do
let archive = foldr addEntryToArchive emptyArchive $
contentTypesEntry : relsEntry : contentEntry : relEntry :
footnoteRelEntry : numEntry : styleEntry : footnotesEntry :
commentsEntry :
docPropsEntry : customPropsEntry :
settingsEntry :
imageEntries ++ refEntries
commentsEntry : docPropsEntry : customPropsEntry : settingsEntry :
imageEntries
++ refEntries
++ if (isEnabled Ext_endnotes opts)
then [endnoteRelEntry, endnotesEntry]
else []
return $ fromArchive archive

newParaPropToOpenXml :: ParaStyleName -> Element
Expand Down Expand Up @@ -525,9 +529,9 @@ extractPageLayout refArchive distArchive = do

-- | Parse and augment relationships from reference.docx
extractRelationships :: PandocMonad m
=> Archive -> Archive
=> WriterOptions -> Archive -> Archive
-> m ([Element], [Element], [Element], Int)
extractRelationships refArchive distArchive = do
extractRelationships opts refArchive distArchive = do
let isImageNode e = findAttr (QName "Type" Nothing Nothing) e == Just "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
let isHeaderNode e = findAttr (QName "Type" Nothing Nothing) e == Just "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
let isFooterNode e = findAttr (QName "Type" Nothing Nothing) e == Just "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer"
Expand All @@ -554,7 +558,7 @@ extractRelationships refArchive distArchive = do
,("Target",target')] () : rels)
_ -> (maxId, rels)

let (newMaxRelId, baserels) = foldr addBaseRel (maxRelId, parsedRels)
let (newMaxRelId, baserels) = foldr addBaseRel (maxRelId, parsedRels) $
[("http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering",
"numbering.xml")
,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
Expand All @@ -571,7 +575,8 @@ extractRelationships refArchive distArchive = do
"footnotes.xml")
,("http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
"comments.xml")
]
] ++ [("http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes",
"endnotes.xml") | isEnabled Ext_endnotes opts]

return (baserels, headers, footers, newMaxRelId)

Expand All @@ -595,6 +600,26 @@ mkFootnoteRelsEntry epochtime externalLinks =
[("xmlns","http://schemas.openxmlformats.org/package/2006/relationships")]
linkrels

-- | Create endnotes XML entry
mkEndnotesEntry :: Integer -> [Element] -> Entry
mkEndnotesEntry epochtime endnotes =
let notes = mknode "w:endnotes" stdAttributes endnotes
in toEntry "word/endnotes.xml" epochtime $ renderXml notes

-- | Create endnote relationships entry
mkEndnoteRelsEntry :: Integer -> M.Map Text Text -> Entry
mkEndnoteRelsEntry epochtime externalLinks =
let linkrels = map toLinkRel $ M.toList externalLinks
toLinkRel (src, ident) = mknode "Relationship"
[("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink")
,("Id",ident)
,("Target",src)
,("TargetMode","External")] ()
in toEntry "word/_rels/endnotes.xml.rels" epochtime
$ renderXml $ mknode "Relationships"
[("xmlns","http://schemas.openxmlformats.org/package/2006/relationships")]
linkrels

-- | Create comments XML entry
mkCommentsEntry :: Integer -> [Element] -> Entry
mkCommentsEntry epochtime comments =
Expand Down Expand Up @@ -627,9 +652,10 @@ mkContentTypesEntry :: Integer
-> [(String, String, Maybe MimeType, B.ByteString)] -- imgs
-> [Element] -- headers
-> [Element] -- footers
-> [Element] -- endnotes
-> Archive -- refArchive
-> Entry
mkContentTypesEntry epochtime imgs headers footers refArchive =
mkContentTypesEntry epochtime imgs headers footers endnotes refArchive =
let mkOverrideNode (part', contentType') = mknode "Override"
[("PartName", T.pack part')
,("ContentType", contentType')] ()
Expand All @@ -638,6 +664,11 @@ mkContentTypesEntry epochtime imgs headers footers refArchive =
fromMaybe "application/octet-stream" mbMimeType)
mkMediaOverride imgpath =
mkOverrideNode ("/" <> imgpath, getMimeTypeDef imgpath)
endnotesOverride = if null endnotes
then [("/word/endnotes.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml")]
else []

overrides = map mkOverrideNode (
[("/word/webSettings.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml")
Expand All @@ -664,6 +695,7 @@ mkContentTypesEntry epochtime imgs headers footers refArchive =
,("/word/footnotes.xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml")
] ++
endnotesOverride ++
map (\x -> (maybe "" (T.unpack . ("/word/" <>)) (extractTarget x),
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml")) headers ++
map (\x -> (maybe "" (T.unpack . ("/word/" <>)) (extractTarget x),
Expand Down Expand Up @@ -723,6 +755,7 @@ mkStylesEntry epochtime styledoc styleMaps st opts =
(\sty -> not $ hasStyleName sty $ smCharStyle styleMaps)
(Set.toList $ stDynamicTextProps st)

-- TODO: add styles for endnotes, when Ext_endnotes is enabled
newstyles = map newParaPropToOpenXml newDynamicParaProps ++
map newTextPropToOpenXml newDynamicTextProps ++
(case writerHighlightMethod opts of
Expand Down Expand Up @@ -836,7 +869,8 @@ collectReferenceEntries refArchive distArchive headers footers = do
, "word/_rels/" `isPrefixOf` eRelativePath e
, ".xml.rels" `isSuffixOf` eRelativePath e
, eRelativePath e /= "word/_rels/document.xml.rels"
, eRelativePath e /= "word/_rels/footnotes.xml.rels" ]
, eRelativePath e /= "word/_rels/footnotes.xml.rels"
, eRelativePath e /= "word/_rels/endnotes.xml.rels" ]
let otherMediaEntries = [ e | e <- zEntries refArchive
, "word/media/" `isPrefixOf` eRelativePath e ]
return $ docPropsAppEntry : themeEntry : fontTableEntry : webSettingsEntry
Expand Down
Loading