@@ -1,5 +1,5 @@
1{-# LANGUAGE DeriveDataTypeable, CPP, MultiParamTypeClasses,
1{-# LANGUAGE DeriveDataTypeable, CPP, MultiParamTypeClasses,
2− FlexibleContexts, ScopedTypeVariables #-}
2+ FlexibleContexts, ScopedTypeVariables, GeneralizedNewtypeDeriving #-}
4Copyright (C) 2006-2014 John MacFarlane <jgm@berkeley.edu>
4Copyright (C) 2006-2014 John MacFarlane <jgm@berkeley.edu>
@@ -50,7 +50,11 @@ module Text.Pandoc.Shared (
50 tabFilter,
50 tabFilter,
51 -- * Media Handling
51 -- * Media Handling
53 insertMedia,
55 insertMedia,
54 -- * Date/time
58 -- * Date/time
55 normalizeDate,
59 normalizeDate,
56 -- * Pandoc block and inline list processing
60 -- * Pandoc block and inline list processing
@@ -102,16 +106,18 @@ import Data.Char ( toLower, isLower, isUpper, isAlpha,
102 isLetter, isDigit, isSpace )
106 isLetter, isDigit, isSpace )
103import Data.List ( find, isPrefixOf, intercalate )
107import Data.List ( find, isPrefixOf, intercalate )
104import qualified Data.Map as M
108import qualified Data.Map as M
109+import Data.Maybe ( fromMaybe )
105import Network.URI ( escapeURIString, isURI, nonStrictRelativeTo,
110import Network.URI ( escapeURIString, isURI, nonStrictRelativeTo,
106 unEscapeString, parseURIReference )
111 unEscapeString, parseURIReference )
107import qualified Data.Set as Set
112import qualified Data.Set as Set
108import System.Directory
113import System.Directory
109import Text.Pandoc.MIME (getMimeType)
114import Text.Pandoc.MIME (getMimeType)
110−import System.FilePath ( (</>), takeExtension, dropExtension )
115+import System.FilePath ( (</>), takeExtension, dropExtension, takeDirectory,
116+ splitPath, joinPath )
111import Data.Generics (Typeable, Data)
117import Data.Generics (Typeable, Data)
112import qualified Control.Monad.State as S
118import qualified Control.Monad.State as S
113import qualified Control.Exception as E
119import qualified Control.Exception as E
114−import Control.Monad (msum, unless)
120+import Control.Monad (msum, unless, MonadPlus(..), when)
115import Text.Pandoc.Pretty (charWidth)
121import Text.Pandoc.Pretty (charWidth)
116import System.Locale (defaultTimeLocale)
122import System.Locale (defaultTimeLocale)
117import Data.Time
123import Data.Time
@@ -292,15 +298,63 @@ tabFilter tabStop =
292--- Media handling
298--- Media handling
295−-- | A map of media paths to their binary representations.
301+-- | A container for a collection of binary resources, with names and
296−type MediaBag = M.Map String BL.ByteString
303+newtype MediaBag = MediaBag (M.Map String (String, BL.ByteString))
306+instance Show MediaBag where
307+ show bag = "MediaBag " ++ show (mediaDirectory bag)
309+emptyMediaBag :: MediaBag
310+emptyMediaBag = MediaBag M.empty
298−-- | Insert a media item into a `MediaBag`
312+-- | Insert a media item into a 'MediaBag', replacing any existing
313+-- value with the same name.
299−insertMedia :: FilePath
314+insertMedia :: FilePath -- ^ relative path and canonical name of resource
315+ -> Maybe String -- ^ mime type (Nothing = determine from extension)
300− -> BL.ByteString
316+ -> BL.ByteString -- ^ contents of resource
301 -> MediaBag
318 -> MediaBag
319+insertMedia fp mbMime contents (MediaBag mediamap) =
320+ MediaBag (M.insert fp (mime, contents) mediamap)
321+ where mime = fromMaybe "application/octet-stream" (mbMime `mplus` fallback)
322+ fallback = case takeExtension fp of
323+ ".gz" -> getMimeType $ dropExtension fp
326+-- | Lookup a media item in a 'MediaBag', returning mime type and contents.
327+lookupMedia :: FilePath
302 -> MediaBag
328 -> MediaBag
303−insertMedia = M.insert
329+ -> Maybe (String, BL.ByteString)
330+lookupMedia fp (MediaBag mediamap) = M.lookup fp mediamap
332+-- | Get a list of the file paths stored in a 'MediaBag', with
333+-- their corresponding mime types and the lengths in bytes of the contents.
334+mediaDirectory :: MediaBag -> [(String, String, Int)]
335+mediaDirectory (MediaBag mediamap) =
336+ M.foldWithKey (\fp (mime,contents) ->
337+ ((fp, mime, fromIntegral $ BL.length contents):)) [] mediamap
339+-- | Extract contents of MediaBag to a given directory. Print informational
340+-- messages if 'verbose' is true.
341+extractMediaBag :: Bool
345+extractMediaBag verbose dir (MediaBag mediamap) = do
346+ sequence_ $ M.foldWithKey
347+ (\fp (_ ,contents) ->
348+ ((writeMedia verbose dir (fp, contents)):)) [] mediamap
350+writeMedia :: Bool -> FilePath -> (FilePath, BL.ByteString) -> IO ()
351+writeMedia verbose dir (subpath, bs) = do
352+ -- we join and split to convert a/b/c to a\b\c on Windows;
353+ -- in zip containers all paths use /
354+ let fullpath = dir </> joinPath (splitPath subpath)
355+ createDirectoryIfMissing True $ takeDirectory fullpath
356+ when verbose $ warn $ "extracting " ++ fullpath
357+ BL.writeFile fullpath bs
306-- Date/time
360-- Date/time
@@ -803,13 +857,9 @@ fetchItem sourceURL s
803fetchItem' :: MediaBag -> Maybe String -> String
857fetchItem' :: MediaBag -> Maybe String -> String
804 -> IO (Either E.SomeException (BS.ByteString, Maybe String))
858 -> IO (Either E.SomeException (BS.ByteString, Maybe String))
805fetchItem' media sourceURL s = do
859fetchItem' media sourceURL s = do
806− case M.lookup s media of
860+ case lookupMedia s media of
807 Nothing -> fetchItem sourceURL s
861 Nothing -> fetchItem sourceURL s
809− let mime = case takeExtension s of
810− ".gz" -> getMimeType $ dropExtension s
812− return $ Right (BS.concat $ toChunks bs, mime)
862+ Just (mime, bs) -> return $ Right (BS.concat $ toChunks bs, Just mime)
814-- | Read from a URL and return raw data and maybe mime type.
864-- | Read from a URL and return raw data and maybe mime type.
815openURL :: String -> IO (Either E.SomeException (BS.ByteString, Maybe String))
865openURL :: String -> IO (Either E.SomeException (BS.ByteString, Maybe String))