From d4dd5e9ab945fca7b3e6bcc97993381a12eb39ab Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Wed, 15 Oct 2025 23:23:45 +0200 Subject: [PATCH] Docx writer: properly handle nested comment spans. Patch credit: @mmourino. Closes #8189. Closes #6959. --- src/Text/Pandoc/Writers/Docx/OpenXML.hs | 19 +++++++++++-------- test/command/6959.md | 12 ++++++++++++ test/docx/golden/comments.docx | Bin 10841 -> 10824 bytes 3 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 test/command/6959.md diff --git a/src/Text/Pandoc/Writers/Docx/OpenXML.hs b/src/Text/Pandoc/Writers/Docx/OpenXML.hs index c9c1bc80f..f687a3493 100644 --- a/src/Text/Pandoc/Writers/Docx/OpenXML.hs +++ b/src/Text/Pandoc/Writers/Docx/OpenXML.hs @@ -773,17 +773,20 @@ inlineToOpenXML' _ (Span (ident,["comment-start"],kvs) ils) = do kvs' = filter (("id" /=) . fst) kvs modify $ \st -> st{ stComments = (("id",ident'):kvs', ils) : stComments st } return [ Elem $ mknode "w:commentRangeStart" [("w:id", ident')] () ] -inlineToOpenXML' _ (Span (ident,["comment-end"],kvs) _) = +inlineToOpenXML' opts (Span (ident,["comment-end"],kvs) content) = do -- prefer the "id" in kvs, since that is the one produced by the docx -- reader. let ident' = fromMaybe ident (lookup "id" kvs) - in return . map Elem $ - [ mknode "w:commentRangeEnd" [("w:id", ident')] () - , mknode "w:r" [] - [ mknode "w:rPr" [] - [ mknode "w:rStyle" [("w:val", "CommentReference")] () ] - , mknode "w:commentReference" [("w:id", ident')] () ] - ] + -- process nested content: see #8189 + nestedContent <- inlinesToOpenXML opts content + let thisCommentEnd = + [ mknode "w:commentRangeEnd" [("w:id", ident')] () + , mknode "w:r" [] + [ mknode "w:rPr" [] + [ mknode "w:rStyle" [("w:val", "CommentReference")] () ] + , mknode "w:commentReference" [("w:id", ident')] () ] + ] + return $ map Elem thisCommentEnd ++ nestedContent inlineToOpenXML' opts (Span (ident,classes,kvs) ils) = do stylemod <- case lookup dynamicStyleKey kvs of Just (fromString . T.unpack -> sty) -> do diff --git a/test/command/6959.md b/test/command/6959.md new file mode 100644 index 000000000..b09a189f5 --- /dev/null +++ b/test/command/6959.md @@ -0,0 +1,12 @@ +``` +% pandoc -t docx | pandoc -f docx -t markdown --track-changes=all +[This is the comment]{.comment-start id="1" author="Mike" +date="2020-12-17T16:53:00Z"} [Here is my reply]{.comment-start id="2" +author="Mike" date="2020-12-17T17:39:00Z"} This is the content being +commented on [[]{.comment-end id="2"}]{.comment-end id="1"} +^D +[This is the comment]{.comment-start id="1" author="Mike" +date="2020-12-17T16:53:00Z"} [Here is my reply]{.comment-start id="2" +author="Mike" date="2020-12-17T17:39:00Z"} This is the content being +commented on [[]{.comment-end id="2"}]{.comment-end id="1"} +``` diff --git a/test/docx/golden/comments.docx b/test/docx/golden/comments.docx index 57dbde327fe46f101d8c2bdafa9ec0062ad4a08e..2fe0f22bfb72e3fd998f7fe0955f6df4767a38f3 100644 GIT binary patch delta 4459 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 HpS}JAI@E|S delta 4444 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