Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions MANUAL.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5923,6 +5923,32 @@ 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 reader: when you convert
with `-f docx+endnotes`, the endnotes of the docx file will become
notes embedded in a span with class "endnote".

It is also supported by the docx writer: when you convert with
`-t docx+endnotes`, all the notes embedded in a span of class "endnote"
will be endnotes in the docx 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
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 @@ -532,6 +533,7 @@ getAllExtensions f = universalExtensions <> getAll f
[ Ext_raw_markdown ]
getAll "docx" = autoIdExtensions <> extensionsFromList
[ Ext_empty_paragraphs
, Ext_endnotes
, Ext_native_numbering
, Ext_styles
, Ext_citations
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
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
45 changes: 32 additions & 13 deletions src/Text/Pandoc/Writers/Docx/OpenXML.hs
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,11 @@ sectionSeparator = do
Nothing -> pure
Nothing

-- | Convert Pandoc document to rendered document contents plus two lists of
-- OpenXML elements (footnotes and comments).
-- | Convert Pandoc document to rendered document contents plus three lists of
-- OpenXML elements (footnotes, endnotes and comments).
writeOpenXML :: PandocMonad m
=> WriterOptions -> Pandoc
-> WS m (Text, [Element], [Element])
-> WS m (Text, [Element], [Element], [Element])
writeOpenXML opts (Pandoc meta blocks) = do
setupTranslations meta
let includeTOC = writerTableOfContents opts || lookupMetaBool "toc" meta
Expand Down Expand Up @@ -264,6 +264,7 @@ writeOpenXML opts (Pandoc meta blocks) = do
doc' <- setFirstPara >> blocksToOpenXML opts blocks
let body = vcat $ map (literal . showContent) doc'
notes' <- gets (reverse . stFootnotes)
endnotes' <- gets (reverse . stEndnotes)
comments <- gets (reverse . stComments)
let toComment (kvs, ils) = do
annotation <- inlinesToOpenXML opts ils
Expand Down Expand Up @@ -322,7 +323,7 @@ writeOpenXML opts (Pandoc meta blocks) = do
$ metadata
tpl <- maybe (lift $ compileDefaultTemplate "openxml") pure $ writerTemplate opts
let rendered = render Nothing $ renderTemplate tpl context
return (rendered, notes', comments')
return (rendered, notes', endnotes', comments')

-- | Convert a list of Pandoc blocks to OpenXML.
blocksToOpenXML :: (PandocMonad m) => WriterOptions -> [Block] -> WS m [Content]
Expand Down Expand Up @@ -755,6 +756,11 @@ inlineToOpenXML' opts SoftBreak = inlineToOpenXML opts (Str " ")
inlineToOpenXML' opts (Span ("",["mark"],[]) ils) =
withTextProp (mknode "w:highlight" [("w:val","yellow")] ()) $
inlinesToOpenXML opts ils
inlineToOpenXML' opts (Span (_,["endnote"],_) ils@([Note _])) | isEnabled Ext_endnotes opts = do
modify $ \s -> s { stInEndnote = isEnabled Ext_endnotes opts }
endnote <- inlinesToOpenXML opts ils
modify $ \s -> s { stInEndnote = False }
return endnote
inlineToOpenXML' opts (Span ("",["csl-block"],[]) ils) =
inlinesToOpenXML opts ils
inlineToOpenXML' opts (Span ("",["csl-left-margin"],[]) ils) =
Expand Down Expand Up @@ -841,11 +847,13 @@ inlineToOpenXML' opts (Span (ident,classes,kvs) ils) = do
langmod $ inlinesToOpenXML opts ils
wrapBookmark ident contents
inlineToOpenXML' opts (Strong lst) =
withTextProp (mknode "w:bCs" [] ()) $ -- needed for LTR, #6911
withTextProp (mknode "w:bCs" [] ()) $ -- needed for LTR, #6911 -- needed for LTR, #6911
-- needed for LTR, #6911
withTextProp (mknode "w:b" [] ()) $
inlinesToOpenXML opts lst
inlineToOpenXML' opts (Emph lst) =
withTextProp (mknode "w:iCs" [] ()) $ -- needed for LTR, #6911
withTextProp (mknode "w:iCs" [] ()) $ -- needed for LTR, #6911 -- needed for LTR, #6911
-- needed for LTR, #6911
withTextProp (mknode "w:i" [] ()) $
inlinesToOpenXML opts lst
inlineToOpenXML' opts (Underline lst) =
Expand Down Expand Up @@ -905,28 +913,39 @@ inlineToOpenXML' opts (Code attrs str) = do
Skylighting _ -> highlighted
_ -> unhighlighted
inlineToOpenXML' opts (Note bs) = do
notes <- gets stFootnotes
isEndnote <- gets stInEndnote
notes <- gets $ if isEndnote then stEndnotes else stFootnotes
notenum <- getUniqueId
footnoteStyle <- rStyleM "Footnote Reference"
footnoteStyle <- rStyleM $ if isEndnote
then "Endnote Reference"
else "Footnote Reference"
let noteRefNodeName = if isEndnote then "w:endnoteRef" else "w:footnoteRef"
let notemarker = mknode "w:r" []
[ mknode "w:rPr" [] footnoteStyle
, mknode "w:footnoteRef" [] () ]
, mknode noteRefNodeName [] () ]
let notemarkerXml = RawInline (Format "openxml") $ ppElement notemarker
let insertNoteRef (Plain ils : xs) = Plain (notemarkerXml : Space : ils) : xs
insertNoteRef (Para ils : xs) = Para (notemarkerXml : Space : ils) : xs
insertNoteRef xs = Para [notemarkerXml] : xs

let noteTextStyleName = if isEndnote then "Endnote Text" else "Footnote Text"
contents <- local (\env -> env{ envListLevel = -1
, envParaProperties = mempty
, envTextProperties = mempty
, envInNote = True })
(withParaPropM (pStyleM "Footnote Text") $
(withParaPropM (pStyleM noteTextStyleName) $
blocksToOpenXML opts $ insertNoteRef bs)
let newnote = mknode "w:footnote" [("w:id", notenum)] contents
modify $ \s -> s{ stFootnotes = newnote : notes }
let noteNodeName = if isEndnote then "w:endnote" else "w:footnote"
let newnote = mknode noteNodeName [("w:id", notenum)] contents
modify $ \s -> if isEndnote
then s{ stEndnotes = newnote : notes }
else s{ stFootnotes = newnote : notes }
let noteReferenceNodeName = if isEndnote
then "w:endnoteReference"
else "w:footnoteReference"
return [ Elem $ mknode "w:r" []
[ mknode "w:rPr" [] footnoteStyle
, mknode "w:footnoteReference" [("w:id", notenum)] () ] ]
, mknode noteReferenceNodeName [("w:id", notenum)] () ] ]
-- internal link:
inlineToOpenXML' opts (Link _ txt (T.uncons -> Just ('#', xs),_)) = do
contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt
Expand Down
Loading