Powerpoint writer: Fix anchor links.

They were broken when I refactored (the Output module wanted to use
state left over from the construction of the Presentation type). This
change introduces a new type `LinkTarget = InternalTarget |
ExternalTarget`. Internal target points to a slide number, and these
will all be resolved before the Presentation is passed along to the
Output module.
This commit is contained in:
Jesse Rosenthal 2018-01-14 21:56:00 -05:00
parent 2c00540485
commit 3156722ac4
2 changed files with 80 additions and 59 deletions

View file

@ -54,7 +54,7 @@ import Text.Pandoc.MIME
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import Text.Pandoc.Writers.OOXML import Text.Pandoc.Writers.OOXML
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (mapMaybe, listToMaybe, catMaybes) import Data.Maybe (mapMaybe, listToMaybe)
import Text.Pandoc.ImageSize import Text.Pandoc.ImageSize
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import System.FilePath.Glob import System.FilePath.Glob
@ -135,24 +135,16 @@ data MediaInfo = MediaInfo { mInfoFilePath :: FilePath
, mInfoCaption :: Bool , mInfoCaption :: Bool
} deriving (Show, Eq) } deriving (Show, Eq)
data WriterState = WriterState { stLinkIds :: M.Map Int (M.Map Int (URL, String)) data WriterState = WriterState { stLinkIds :: M.Map Int (M.Map Int LinkTarget)
-- (FP, Local ID, Global ID, Maybe Mime) -- (FP, Local ID, Global ID, Maybe Mime)
, stMediaIds :: M.Map Int [MediaInfo] , stMediaIds :: M.Map Int [MediaInfo]
, stMediaGlobalIds :: M.Map FilePath Int , stMediaGlobalIds :: M.Map FilePath Int
, stNoteIds :: M.Map Int [Block]
-- associate anchors with slide id
, stAnchorMap :: M.Map String Int
-- media inherited from the template.
, stTemplateMedia :: [FilePath]
} deriving (Show, Eq) } deriving (Show, Eq)
instance Default WriterState where instance Default WriterState where
def = WriterState { stLinkIds = mempty def = WriterState { stLinkIds = mempty
, stMediaIds = mempty , stMediaIds = mempty
, stMediaGlobalIds = mempty , stMediaGlobalIds = mempty
, stNoteIds = mempty
, stAnchorMap= mempty
, stTemplateMedia = []
} }
type P m = ReaderT WriterEnv (StateT WriterState m) type P m = ReaderT WriterEnv (StateT WriterState m)
@ -420,7 +412,7 @@ replaceNamedChildren ns prefix name newKids element =
---------------------------------------------------------------- ----------------------------------------------------------------
registerLink :: PandocMonad m => (URL, String) -> P m Int registerLink :: PandocMonad m => LinkTarget -> P m Int
registerLink link = do registerLink link = do
curSlideId <- asks envCurSlideId curSlideId <- asks envCurSlideId
linkReg <- gets stLinkIds linkReg <- gets stLinkIds
@ -729,20 +721,15 @@ paraElemToElement (Run rpr s) = do
-- first we have to make sure that if it's an -- first we have to make sure that if it's an
-- anchor, it's in the anchor map. If not, there's -- anchor, it's in the anchor map. If not, there's
-- no link. -- no link.
anchorMap <- gets stAnchorMap
return $ case link of return $ case link of
-- anchor with nothing in the map InternalTarget _ ->
('#':target, _) | Nothing <- M.lookup target anchorMap ->
[]
-- anchor that is in the map
('#':_, _) ->
let linkAttrs = let linkAttrs =
[ ("r:id", "rId" ++ show idNum) [ ("r:id", "rId" ++ show idNum)
, ("action", "ppaction://hlinksldjump") , ("action", "ppaction://hlinksldjump")
] ]
in [mknode "a:hlinkClick" linkAttrs ()] in [mknode "a:hlinkClick" linkAttrs ()]
-- external -- external
_ -> ExternalTarget _ ->
let linkAttrs = let linkAttrs =
[ ("r:id", "rId" ++ show idNum) [ ("r:id", "rId" ++ show idNum)
] ]
@ -1191,31 +1178,23 @@ slideToSlideRelEntry slide idNum = do
element <- slideToSlideRelElement slide idNum element <- slideToSlideRelElement slide idNum
elemToEntry ("ppt/slides/_rels/" ++ slideToFilePath slide idNum ++ ".rels") element elemToEntry ("ppt/slides/_rels/" ++ slideToFilePath slide idNum ++ ".rels") element
linkRelElement :: PandocMonad m => Int -> (URL, String) -> P m (Maybe Element) linkRelElement :: PandocMonad m => Int -> LinkTarget -> P m Element
linkRelElement idNum (url, _) = do linkRelElement idNum (InternalTarget num) = do
anchorMap <- gets stAnchorMap return $
case url of
-- if it's an anchor in the map, we use the slide number for an
-- internal link.
'#' : anchor | Just num <- M.lookup anchor anchorMap ->
return $ Just $
mknode "Relationship" [ ("Id", "rId" ++ show idNum) mknode "Relationship" [ ("Id", "rId" ++ show idNum)
, ("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide") , ("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide")
, ("Target", "slide" ++ show num ++ ".xml") , ("Target", "slide" ++ show num ++ ".xml")
] () ] ()
-- if it's an anchor not in the map, we return nothing. linkRelElement idNum (ExternalTarget (url, _)) = do
'#' : _ -> return Nothing return $
-- Anything else we treat as an external link
_ ->
return $ Just $
mknode "Relationship" [ ("Id", "rId" ++ show idNum) mknode "Relationship" [ ("Id", "rId" ++ show idNum)
, ("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink") , ("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink")
, ("Target", url) , ("Target", url)
, ("TargetMode", "External") , ("TargetMode", "External")
] () ] ()
linkRelElements :: PandocMonad m => M.Map Int (URL, String) -> P m [Element] linkRelElements :: PandocMonad m => M.Map Int LinkTarget -> P m [Element]
linkRelElements mp = catMaybes <$> mapM (\(n, lnk) -> linkRelElement n lnk) (M.toList mp) linkRelElements mp = mapM (\(n, lnk) -> linkRelElement n lnk) (M.toList mp)
mediaRelElement :: MediaInfo -> Element mediaRelElement :: MediaInfo -> Element
mediaRelElement mInfo = mediaRelElement mInfo =

View file

@ -35,7 +35,6 @@ Presentation.
module Text.Pandoc.Writers.Powerpoint.Presentation ( documentToPresentation module Text.Pandoc.Writers.Powerpoint.Presentation ( documentToPresentation
, Presentation(..) , Presentation(..)
, Slide(..) , Slide(..)
, SlideElement(..)
, Shape(..) , Shape(..)
, Graphic(..) , Graphic(..)
, BulletType(..) , BulletType(..)
@ -50,6 +49,7 @@ module Text.Pandoc.Writers.Powerpoint.Presentation ( documentToPresentation
, PicProps(..) , PicProps(..)
, URL , URL
, TeXString(..) , TeXString(..)
, LinkTarget(..)
) where ) where
@ -78,10 +78,6 @@ data WriterEnv = WriterEnv { envMetadata :: Meta
, envInList :: Bool , envInList :: Bool
, envInNoteSlide :: Bool , envInNoteSlide :: Bool
, envCurSlideId :: Int , envCurSlideId :: Int
-- the difference between the number at
-- the end of the slide file name and
-- the rId number
, envSlideIdOffset :: Int
} }
deriving (Show) deriving (Show)
@ -95,7 +91,6 @@ instance Default WriterEnv where
, envInList = False , envInList = False
, envInNoteSlide = False , envInNoteSlide = False
, envCurSlideId = 1 , envCurSlideId = 1
, envSlideIdOffset = 1
} }
@ -139,9 +134,6 @@ data Slide = MetadataSlide { metadataSlideTitle :: [ParaElem]
} }
deriving (Show, Eq) deriving (Show, Eq)
data SlideElement = SlideElement Pixels Pixels Pixels Pixels Shape
deriving (Show, Eq)
data Shape = Pic PicProps FilePath Text.Pandoc.Definition.Attr [ParaElem] data Shape = Pic PicProps FilePath Text.Pandoc.Definition.Attr [ParaElem]
| GraphicFrame [Graphic] [ParaElem] | GraphicFrame [Graphic] [ParaElem]
| TextBox [Paragraph] | TextBox [Paragraph]
@ -206,12 +198,16 @@ data Capitals = NoCapitals | SmallCapitals | AllCapitals
type URL = String type URL = String
data LinkTarget = ExternalTarget (URL, String)
| InternalTarget Int -- slideId
deriving (Show, Eq)
data RunProps = RunProps { rPropBold :: Bool data RunProps = RunProps { rPropBold :: Bool
, rPropItalics :: Bool , rPropItalics :: Bool
, rStrikethrough :: Maybe Strikethrough , rStrikethrough :: Maybe Strikethrough
, rBaseline :: Maybe Int , rBaseline :: Maybe Int
, rCap :: Maybe Capitals , rCap :: Maybe Capitals
, rLink :: Maybe (URL, String) , rLink :: Maybe LinkTarget
, rPropCode :: Bool , rPropCode :: Bool
, rPropBlockQuote :: Bool , rPropBlockQuote :: Bool
, rPropForceSize :: Maybe Pixels , rPropForceSize :: Maybe Pixels
@ -229,7 +225,7 @@ instance Default RunProps where
, rPropForceSize = Nothing , rPropForceSize = Nothing
} }
data PicProps = PicProps { picPropLink :: Maybe (URL, String) data PicProps = PicProps { picPropLink :: Maybe LinkTarget
} deriving (Show, Eq) } deriving (Show, Eq)
instance Default PicProps where instance Default PicProps where
@ -267,7 +263,7 @@ inlineToParElems Space = inlineToParElems (Str " ")
inlineToParElems SoftBreak = inlineToParElems (Str " ") inlineToParElems SoftBreak = inlineToParElems (Str " ")
inlineToParElems LineBreak = return [Break] inlineToParElems LineBreak = return [Break]
inlineToParElems (Link _ ils (url, title)) = do inlineToParElems (Link _ ils (url, title)) = do
local (\r ->r{envRunProps = (envRunProps r){rLink = Just (url, title)}}) $ local (\r ->r{envRunProps = (envRunProps r){rLink = Just $ ExternalTarget (url, title)}}) $
inlinesToParElems ils inlinesToParElems ils
inlineToParElems (Code _ str) = do inlineToParElems (Code _ str) = do
local (\r ->r{envRunProps = (envRunProps r){rPropCode = True}}) $ local (\r ->r{envRunProps = (envRunProps r){rPropCode = True}}) $
@ -414,10 +410,10 @@ blockToShape (Para (il:_)) | Image attr ils (url, _) <- il =
Pic def url attr <$> (inlinesToParElems ils) Pic def url attr <$> (inlinesToParElems ils)
blockToShape (Plain (il:_)) | Link _ (il':_) target <- il blockToShape (Plain (il:_)) | Link _ (il':_) target <- il
, Image attr ils (url, _) <- il' = , Image attr ils (url, _) <- il' =
Pic def{picPropLink = Just target} url attr <$> (inlinesToParElems ils) Pic def{picPropLink = Just $ ExternalTarget target} url attr <$> (inlinesToParElems ils)
blockToShape (Para (il:_)) | Link _ (il':_) target <- il blockToShape (Para (il:_)) | Link _ (il':_) target <- il
, Image attr ils (url, _) <- il' = , Image attr ils (url, _) <- il' =
Pic def{picPropLink = Just target} url attr <$> (inlinesToParElems ils) Pic def{picPropLink = Just $ ExternalTarget target} url attr <$> (inlinesToParElems ils)
blockToShape (Table caption algn _ hdrCells rows) = do blockToShape (Table caption algn _ hdrCells rows) = do
caption' <- inlinesToParElems caption caption' <- inlinesToParElems caption
hdrCells' <- rowToParagraphs algn hdrCells hdrCells' <- rowToParagraphs algn hdrCells
@ -644,6 +640,51 @@ combineParaElems' (Just pElem') (pElem : pElems)
combineParaElems :: [ParaElem] -> [ParaElem] combineParaElems :: [ParaElem] -> [ParaElem]
combineParaElems = combineParaElems' Nothing combineParaElems = combineParaElems' Nothing
applyToParagraph :: Monad m => (ParaElem -> m ParaElem) -> Paragraph -> m Paragraph
applyToParagraph f para = do
paraElems' <- mapM f $ paraElems para
return $ para {paraElems = paraElems'}
applyToShape :: Monad m => (ParaElem -> m ParaElem) -> Shape -> m Shape
applyToShape f (Pic pPr fp attr pes) = do
pes' <- mapM f pes
return $ Pic pPr fp attr pes'
applyToShape f (GraphicFrame gfx pes) = do
pes' <- mapM f pes
return $ GraphicFrame gfx pes'
applyToShape f (TextBox paras) = do
paras' <- mapM (applyToParagraph f) paras
return $ TextBox paras'
applyToSlide :: Monad m => (ParaElem -> m ParaElem) -> Slide -> m Slide
applyToSlide f (MetadataSlide title subtitle authors date) = do
title' <- mapM f title
subtitle' <- mapM f subtitle
authors' <- mapM (mapM f) authors
date' <- mapM f date
return $ MetadataSlide title' subtitle' authors' date'
applyToSlide f (TitleSlide title) = do
title' <- mapM f title
return $ TitleSlide title'
applyToSlide f (ContentSlide hdr content) = do
hdr' <- mapM f hdr
content' <- mapM (applyToShape f) content
return $ ContentSlide hdr' content'
applyToSlide f (TwoColumnSlide hdr contentL contentR) = do
hdr' <- mapM f hdr
contentL' <- mapM (applyToShape f) contentL
contentR' <- mapM (applyToShape f) contentR
return $ TwoColumnSlide hdr' contentL' contentR'
replaceAnchor :: PandocMonad m => ParaElem -> Pres m ParaElem
replaceAnchor (Run rProps s)
| Just (ExternalTarget ('#':anchor, _)) <- rLink rProps = do
anchorMap <- gets stAnchorMap
return $ case M.lookup anchor anchorMap of
Just n -> Run (rProps{rLink = Just $ InternalTarget n}) s
Nothing -> Run rProps s
replaceAnchor pe = return pe
blocksToPresentation :: PandocMonad m => [Block] -> Pres m Presentation blocksToPresentation :: PandocMonad m => [Block] -> Pres m Presentation
blocksToPresentation blks = do blocksToPresentation blks = do
opts <- asks envOpts opts <- asks envOpts
@ -683,9 +724,10 @@ blocksToPresentation blks = do
}) })
(blocksToSlide $ notesSlideBlocks) (blocksToSlide $ notesSlideBlocks)
return [notesSlide] return [notesSlide]
return $
Presentation $ let slides = metadataslides ++ tocSlides ++ bodyslides ++ notesSlides
metadataslides ++ tocSlides ++ bodyslides ++ notesSlides slides' <- mapM (applyToSlide replaceAnchor) slides
return $ Presentation slides'
documentToPresentation :: PandocMonad m documentToPresentation :: PandocMonad m
=> WriterOptions => WriterOptions