@@ -28,26 +28,23 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2828
2929Conversion of 'Pandoc' documents to EPUB.
3030-}
31−module Text.Pandoc.Writers.EPUB ( writeEPUB ) where
32−import Data.IORef ( IORef, newIORef, readIORef, modifyIORef )
31+module Text.Pandoc.Writers.EPUB ( writeEPUB, writeEPUBPure ) where
32+import Data.IORef ( IORef )
3333import qualified Data.Map as M
3434import qualified Data.Set as Set
3535import Data.Maybe ( fromMaybe, catMaybes )
3636import Data.List ( isPrefixOf, isInfixOf, intercalate )
37−import System.Environment ( getEnv )
3837import Text.Printf (printf)
3938import System.FilePath ( takeExtension, takeFileName )
40−import System.FilePath.Glob ( namesMatching )
4139import Network.HTTP ( urlEncode )
4240import qualified Data.ByteString.Lazy as B
4341import qualified Data.ByteString.Lazy.Char8 as B8
4442import qualified Text.Pandoc.UTF8 as UTF8
4543import Codec.Archive.Zip ( emptyArchive, addEntryToArchive, eRelativePath, fromEntry , Entry, toEntry, fromArchive)
46−import Data.Time.Clock.POSIX ( getPOSIXTime )
4744import Text.Pandoc.Compat.Time
4845import Text.Pandoc.Shared ( renderTags', safeRead, uniqueIdent, trim
49− , normalizeDate, readDataFile, stringify, warn
50− , hierarchicalize, fetchItem' )
46+ , normalizeDate, stringify
47+ , hierarchicalize )
5148import qualified Text.Pandoc.Shared as S (Element(..))
5249import Text.Pandoc.Builder (fromList, setMeta)
5350import Text.Pandoc.Options ( WriterOptions(..)
@@ -58,17 +55,19 @@ import Text.Pandoc.Options ( WriterOptions(..)
5855import Text.Pandoc.Definition
5956import Text.Pandoc.Walk (walk, walkM, query)
6057import Control.Monad.State (modify, get, State, put, evalState)
61−import Control.Monad (mplus, liftM, when)
58+import Control.Monad (mplus, when)
6259import Text.XML.Light ( unode, Element(..), unqual, Attr(..), add_attrs
6360 , strContent, lookupAttr, Node(..), QName(..), parseXML
6461 , onlyElems, node, ppElement)
65−import Text.Pandoc.UUID (getRandomUUID)
6662import Text.Pandoc.Writers.HTML ( writeHtml )
6763import Data.Char ( toLower, isDigit, isAlphaNum )
6864import Text.Pandoc.MIME (MimeType, getMimeType, extensionFromMimeType)
69−import qualified Control.Exception as E
7065import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
7166import Text.HTML.TagSoup (Tag(TagOpen), fromAttrib, parseTags)
67+import Text.Pandoc.Free (PandocAction, runIO)
68+import qualified Text.Pandoc.Free as P
69+
70+type EPUBAction = PandocAction [(FilePath, (FilePath, Maybe Entry))]
7271
7372-- A Chapter includes a list of blocks and maybe a section
7473-- number offset. Note, some chapters are unnumbered. The section
@@ -143,7 +142,7 @@ removeNote :: Inline -> Inline
143142removeNote (Note _) = Str ""
144143removeNote x = x
145144
146−getEPUBMetadata :: WriterOptions -> Meta -> IO EPUBMetadata
145+getEPUBMetadata :: WriterOptions -> Meta -> EPUBAction EPUBMetadata
147146getEPUBMetadata opts meta = do
148147 let md = metadataFromMeta opts meta
149148 let elts = onlyElems $ parseXML $ writerEpubMetadata opts
@@ -151,7 +150,7 @@ getEPUBMetadata opts meta = do
151150 let addIdentifier m =
152151 if null (epubIdentifier m)
153152 then do
154− randomId <- fmap show getRandomUUID
153+ randomId <- fmap show P.newUUID
155154 return $ m{ epubIdentifier = [Identifier randomId Nothing] }
156155 else return m
157156 let addLanguage m =
@@ -159,16 +158,19 @@ getEPUBMetadata opts meta = do
159158 then case lookup "lang" (writerVariables opts) of
160159 Just x -> return m{ epubLanguage = x }
161160 Nothing -> do
162− localeLang <- E.catch (liftM
163− (map (\c -> if c == '_' then '-' else c) .
164− takeWhile (/='.')) $ getEnv "LANG")
165− (\e -> let _ = (e :: E.SomeException) in return "en-US")
161+ mLang <- P.lookupEnv "LANG"
162+ let localeLang =
163+ case mLang of
164+ Just lang ->
165+ map (\c -> if c == '_' then '-' else c) $
166+ takeWhile (/='.') lang
167+ Nothing -> "en-US"
166168 return m{ epubLanguage = localeLang }
167169 else return m
168170 let fixDate m =
169171 if null (epubDate m)
170172 then do
171− currentTime <- getCurrentTime
173+ currentTime <- P.getCurrentTime
172174 return $ m{ epubDate = [ Date{
173175 dateText = showDateTimeISO8601 currentTime
174176 , dateEvent = Nothing } ] }
@@ -333,10 +335,15 @@ metadataFromMeta opts meta = EPUBMetadata{
333335writeEPUB :: WriterOptions -- ^ Writer options
334336 -> Pandoc -- ^ Document to convert
335337 -> IO B.ByteString
336−writeEPUB opts doc@(Pandoc meta _) = do
338+writeEPUB opts doc = runIO $ writeEPUBPure opts doc
339+
340+writeEPUBPure :: WriterOptions -- ^ Writer options
341+ -> Pandoc -- ^ Document to convert
342+ -> EPUBAction B.ByteString
343+writeEPUBPure opts doc@(Pandoc meta _) = do
337344 let version = fromMaybe EPUB2 (writerEpubVersion opts)
338345 let epub3 = version == EPUB3
339− epochtime <- floor `fmap` getPOSIXTime
346+ epochtime <- floor <$> P.getPOSIXTime
340347 let mkEntry path content = toEntry path epochtime content
341348 let vars = ("epub3", if epub3 then "true" else "false")
342349 : ("css", "stylesheet.css")
@@ -362,7 +369,7 @@ writeEPUB opts doc@(Pandoc meta _) = do
362369 let cpContent = renderHtml $ writeHtml
363370 opts'{ writerVariables = ("coverpage","true"):vars }
364371 (Pandoc meta [RawBlock (Format "html") $ "<div id=\"cover-image\">\n<img src=\"" ++ coverImage ++ "\" alt=\"cover image\" />\n</div>"])
365− imgContent <- B.readFile img
372+ imgContent <- P.readFileLazy img
366373 return ( [mkEntry "cover.xhtml" cpContent]
367374 , [mkEntry coverImage imgContent] )
368375
@@ -373,18 +380,18 @@ writeEPUB opts doc@(Pandoc meta _) = do
373380 let tpEntry = mkEntry "title_page.xhtml" tpContent
374381
375382 -- handle pictures
376− mediaRef <- newIORef []
383+ mediaRef <- P.newIORef []
377384 Pandoc _ blocks <- walkM (transformInline opts' mediaRef) doc >>=
378385 walkM (transformBlock opts' mediaRef)
379− picEntries <- (catMaybes . map (snd . snd)) <$> readIORef mediaRef
386+ picEntries <- (catMaybes . map (snd . snd)) <$> P.readIORef mediaRef
380387
381388 -- handle fonts
382389 let matchingGlob f = do
383− xs <- namesMatching f
390+ xs <- P.namesMatching f
384391 when (null xs) $
385− warn $ f ++ " did not match any font files."
392+ P.warn $ f ++ " did not match any font files."
386393 return xs
387− let mkFontEntry f = mkEntry (takeFileName f) `fmap` B.readFile f
394+ let mkFontEntry f = mkEntry (takeFileName f) `fmap` P.readFileLazy f
388395 fontFiles <- concat <$> mapM matchingGlob (writerEpubFonts opts')
389396 fontEntries <- mapM mkFontEntry fontFiles
390397
@@ -521,7 +528,7 @@ writeEPUB opts doc@(Pandoc meta _) = do
521528 let uuid = case epubIdentifier metadata of
522529 (x:_) -> identifierText x -- use first identifier as UUID
523530 [] -> error "epubIdentifier is null" -- shouldn't happen
524− currentTime <- getCurrentTime
531+ currentTime <- P.getCurrentTime
525532 let contentsData = UTF8.fromStringLazy $ ppTopElement $
526533 unode "package" ! [("version", case version of
527534 EPUB2 -> "2.0"
@@ -693,10 +700,10 @@ writeEPUB opts doc@(Pandoc meta _) = do
693700
694701 -- stylesheet
695702 stylesheet <- case epubStylesheet metadata of
696− Just (StylesheetPath fp) -> UTF8.readFile fp
703+ Just (StylesheetPath fp) -> P.readFileUTF8 fp
697704 Just (StylesheetContents s) -> return s
698705 Nothing -> UTF8.toString `fmap`
699− readDataFile (writerUserDataDir opts) "epub.css"
706+ P.readDataFile (writerUserDataDir opts) "epub.css"
700707 let stylesheetEntry = mkEntry "stylesheet.css" $ UTF8.fromStringLazy stylesheet
701708
702709 -- construct archive
@@ -815,7 +822,7 @@ showDateTimeISO8601 = formatTime defaultTimeLocale "%FT%TZ"
815822transformTag :: WriterOptions
816823 -> IORef [(FilePath, (FilePath, Maybe Entry))] -- ^ (oldpath, newpath, entry) media
817824 -> Tag String
818− -> IO (Tag String)
825+ -> EPUBAction (Tag String)
819826transformTag opts mediaRef tag@(TagOpen name attr)
820827 | name `elem` ["video", "source", "img", "audio"] &&
821828 lookup "data-external" attr == Nothing = do
@@ -832,34 +839,34 @@ transformTag _ _ tag = return tag
832839modifyMediaRef :: WriterOptions
833840 -> IORef [(FilePath, (FilePath, Maybe Entry))]
834841 -> FilePath
835− -> IO FilePath
842+ -> EPUBAction FilePath
836843modifyMediaRef _ _ "" = return ""
837844modifyMediaRef opts mediaRef oldsrc = do
838− media <- readIORef mediaRef
845+ media <- P.readIORef mediaRef
839846 case lookup oldsrc media of
840847 Just (n,_) -> return n
841848 Nothing -> do
842− res <- fetchItem' (writerMediaBag opts)
849+ res <- P.fetchItem' (writerMediaBag opts)
843850 (writerSourceURL opts) oldsrc
844851 (new, mbEntry) <-
845852 case res of
846853 Left _ -> do
847− warn $ "Could not find media `" ++ oldsrc ++ "', skipping..."
854+ P.warn $ "Could not find media `" ++ oldsrc ++ "', skipping..."
848855 return (oldsrc, Nothing)
849856 Right (img,mbMime) -> do
850857 let new = "media/file" ++ show (length media) ++
851858 fromMaybe (takeExtension (takeWhile (/='?') oldsrc))
852859 (('.':) <$> (mbMime >>= extensionFromMimeType))
853− epochtime <- floor `fmap` getPOSIXTime
860+ epochtime <- floor `fmap` P.getPOSIXTime
854861 let entry = toEntry new epochtime $ B.fromChunks . (:[]) $ img
855862 return (new, Just entry)
856− modifyIORef mediaRef ( (oldsrc, (new, mbEntry)): )
863+ P.modifyIORef mediaRef ( (oldsrc, (new, mbEntry)): )
857864 return new
858865
859866transformBlock :: WriterOptions
860867 -> IORef [(FilePath, (FilePath, Maybe Entry))] -- ^ (oldpath, newpath, entry) media
861868 -> Block
862− -> IO Block
869+ -> EPUBAction Block
863870transformBlock opts mediaRef (RawBlock fmt raw)
864871 | fmt == Format "html" = do
865872 let tags = parseTags raw
@@ -870,7 +877,7 @@ transformBlock _ _ b = return b
870877transformInline :: WriterOptions
871878 -> IORef [(FilePath, (FilePath, Maybe Entry))] -- ^ (oldpath, newpath) media
872879 -> Inline
873− -> IO Inline
880+ -> EPUBAction Inline
874881transformInline opts mediaRef (Image attr lab (src,tit)) = do
875882 newsrc <- modifyMediaRef opts mediaRef src
876883 return $ Image attr lab (newsrc, tit)