From d333c283cc7149462491e97e95d3ccfaefab84c2 Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Tue, 20 Nov 2018 23:43:21 -0500 Subject: [PATCH] Docx writer: Fix bookmarks to headers with long titles. Word has a 40 character limit for bookmark names. In addition, bookmarks must begin with a letter. Since pandoc's auto-generated identifiers may not respect these constraints, some internal links did not work. With this change, pandoc uses a bookmark name based on the SHA1 hash of the identifier when the identifier isn't a legal bookmark name. Closes #5091. --- Makefile | 2 +- src/Text/Pandoc/Writers/Docx.hs | 22 ++++++++++++++---- .../docx/golden/nested_anchors_in_header.docx | Bin 10251 -> 10196 bytes 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index eab8bb291..7006956a9 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ version?=$(shell grep '^[Vv]ersion:' pandoc.cabal | awk '{print $$2;}') pandoc=$(shell find dist -name pandoc -type f -exec ls -t {} \; | head -1) SOURCEFILES?=$(shell find pandoc.hs src test -name '*.hs') BRANCH?=master -RESOLVER=lts-12 +RESOLVER?=lts-12 GHCOPTS=-fdiagnostics-color=always -Wall -fno-warn-unused-do-bind -Wincomplete-record-updates -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wincomplete-uni-patterns -Werror=missing-home-modules -Widentities -Wcpp-undef -fhide-source-paths -j +RTS -A32M -RTS # Later: # -Wpartial-fields (currently used in Powerpoint writer) diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs index a441aeec7..c2a160320 100644 --- a/src/Text/Pandoc/Writers/Docx.hs +++ b/src/Text/Pandoc/Writers/Docx.hs @@ -41,19 +41,21 @@ import Control.Monad.Reader import Control.Monad.State.Strict import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL -import Data.Char (isSpace, ord, toLower) +import Data.Char (isSpace, ord, toLower, isLetter) import Data.List (intercalate, isPrefixOf, isSuffixOf) import qualified Data.Map as M import Data.Maybe (fromMaybe, isNothing, mapMaybe, maybeToList) import qualified Data.Set as Set import qualified Data.Text as T import Data.Time.Clock.POSIX +import Data.Digest.Pure.SHA (sha1, showDigest) import Skylighting import System.Random (randomR, StdGen, mkStdGen) import Text.Pandoc.BCP47 (getLang, renderLang) import Text.Pandoc.Class (PandocMonad, report, toLang) import qualified Text.Pandoc.Class as P import Data.Time +import Text.Pandoc.UTF8 (fromStringLazy) import Text.Pandoc.Definition import Text.Pandoc.Generic import Text.Pandoc.Highlighting (highlight) @@ -1268,7 +1270,8 @@ inlineToOpenXML' opts (Note bs) = do -- internal link: inlineToOpenXML' opts (Link _ txt ('#':xs,_)) = do contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt - return [ mknode "w:hyperlink" [("w:anchor",xs)] contents ] + return + [ mknode "w:hyperlink" [("w:anchor", toBookmarkName xs)] contents ] -- external link: inlineToOpenXML' opts (Link _ txt (src,_)) = do contents <- withTextPropM (rStyleM "Hyperlink") $ inlinesToOpenXML opts txt @@ -1427,7 +1430,18 @@ wrapBookmark :: (PandocMonad m) => String -> [Element] -> WS m [Element] wrapBookmark [] contents = return contents wrapBookmark ident contents = do id' <- getUniqueId - let bookmarkStart = mknode "w:bookmarkStart" [("w:id", id') - ,("w:name",ident)] () + let bookmarkStart = mknode "w:bookmarkStart" + [("w:id", id') + ,("w:name", toBookmarkName ident)] () bookmarkEnd = mknode "w:bookmarkEnd" [("w:id", id')] () return $ bookmarkStart : contents ++ [bookmarkEnd] + +-- Word imposes a 40 character limit on bookmark names and requires +-- that they begin with a letter. So we just use a hash of the +-- identifer when otherwise we'd have an illegal bookmark name. +toBookmarkName :: String -> String +toBookmarkName s = + case s of + (c:_) | isLetter c + , length s <= 40 -> s + _ -> 'X' : drop 1 (showDigest (sha1 (fromStringLazy s))) diff --git a/test/docx/golden/nested_anchors_in_header.docx b/test/docx/golden/nested_anchors_in_header.docx index 20f83b3caf860c9f9de77238f60f8761a17f90d1..56f59584ac98dbbe0fd6689d426375ed566da69a 100644 GIT binary patch delta 1872 zcmZWp3pkT|82>)TjI^<#jExzya+nk;l@qydNiCNol>6qC>#$5|R8H;ip^K)AoYiQq zN8w01MKThRTS-Z|B}%D|Iy2{~hrZ|ep8x;JdKt@3U zn8ZbwD4z#qC2Ls`hys^NHq@^*N=0HB06-w2W*H4mhx?Q8917-U|8|c)0W9OKz#ywa z)N}VT-I+ArxeQ(CkL{)f`vg9qFoml_dU6lhpVe*6D5nOWD({u6K)qr&^jp4Kuiij% zUoEF(L>yo+JnRq3`lhgKyBG|1)!Nax`o_VI12|i)W^$lnFx()S%^*4j913yD^i_PR z@_<8W@Ao%0-0_(%7N1KqL~Hqx*Wjqs>i$?4MTA-CvL3mV2Gd_>$~RieUdoWSwNkTv zhFL-NHXuUEns{tv-C<3kJwnxc%3~TOs8h^;s>!=+&Xc2JtNTV`Zy7_KtXsxn0{9Nz zmZS0>1{=>GdY;1!+95C*!#nXjcCu#1ll{xh;pwUw`oD&vl}D|v{9Dke zyiY#eyakr+VN`5NzS$SOGEcbTgV*47qtLt8kk|Q5%T?U?HeirUV55BAzB7L59Mzbs z*K}Ju|nw^1>=wI=BO2yB{dA z^Q-p1z~D2^rf_rv&!;BB#_vV*ySOdIJi_DQFS!$+S%dLDW1iXgO9T6Bk8JY4uO@H0 zRnG{}(0R$I_>^=y6Flk!a2ef+9P8s*ZDqACn#U-H0oS=%riZhuh4kN+r6dtx^62GzA;ve1V5_o;_1 z@F%0zM%mre>QU3RQ5%GH2ROSXN>=={lU^m9`BdQ6&#o?5 zWfeu=X%pn^NwgdBew0+eLB0D>6f*^1VE?i#xs6i)L!I&hX&)rP+vXdBjxbk{Moy~O z&qumc;UHqJMX=FUcpUT^jpH{b*5Lz_(+#-BU zuVFqL4^vgx^1Qxz^vtYPShdd>HD`A7(VlKgzb%fdirdIci^!=GuOYM2U|i^MWfb`I z@H>>Njo9t{3GdIA-Gq2rNaijILA}urx)D>S;1bZ~YBES;B+>3qrE7AmFTxb#VMB?; z476LL$}nehuWaV!*27?0dgiQ#O#&I3Keda^;s{+lbXANL*qUF%vz$;%JoYyYGb~6)>?VN-%TWJw@udAk)82Y z_oL^h!rWYU=QbB7^QPrZD@hFcPWHN&9q+%e#Pn}+!EHIBY3(~^z7FhJ?Ac8VOHEPZ z%}u9DYlkVlo8NQK(8NH3t)skqwcz7Npno2*F5V2q})J$9{J$;d! zCLs-dBhH+W+{uE+`5*vnLji!Qq#{3_95?{P{uZ&%Bs`kw9}p1|9%Rg7GF+WO$z=XM zys0MqT;C-M08mT=0OSv_WVavjKRz-lju8;~Ggv#C3JA80XiC^!WEZnvh5Ge%@pB55 zR#`3WmE0F0;Z<3{@D9Qo=;(ZJ2gyASKm9X{h9=Qy>)(ISOZuGWs0a}q5Lk{7c;(;xxrCmG0>>di&}0}@T;*8l(j delta 1723 zcmZux3pmqj7~jS=v}!KpPHcv-W-gD1lH@YiQAbiyv2t%14&wY%p@<`Y)QM6E)sC=I zx$J0NjJcdyE@yJdrIAt+leEsPp3~_(=X;*-`+dLn{oeO`p7;BG?=y0i*IF+(DQP7L zOhyJmIOkHkwgf6AM#gO@8oExr@rWRIJKrm>q6Q&GI`VJlA5tNt$L*tR)&_>y7 zA=6kDMP-tE#MTpnV)MR08|(O@g(C?F=}V_ikE*^W(L@dPgZv2dmdB0}j&mQogqOD) zWjOm1^~~%rcjTs{sCKdj)tGBIozJnqj5U`!k1J;+qC;&9t{O)p+NBdois6hF;aajC zkx^;pH4E&gdlP2@qQ?YkWhe_|kj54F&)h@rdM>yd@LSk;f^`l&V?5n%O(3{YxJ-xpZl7Kr059}TBC-o5W2-kGJJvA2#{xP;7fkqMoq!9nY%MW1N| zF6g;t7aBE+n!3M~+)w`yW;9}SW#nc^8+GR~MxkQr(V==@Z?P2BUXIS?n_*tr;#(vYYigOD>#ii*l^~|a%G>_zI%KHn1R`ebtdtHlp*gqN9N#zU4Pa2xlr z9e_(j@Z%D4;CSM3h4ssxf!lg4DO||C$^l3p5G;E0MYM$YLDr0BxUEFjpa@YZ(RUTH z=c~|DWG!VRmU?+-iKR{v36)({e#G_|sy2vL(gHllj|iyrw=0>Og_fP0A67NYLin;+ zY^L4w(#JEU7TT_;A<;I!#yEF=7A^KEZ8?+pB#rRhdtv5~(eV2UK%MBim- zY*d7$*7Yp#XRVU?bS|o^$YxQc*jFcX!;XL&;QXT$SLRcz_GY54XG|o`?EWE#cGK52 z>If4b!35W!h@dy2#R+on+Vmy+R7q2kJK(tJ8Bm?oUBu{aN>6#%eTd*9)L_OP8I032 zl=t^B*-WZ+=)IF0kjHI+_JaDjO_KAjICUrnJc`rS{=b+1 ze*0ap9H%RZXcI?tfs;6_grW4+VQN~Sq5ju&?)vJ`NigIa%Mv@)F?pDcAX6WWn8vK8 F`x}w{=6L`B