diff --git a/MANUAL.txt b/MANUAL.txt index 716ffb199a28..537361929ae5 100644 --- a/MANUAL.txt +++ b/MANUAL.txt @@ -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` ### diff --git a/data/docx/word/endnotes.xml b/data/docx/word/endnotes.xml new file mode 100644 index 000000000000..a9bb2c2174c1 --- /dev/null +++ b/data/docx/word/endnotes.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + Endnote Text. + + + + Endnote Block Text + + + diff --git a/data/docx/word/settings.xml b/data/docx/word/settings.xml index 19bd52257d8a..26136d89cb4d 100644 --- a/data/docx/word/settings.xml +++ b/data/docx/word/settings.xml @@ -16,6 +16,10 @@ + + + + diff --git a/pandoc-lua-engine/test/lua/module/pandoc-format.lua b/pandoc-lua-engine/test/lua/module/pandoc-format.lua index 6a1915a163f7..d2a10b94e675 100644 --- a/pandoc-lua-engine/test/lua/module/pandoc-format.lua +++ b/pandoc-lua-engine/test/lua/module/pandoc-format.lua @@ -24,6 +24,7 @@ return { 'citations', 'east_asian_line_breaks', 'empty_paragraphs', + 'endnotes', 'gfm_auto_identifiers', 'native_numbering', 'styles', diff --git a/src/Text/Pandoc/Extensions.hs b/src/Text/Pandoc/Extensions.hs index 7fcb214cdbf4..6ef19648d5c2 100644 --- a/src/Text/Pandoc/Extensions.hs +++ b/src/Text/Pandoc/Extensions.hs @@ -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 @@ -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 diff --git a/src/Text/Pandoc/Readers/Docx.hs b/src/Text/Pandoc/Readers/Docx.hs index da0cca1dc506..152c9584eaf9 100644 --- a/src/Text/Pandoc/Readers/Docx.hs +++ b/src/Text/Pandoc/Readers/Docx.hs @@ -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 @@ -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 diff --git a/src/Text/Pandoc/Readers/ODT.hs b/src/Text/Pandoc/Readers/ODT.hs index e4c2900ff5be..3b94b112e469 100644 --- a/src/Text/Pandoc/Readers/ODT.hs +++ b/src/Text/Pandoc/Readers/ODT.hs @@ -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" @@ -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) diff --git a/src/Text/Pandoc/Readers/ODT/ContentReader.hs b/src/Text/Pandoc/Readers/ODT/ContentReader.hs index 2e192f3e3230..5c951fa1f2fe 100644 --- a/src/Text/Pandoc/Readers/ODT/ContentReader.hs +++ b/src/Text/Pandoc/Readers/ODT/ContentReader.hs @@ -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) @@ -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 @@ -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" diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs index 782833965591..6f6a8f87e9a4 100644 --- a/src/Text/Pandoc/Writers/Docx.hs +++ b/src/Text/Pandoc/Writers/Docx.hs @@ -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 @@ -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') @@ -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) @@ -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 @@ -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" @@ -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", @@ -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) @@ -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 = @@ -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')] () @@ -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") @@ -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), @@ -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 @@ -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 diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs index 5490a732de52..1fc28e7ea32b 100644 --- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs +++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs @@ -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 @@ -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 @@ -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] @@ -755,6 +756,13 @@ 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) = if isEnabled Ext_endnotes opts + then (do + modify $ \s -> s { stInEndnote = isEnabled Ext_endnotes opts } + endnote <- inlinesToOpenXML opts ils + modify $ \s -> s { stInEndnote = False } + return endnote) + else inlinesToOpenXML opts ils inlineToOpenXML' opts (Span ("",["csl-block"],[]) ils) = inlinesToOpenXML opts ils inlineToOpenXML' opts (Span ("",["csl-left-margin"],[]) ils) = @@ -841,11 +849,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) = @@ -905,28 +915,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 diff --git a/src/Text/Pandoc/Writers/Docx/Types.hs b/src/Text/Pandoc/Writers/Docx/Types.hs index febef6603dd3..d985d98a16a0 100644 --- a/src/Text/Pandoc/Writers/Docx/Types.hs +++ b/src/Text/Pandoc/Writers/Docx/Types.hs @@ -111,6 +111,7 @@ defaultWriterEnv = WriterEnv data WriterState = WriterState{ stFootnotes :: [Element] + , stEndnotes :: [Element] , stComments :: [([(Text, Text)], [Inline])] , stSectionIds :: Set.Set Text , stExternalLinks :: M.Map Text Text @@ -126,6 +127,7 @@ data WriterState = WriterState{ -- Should only be used once, for the first paragraph. , stInTable :: Bool , stInList :: Bool + , stInEndnote :: Bool , stTocTitle :: [Inline] , stDynamicParaProps :: Set.Set ParaStyleName , stDynamicTextProps :: Set.Set CharStyleName @@ -137,6 +139,7 @@ data WriterState = WriterState{ defaultWriterState :: WriterState defaultWriterState = WriterState{ stFootnotes = defaultFootnotes + , stEndnotes = defaultEndnotes , stComments = [] , stSectionIds = Set.empty , stExternalLinks = M.empty @@ -151,6 +154,7 @@ defaultWriterState = WriterState{ , stNumIdUsed = False , stInTable = False , stInList = False + , stInEndnote = False , stTocTitle = [Str "Table of Contents"] , stDynamicParaProps = Set.empty , stDynamicTextProps = Set.empty @@ -180,6 +184,21 @@ defaultFootnotes = [ mknode "w:footnote" [ mknode "w:r" [] [ mknode "w:continuationSeparator" [] ()]]]] +-- TODO: verify whether Word behaves the same with endnotes as it does with footnotes. +-- For now, let's do the same as for footnotes +defaultEndnotes :: [Element] +-- defaultEndnotes = [] +defaultEndnotes = [ mknode "w:endnote" + [("w:type", "separator"), ("w:id", "-1")] + [ mknode "w:p" [] + [mknode "w:r" [] + [ mknode "w:separator" [] ()]]] + , mknode "w:endnote" + [("w:type", "continuationSeparator"), ("w:id", "0")] + [ mknode "w:p" [] + [ mknode "w:r" [] + [ mknode "w:continuationSeparator" [] ()]]]] + pStyleM :: (PandocMonad m) => ParaStyleName -> WS m XML.Element pStyleM styleName = do pStyleMap <- gets (smParaStyle . stStyleMaps) diff --git a/src/Text/Pandoc/Writers/OpenDocument.hs b/src/Text/Pandoc/Writers/OpenDocument.hs index 0d49d72e10a4..79feab6e8cdf 100644 --- a/src/Text/Pandoc/Writers/OpenDocument.hs +++ b/src/Text/Pandoc/Writers/OpenDocument.hs @@ -64,6 +64,7 @@ data ReferenceType data WriterState = WriterState { stNotes :: [Doc Text] + , stEndnotes :: [Doc Text] , stTableStyles :: [Doc Text] , stParaStyles :: [Doc Text] , stListStyles :: [(Int, [Doc Text])] @@ -72,6 +73,7 @@ data WriterState = , stTextStyleAttr :: Set.Set TextStyle , stIndentPara :: Int , stInDefinition :: Bool + , stInEndnote :: Bool , stTight :: Bool , stFirstPara :: Bool , stImageId :: Int @@ -83,6 +85,7 @@ data WriterState = defaultWriterState :: WriterState defaultWriterState = WriterState { stNotes = [] + , stEndnotes = [] , stTableStyles = [] , stParaStyles = [] , stListStyles = [] @@ -90,6 +93,7 @@ defaultWriterState = , stTextStyleAttr = Set.empty , stIndentPara = 0 , stInDefinition = False + , stInEndnote = False , stTight = False , stFirstPara = False , stImageId = 1 @@ -636,6 +640,11 @@ inlineToOpenDocument o ils Span ("", ["mark"], []) xs -> inTags False "text:span" [("text:style-name","Highlighted")] <$> inlinesToOpenDocument o xs + Span (_, ["endnote"], _) xs | isEnabled Ext_endnotes o -> do + modify (\st -> st{ stInEndnote = True }) + s <- inlinesToOpenDocument o xs + modify (\st -> st{ stInEndnote = False }) + return s Span attr xs -> mkSpan attr xs LineBreak -> return $ selfClosingTag "text:line-break" [] Str s -> return $ handleSpaces $ escapeStringForXML s @@ -701,15 +710,21 @@ inlineToOpenDocument o ils if T.null ident then i else fmap mkBookmarkedSpan i - mkNote l = do - n <- length <$> gets stNotes - let footNote t = inTags False "text:note" - [ ("text:id" , "ftn" <> tshow n) - , ("text:note-class", "footnote" )] $ + mkNote l = do + inEndnote <- gets stInEndnote + nFootnote <- length <$> gets stNotes + nEndnote <- length <$> gets stEndnotes + let n = if inEndnote then nEndnote else nFootnote + idn = nFootnote + nEndnote + noteClass = if inEndnote then "endnote" else "footnote" + pStyleName = if inEndnote then "Endnote" else "Footnote" + footNote t = inTags False "text:note" + [ ("text:id" , "ftn" <> tshow idn) + , ("text:note-class", noteClass )] $ inTagsSimple "text:note-citation" (text . show $ n + 1) <> inTagsSimple "text:note-body" t nn <- footNote <$> withAlteredTextStyles (const mempty) - (withParagraphStyle o "Footnote" l) + (withParagraphStyle o pStyleName l) addNote nn return nn diff --git a/test/command/11501.md b/test/command/11501.md new file mode 100644 index 000000000000..7ac324c72651 --- /dev/null +++ b/test/command/11501.md @@ -0,0 +1,26 @@ +``` +% pandoc -f markdown -t docx+endnotes -o - | pandoc -f docx+endnotes -t markdown +First paragraph with an endnote[[^e1]]{.endnote} and a footnote[^1]. + +Second paragraph with a footnote[^2] and an endnote [[^e2]]{.endnote}. + +[^1]: First footnote. + +[^2]: Second footnote. + +[^e1]: First endnote. + +[^e2]: Second endnote. +^D +First paragraph with an endnote[[^1]]{.endnote} and a footnote[^2]. + +Second paragraph with a footnote[^3] and an endnote [[^4]]{.endnote}. + +[^1]: First endnote. + +[^2]: First footnote. + +[^3]: Second footnote. + +[^4]: Second endnote. +``` \ No newline at end of file diff --git a/test/command/11516.md b/test/command/11516.md new file mode 100644 index 000000000000..844d15fc12ed --- /dev/null +++ b/test/command/11516.md @@ -0,0 +1,32 @@ +``` +% pandoc -f markdown -t odt+endnotes -o - | pandoc -f odt+endnotes -t markdown +First paragraph with an endnote[[^e1]]{.endnote} and a footnote[^1]. + +Second paragraph with a footnote[^2] and an endnote [[^e2]]{.endnote}. + +[^1]: First footnote. + +[^2]: Second footnote. + +[^e1]: First endnote. + +[^e2]: Second endnote. +^D +First paragraph with an endnote[[^1]]{.endnote} and a footnote[^2]. + +Second paragraph with a footnote[^3] and an endnote [[^4]]{.endnote}. + +[^1]: First endnote. + +[^2]: First footnote. + +[^3]: Second footnote. + +[^4]: Second endnote. +``` +``` +% pandoc -f markdown -t odt+endnotes -o - | pandoc -f odt+endnotes -t markdown --wrap=none +A paragraph with a [span]{.endnote} with an endnote class. +^D +A paragraph with a span with an endnote class. +```