Powerpoint writer: Improve templating using --reference-doc

Templating should work much more reliably now. There is still some
problem with image placement when we change sizes. A further commit
will address this.
This commit is contained in:
Jesse Rosenthal 2018-01-12 17:28:58 -05:00
parent 6528082401
commit a2870a1aeb

View file

@ -36,11 +36,11 @@ import Control.Monad.Except (throwError)
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.State import Control.Monad.State
import Codec.Archive.Zip import Codec.Archive.Zip
import Data.List (intercalate, stripPrefix, isPrefixOf, nub) import Data.List (intercalate, stripPrefix, nub, union)
import Data.Default import Data.Default
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, posixSecondsToUTCTime)
import System.FilePath.Posix (splitDirectories, splitExtension) import System.FilePath.Posix (splitDirectories, splitExtension, takeExtension)
import Text.XML.Light import Text.XML.Light
import qualified Text.XML.Light.Cursor as XMLC import qualified Text.XML.Light.Cursor as XMLC
import Text.Pandoc.Definition import Text.Pandoc.Definition
@ -61,6 +61,7 @@ import qualified Data.Map as M
import Data.Maybe (mapMaybe, listToMaybe, maybeToList, catMaybes) import Data.Maybe (mapMaybe, listToMaybe, maybeToList, catMaybes)
import Text.Pandoc.ImageSize import Text.Pandoc.ImageSize
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import System.FilePath.Glob
import Text.TeXMath import Text.TeXMath
import Text.Pandoc.Writers.Math (convertMath) import Text.Pandoc.Writers.Math (convertMath)
@ -90,9 +91,13 @@ writePowerpoint opts (Pandoc meta blks) = do
Just n -> n Just n -> n
Nothing -> getSlideLevel blks' Nothing -> getSlideLevel blks'
} }
runP env def $ do pres <- blocksToPresentation blks'
archv <- presentationToArchive pres let st = def { stMediaGlobalIds = initialGlobalIds refArchive distArchive
return $ fromArchive archv }
runP env st $ do pres <- blocksToPresentation blks'
archv <- presentationToArchive pres
return $ fromArchive archv
concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b] concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]
concatMapM f xs = liftM concat (mapM f xs) concatMapM f xs = liftM concat (mapM f xs)
@ -149,6 +154,8 @@ data WriterState = WriterState { stLinkIds :: M.Map Int (M.Map Int (URL, String)
, stNoteIds :: M.Map Int [Block] , stNoteIds :: M.Map Int [Block]
-- associate anchors with slide id -- associate anchors with slide id
, stAnchorMap :: M.Map String Int , 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
@ -157,8 +164,25 @@ instance Default WriterState where
, stMediaGlobalIds = mempty , stMediaGlobalIds = mempty
, stNoteIds = mempty , stNoteIds = mempty
, stAnchorMap= mempty , stAnchorMap= mempty
, stTemplateMedia = []
} }
-- This populates the global ids map with images already in the
-- template, so the ids won't be used by images introduced by the
-- user.
initialGlobalIds :: Archive -> Archive -> M.Map FilePath Int
initialGlobalIds refArchive distArchive =
let archiveFiles = filesInArchive refArchive `union` filesInArchive distArchive
mediaPaths = filter (match (compile "ppt/media/image")) archiveFiles
go :: FilePath -> Maybe (FilePath, Int)
go fp = do
s <- stripPrefix "ppt/media/image" $ fst $ splitExtension fp
(n, _) <- listToMaybe $ reads s
return (fp, n)
in
M.fromList $ mapMaybe go mediaPaths
type P m = ReaderT WriterEnv (StateT WriterState m) type P m = ReaderT WriterEnv (StateT WriterState m)
runP :: Monad m => WriterEnv -> WriterState -> P m a -> m a runP :: Monad m => WriterEnv -> WriterState -> P m a -> m a
@ -760,75 +784,111 @@ copyFileToArchive arch fp = do
Nothing -> fail $ fp ++ " missing in reference file" Nothing -> fail $ fp ++ " missing in reference file"
Just e -> return $ addEntryToArchive e arch Just e -> return $ addEntryToArchive e arch
getMediaFiles :: PandocMonad m => P m [FilePath] -- getMediaFiles :: PandocMonad m => P m [FilePath]
getMediaFiles = do -- getMediaFiles = do
-- refArchive <- asks envRefArchive
-- distArchive <- asks envDistArchive
-- let allEntries = nub $ filesInArchive refArchive ++ filesInArchive distArchive
-- return $ filter (isPrefixOf "ppt/media") allEntries
-- copyFileToArchiveIfExists :: PandocMonad m => Archive -> FilePath -> P m Archive
-- copyFileToArchiveIfExists arch fp = do
-- refArchive <- asks envRefArchive
-- distArchive <- asks envDistArchive
-- case findEntryByPath fp refArchive `mplus` findEntryByPath fp distArchive of
-- Nothing -> return $ arch
-- Just e -> return $ addEntryToArchive e arch
inheritedPatterns :: [Pattern]
inheritedPatterns = map compile [ "_rels/.rels"
, "docProps/app.xml"
, "docProps/core.xml"
, "ppt/slideLayouts/slideLayout*.xml"
, "ppt/slideLayouts/_rels/slideLayout*.xml.rels"
, "ppt/slideMasters/slideMaster1.xml"
, "ppt/slideMasters/_rels/slideMaster1.xml.rels"
, "ppt/theme/theme1.xml"
, "ppt/theme/_rels/theme1.xml.rels"
, "ppt/presProps.xml"
, "ppt/viewProps.xml"
, "ppt/tableStyles.xml"
, "ppt/media/image*"
]
patternToFilePaths :: PandocMonad m => Pattern -> P m [FilePath]
patternToFilePaths pat = do
refArchive <- asks envRefArchive refArchive <- asks envRefArchive
distArchive <- asks envDistArchive distArchive <- asks envDistArchive
let allEntries = nub $ filesInArchive refArchive ++ filesInArchive distArchive
return $ filter (isPrefixOf "ppt/media") allEntries
let archiveFiles = filesInArchive refArchive `union` filesInArchive distArchive
return $ filter (match pat) archiveFiles
copyFileToArchiveIfExists :: PandocMonad m => Archive -> FilePath -> P m Archive patternsToFilePaths :: PandocMonad m => [Pattern] -> P m [FilePath]
copyFileToArchiveIfExists arch fp = do patternsToFilePaths pats = concat <$> mapM patternToFilePaths pats
refArchive <- asks envRefArchive
distArchive <- asks envDistArchive
case findEntryByPath fp refArchive `mplus` findEntryByPath fp distArchive of
Nothing -> return $ arch
Just e -> return $ addEntryToArchive e arch
inheritedFiles :: [FilePath] -- requiredFiles :: [FilePath]
inheritedFiles = [ "_rels/.rels" -- requiredFiles = inheritedFiles
, "docProps/app.xml"
, "docProps/core.xml" -- inheritedFiles :: [FilePath]
, "ppt/slideLayouts/slideLayout4.xml" -- inheritedFiles = [ "_rels/.rels"
, "ppt/slideLayouts/_rels/slideLayout9.xml.rels" -- , "docProps/app.xml"
, "ppt/slideLayouts/_rels/slideLayout2.xml.rels" -- , "docProps/core.xml"
, "ppt/slideLayouts/_rels/slideLayout10.xml.rels" -- , "ppt/slideLayouts/slideLayout4.xml"
, "ppt/slideLayouts/_rels/slideLayout1.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout9.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout3.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout2.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout5.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout10.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout7.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout1.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout8.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout3.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout11.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout5.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout4.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout7.xml.rels"
, "ppt/slideLayouts/_rels/slideLayout6.xml.rels" -- , "ppt/slideLayouts/_rels/slideLayout8.xml.rels"
, "ppt/slideLayouts/slideLayout2.xml" -- , "ppt/slideLayouts/_rels/slideLayout11.xml.rels"
, "ppt/slideLayouts/slideLayout8.xml" -- , "ppt/slideLayouts/_rels/slideLayout4.xml.rels"
, "ppt/slideLayouts/slideLayout11.xml" -- , "ppt/slideLayouts/_rels/slideLayout6.xml.rels"
, "ppt/slideLayouts/slideLayout3.xml" -- , "ppt/slideLayouts/slideLayout2.xml"
, "ppt/slideLayouts/slideLayout6.xml" -- , "ppt/slideLayouts/slideLayout8.xml"
, "ppt/slideLayouts/slideLayout9.xml" -- , "ppt/slideLayouts/slideLayout11.xml"
, "ppt/slideLayouts/slideLayout5.xml" -- , "ppt/slideLayouts/slideLayout3.xml"
, "ppt/slideLayouts/slideLayout7.xml" -- , "ppt/slideLayouts/slideLayout6.xml"
, "ppt/slideLayouts/slideLayout1.xml" -- , "ppt/slideLayouts/slideLayout9.xml"
, "ppt/slideLayouts/slideLayout10.xml" -- , "ppt/slideLayouts/slideLayout5.xml"
-- , "ppt/_rels/presentation.xml.rels" -- , "ppt/slideLayouts/slideLayout7.xml"
, "ppt/theme/theme1.xml" -- , "ppt/slideLayouts/slideLayout1.xml"
, "ppt/presProps.xml" -- , "ppt/slideLayouts/slideLayout10.xml"
-- , "ppt/slides/_rels/slide1.xml.rels" -- -- , "ppt/_rels/presentation.xml.rels"
-- , "ppt/slides/_rels/slide2.xml.rels" -- , "ppt/theme/theme1.xml"
-- This is the one we're -- , "ppt/presProps.xml"
-- going to build -- -- , "ppt/slides/_rels/slide1.xml.rels"
-- , "ppt/slides/slide2.xml" -- -- , "ppt/slides/_rels/slide2.xml.rels"
-- , "ppt/slides/slide1.xml" -- -- This is the one we're
, "ppt/viewProps.xml" -- -- going to build
, "ppt/tableStyles.xml" -- -- , "ppt/slides/slide2.xml"
, "ppt/slideMasters/_rels/slideMaster1.xml.rels" -- -- , "ppt/slides/slide1.xml"
, "ppt/slideMasters/slideMaster1.xml" -- , "ppt/viewProps.xml"
-- , "ppt/presentation.xml" -- , "ppt/tableStyles.xml"
-- , "[Content_Types].xml" -- , "ppt/slideMasters/_rels/slideMaster1.xml.rels"
] -- , "ppt/slideMasters/slideMaster1.xml"
-- -- , "ppt/presentation.xml"
-- -- , "[Content_Types].xml"
-- ]
-- -- Here are some that might not be there. We won't fail if they're not
-- possibleInheritedFiles :: [FilePath]
-- possibleInheritedFiles = [ "ppt/theme/_rels/theme1.xml.rels" ]
-- Here are some that might not be there. We won't fail if they're not
possibleInheritedFiles :: [FilePath]
possibleInheritedFiles = [ "ppt/theme/_rels/theme1.xml.rels" ]
presentationToArchive :: PandocMonad m => Presentation -> P m Archive presentationToArchive :: PandocMonad m => Presentation -> P m Archive
presentationToArchive p@(Presentation _ slides) = do presentationToArchive p@(Presentation _ slides) = do
newArch <- foldM copyFileToArchive emptyArchive inheritedFiles filePaths <- patternsToFilePaths inheritedPatterns
mediaDir <- getMediaFiles newArch' <- foldM copyFileToArchive emptyArchive filePaths
newArch' <- foldM copyFileToArchiveIfExists newArch $
possibleInheritedFiles ++ mediaDir -- set the template media to the relevant fps:
-- we register any media fp in the filepaths
-- mediaDir <- getMediaFiles
-- newArch' <- foldM copyFileToArchiveIfExists newArch $
-- possibleInheritedFiles ++ mediaDir
-- presentation entry and rels. We have to do the rels first to make -- presentation entry and rels. We have to do the rels first to make
-- sure we know the correct offset for the rIds. -- sure we know the correct offset for the rIds.
presEntry <- presentationToPresEntry p presEntry <- presentationToPresEntry p
@ -1808,6 +1868,17 @@ contentTypesToEntry ct = elemToEntry "[Content_Types].xml" $ contentTypesToEleme
pathToOverride :: FilePath -> Maybe OverrideContentType pathToOverride :: FilePath -> Maybe OverrideContentType
pathToOverride fp = OverrideContentType ("/" ++ fp) <$> (getContentType fp) pathToOverride fp = OverrideContentType ("/" ++ fp) <$> (getContentType fp)
mediaFileContentType :: FilePath -> Maybe DefaultContentType
mediaFileContentType fp = case takeExtension fp of
'.' : ext -> Just $
DefaultContentType { defContentTypesExt = ext
, defContentTypesType =
case getMimeType fp of
Just mt -> mt
Nothing -> "application/octet-stream"
}
_ -> Nothing
mediaContentType :: MediaInfo -> Maybe DefaultContentType mediaContentType :: MediaInfo -> Maybe DefaultContentType
mediaContentType mInfo mediaContentType mInfo
| Just ('.' : ext) <- mInfoExt mInfo = | Just ('.' : ext) <- mInfoExt mInfo =
@ -1822,11 +1893,16 @@ mediaContentType mInfo
presentationToContentTypes :: PandocMonad m => Presentation -> P m ContentTypes presentationToContentTypes :: PandocMonad m => Presentation -> P m ContentTypes
presentationToContentTypes (Presentation _ slides) = do presentationToContentTypes (Presentation _ slides) = do
mediaInfos <- (mconcat . M.elems) <$> gets stMediaIds mediaInfos <- (mconcat . M.elems) <$> gets stMediaIds
filePaths <- patternsToFilePaths inheritedPatterns
let mediaFps = filter (match (compile "ppt/media/image*")) filePaths
let defaults = [ DefaultContentType "xml" "application/xml" let defaults = [ DefaultContentType "xml" "application/xml"
, DefaultContentType "rels" "application/vnd.openxmlformats-package.relationships+xml" , DefaultContentType "rels" "application/vnd.openxmlformats-package.relationships+xml"
] ]
mediaDefaults = nub $ mapMaybe mediaContentType mediaInfos mediaDefaults = nub $
inheritedOverrides = mapMaybe pathToOverride inheritedFiles (mapMaybe mediaContentType $ mediaInfos) ++
(mapMaybe mediaFileContentType $ mediaFps)
inheritedOverrides = mapMaybe pathToOverride filePaths
presOverride = mapMaybe pathToOverride ["ppt/presentation.xml"] presOverride = mapMaybe pathToOverride ["ppt/presentation.xml"]
slideOverrides = slideOverrides =
mapMaybe mapMaybe