@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12{-# LANGUAGE DeriveFunctor #-}
23{-# LANGUAGE TypeSynonymInstances #-}
34{-# LANGUAGE FlexibleInstances #-}
@@ -71,8 +72,15 @@ module Text.Pandoc.Class ( PandocMonad(..)
7172 , runIO
7273 , runIOorExplode
7374 , runPure
75+ , readDefaultDataFile
76+ , readDataFile
7477 , fillMediaBag
7578 , extractMedia
79+ , toLang
80+ , setTranslations
81+ , translateTerm
82+ , Translations(..)
83+ , Term(..)
7684 ) where
7785
7886import Prelude hiding (readFile)
@@ -81,10 +89,9 @@ import qualified System.Random as IO (newStdGen)
8189import Codec.Archive.Zip (Archive, fromArchive, emptyArchive)
8290import Data.Unique (hashUnique)
8391import qualified Data.Unique as IO (newUnique)
84−import qualified Text.Pandoc.Shared as IO ( readDataFile
85− , readDefaultDataFile
86− , openURL )
92+import qualified Text.Pandoc.Shared as Shared
8793import qualified Text.Pandoc.UTF8 as UTF8
94+import qualified System.Directory as Directory
8895import Text.Pandoc.Compat.Time (UTCTime)
8996import Text.Pandoc.Logging
9097import Text.Parsec (ParsecT, getPosition, sourceLine, sourceName)
@@ -98,6 +105,7 @@ import Data.Time.Clock.POSIX ( utcTimeToPOSIXSeconds
98105 , posixSecondsToUTCTime
99106 , POSIXTime )
100107import Data.Time.LocalTime (TimeZone, ZonedTime, utcToZonedTime, utc)
108+import qualified System.FilePath.Posix as Posix
101109import Network.URI ( escapeURIString, nonStrictRelativeTo,
102110 unEscapeString, parseURIReference, isAllowedInURI,
103111 parseURI, URI(..) )
@@ -111,7 +119,7 @@ import qualified System.Environment as IO (lookupEnv)
111119import System.FilePath.Glob (match, compile)
112120import System.Directory (createDirectoryIfMissing, getDirectoryContents,
113121 doesDirectoryExist)
114−import System.FilePath ((</>), (<.>), takeDirectory,
122+import System.FilePath ((</>), (<.>), takeDirectory, splitDirectories,
115123 takeExtension, dropExtension, isRelative, normalise)
116124import qualified System.FilePath.Glob as IO (glob)
117125import qualified System.Directory as IO (getModificationTime)
@@ -121,13 +129,21 @@ import Control.Monad.State.Strict
121129import Control.Monad.Except
122130import Control.Monad.Writer (WriterT)
123131import Control.Monad.RWS (RWST)
132+import Codec.Archive.Zip
124133import Data.Word (Word8)
125134import Data.Default
126135import System.IO.Error
127136import System.IO (stderr)
128137import qualified Data.Map as M
129138import Text.Pandoc.Error
139+import Text.Pandoc.BCP47 (Lang(..), parseBCP47, renderLang)
140+import Text.Pandoc.Translations (Term(..), Translations(..), readTranslations)
130141import qualified Debug.Trace
142+#ifdef EMBED_DATA_FILES
143+import Text.Pandoc.Data (dataFiles)
144+#else
145+import Paths_pandoc (getDataFileName)
146+#endif
131147
132148-- | The PandocMonad typeclass contains all the potentially
133149-- IO-related functions used in pandoc's readers and writers.
@@ -155,15 +171,12 @@ class (Functor m, Applicative m, Monad m, MonadError PandocError m)
155171 -- | Read the strict ByteString contents from a file path,
156172 -- raising an error on failure.
157173 readFileStrict :: FilePath -> m B.ByteString
158− -- | Read file from from Cabal data directory.
159− readDefaultDataFile :: FilePath -> m B.ByteString
160− -- | Read file from user data directory or,
161− -- if not found there, from Cabal data directory.
162− readDataFile :: FilePath -> m B.ByteString
163174 -- | Return a list of paths that match a glob, relative to
164175 -- the working directory. See 'System.FilePath.Glob' for
165176 -- the glob syntax.
166177 glob :: String -> m [FilePath]
178+ -- | Returns True if file exists.
179+ fileExists :: FilePath -> m Bool
167180 -- | Return the modification time of a file.
168181 getModificationTime :: FilePath -> m UTCTime
169182 -- | Get the value of the 'CommonState' used by all instances
@@ -272,6 +285,9 @@ data CommonState = CommonState { stLog :: [LogMessage]
272285 -- ^ Directory to search for data files
273286 , stMediaBag :: MediaBag
274287 -- ^ Media parsed from binary containers
288+ , stTranslations :: Maybe
289+ (Lang, Maybe Translations)
290+ -- ^ Translations for localization
275291 , stInputFiles :: Maybe [FilePath]
276292 -- ^ List of input files from command line
277293 , stOutputFile :: Maybe FilePath
@@ -290,6 +306,7 @@ instance Default CommonState where
290306 def = CommonState { stLog = []
291307 , stUserDataDir = Nothing
292308 , stMediaBag = mempty
309+ , stTranslations = Nothing
293310 , stInputFiles = Nothing
294311 , stOutputFile = Nothing
295312 , stResourcePath = ["."]
@@ -297,6 +314,71 @@ instance Default CommonState where
297314 , stTrace = False
298315 }
299316
317+-- | Convert BCP47 string to a Lang, issuing warning
318+-- if there are problems.
319+toLang :: PandocMonad m => Maybe String -> m (Maybe Lang)
320+toLang Nothing = return Nothing
321+toLang (Just s) =
322+ case parseBCP47 s of
323+ Left _ -> do
324+ report $ InvalidLang s
325+ return Nothing
326+ Right l -> return (Just l)
327+
328+-- | Select the language to use with 'translateTerm'.
329+-- Note that this does not read a translation file;
330+-- that is only done the first time 'translateTerm' is
331+-- used.
332+setTranslations :: PandocMonad m => Lang -> m ()
333+setTranslations lang =
334+ modifyCommonState $ \st -> st{ stTranslations = Just (lang, Nothing) }
335+
336+-- | Load term map.
337+getTranslations :: PandocMonad m => m Translations
338+getTranslations = do
339+ mbtrans <- getsCommonState stTranslations
340+ case mbtrans of
341+ Nothing -> return mempty -- no language defined
342+ Just (_, Just t) -> return t
343+ Just (lang, Nothing) -> do -- read from file
344+ let translationFile = "translations/" ++ renderLang lang ++ ".trans"
345+ let fallbackFile = "translations/" ++ langLanguage lang ++ ".trans"
346+ let getTrans bs =
347+ case readTranslations (UTF8.toString bs) of
348+ Left e -> do
349+ report $ CouldNotLoadTranslations (renderLang lang) e
350+ -- make sure we don't try again...
351+ modifyCommonState $ \st ->
352+ st{ stTranslations = Nothing }
353+ return mempty
354+ Right t -> do
355+ modifyCommonState $ \st ->
356+ st{ stTranslations = Just (lang, Just t) }
357+ return t
358+ catchError (readDataFile translationFile >>= getTrans)
359+ (\_ ->
360+ catchError (readDataFile fallbackFile >>= getTrans)
361+ (\e -> do
362+ report $ CouldNotLoadTranslations (renderLang lang)
363+ $ case e of
364+ PandocCouldNotFindDataFileError _ ->
365+ ("data file " ++ fallbackFile ++ " not found")
366+ _ -> ""
367+ -- make sure we don't try again...
368+ modifyCommonState $ \st -> st{ stTranslations = Nothing }
369+ return mempty))
370+
371+-- | Get a translation from the current term map.
372+-- Issue a warning if the term is not defined.
373+translateTerm :: PandocMonad m => Term -> m String
374+translateTerm term = do
375+ Translations termMap <- getTranslations
376+ case M.lookup term termMap of
377+ Just s -> return s
378+ Nothing -> do
379+ report $ NoTranslation (show term)
380+ return ""
381+
300382-- | Evaluate a 'PandocIO' operation.
301383runIO :: PandocIO a -> IO (Either PandocError a)
302384runIO ma = flip evalStateT def $ runExceptT $ unPandocIO ma
@@ -317,7 +399,7 @@ liftIOError :: (String -> IO a) -> String -> PandocIO a
317399liftIOError f u = do
318400 res <- liftIO $ tryIOError $ f u
319401 case res of
320− Left e -> throwError $ PandocIOError u e
402+ Left e -> throwError $ PandocIOError u e
321403 Right r -> return r
322404
323405instance PandocMonad PandocIO where
@@ -328,17 +410,15 @@ instance PandocMonad PandocIO where
328410 newUniqueHash = hashUnique <$> liftIO IO.newUnique
329411 openURL u = do
330412 report $ Fetching u
331− res <- liftIO (IO.openURL u)
413+ res <- liftIOError Shared.openURL u
332414 case res of
333415 Right r -> return r
334416 Left e -> throwError $ PandocHttpError u e
335417 readFileLazy s = liftIOError BL.readFile s
336418 readFileStrict s = liftIOError B.readFile s
337− readDefaultDataFile fname = liftIOError IO.readDefaultDataFile fname
338− readDataFile fname = do
339− datadir <- getUserDataDir
340− liftIOError (IO.readDataFile datadir) fname
341− glob = liftIO . IO.glob
419+
420+ glob = liftIOError IO.glob
421+ fileExists = liftIOError Directory.doesFileExist
342422 getModificationTime fp = liftIOError IO.getModificationTime fp
343423 getCommonState = PandocIO $ lift get
344424 putCommonState x = PandocIO $ lift $ put x
@@ -432,6 +512,109 @@ downloadOrRead sourceURL s =
432512 convertSlash '\\' = '/'
433513 convertSlash x = x
434514
515+getDefaultReferenceDocx :: PandocMonad m => m Archive
516+getDefaultReferenceDocx = do
517+ let paths = ["[Content_Types].xml",
518+ "_rels/.rels",
519+ "docProps/app.xml",
520+ "docProps/core.xml",
521+ "word/document.xml",
522+ "word/fontTable.xml",
523+ "word/footnotes.xml",
524+ "word/numbering.xml",
525+ "word/settings.xml",
526+ "word/webSettings.xml",
527+ "word/styles.xml",
528+ "word/_rels/document.xml.rels",
529+ "word/_rels/footnotes.xml.rels",
530+ "word/theme/theme1.xml"]
531+ let toLazy = BL.fromChunks . (:[])
532+ let pathToEntry path = do
533+ epochtime <- (floor . utcTimeToPOSIXSeconds) <$> getCurrentTime
534+ contents <- toLazy <$> readDataFile ("docx/" ++ path)
535+ return $ toEntry path epochtime contents
536+ datadir <- getUserDataDir
537+ mbArchive <- case datadir of
538+ Nothing -> return Nothing
539+ Just d -> do
540+ exists <- fileExists (d </> "reference.docx")
541+ if exists
542+ then return (Just (d </> "reference.docx"))
543+ else return Nothing
544+ case mbArchive of
545+ Just arch -> toArchive <$> readFileLazy arch
546+ Nothing -> foldr addEntryToArchive emptyArchive <$>
547+ mapM pathToEntry paths
548+
549+getDefaultReferenceODT :: PandocMonad m => m Archive
550+getDefaultReferenceODT = do
551+ let paths = ["mimetype",
552+ "manifest.rdf",
553+ "styles.xml",
554+ "content.xml",
555+ "meta.xml",
556+ "settings.xml",
557+ "Configurations2/accelerator/current.xml",
558+ "Thumbnails/thumbnail.png",
559+ "META-INF/manifest.xml"]
560+ let pathToEntry path = do epochtime <- floor `fmap` getPOSIXTime
561+ contents <- (BL.fromChunks . (:[])) `fmap`
562+ readDataFile ("odt/" ++ path)
563+ return $ toEntry path epochtime contents
564+ datadir <- getUserDataDir
565+ mbArchive <- case datadir of
566+ Nothing -> return Nothing
567+ Just d -> do
568+ exists <- fileExists (d </> "reference.odt")
569+ if exists
570+ then return (Just (d </> "reference.odt"))
571+ else return Nothing
572+ case mbArchive of
573+ Just arch -> toArchive <$> readFileLazy arch
574+ Nothing -> foldr addEntryToArchive emptyArchive <$>
575+ mapM pathToEntry paths
576+
577+-- | Read file from user data directory or,
578+-- if not found there, from Cabal data directory.
579+readDataFile :: PandocMonad m => FilePath -> m B.ByteString
580+readDataFile fname = do
581+ datadir <- getUserDataDir
582+ case datadir of
583+ Nothing -> readDefaultDataFile fname
584+ Just userDir -> do
585+ exists <- fileExists (userDir </> fname)
586+ if exists
587+ then readFileStrict (userDir </> fname)
588+ else readDefaultDataFile fname
589+
590+-- | Read file from from Cabal data directory.
591+readDefaultDataFile :: PandocMonad m => FilePath -> m B.ByteString
592+readDefaultDataFile "reference.docx" =
593+ (B.concat . BL.toChunks . fromArchive) <$> getDefaultReferenceDocx
594+readDefaultDataFile "reference.odt" =
595+ (B.concat . BL.toChunks . fromArchive) <$> getDefaultReferenceODT
596+readDefaultDataFile fname =
597+#ifdef EMBED_DATA_FILES
598+ case lookup (makeCanonical fname) dataFiles of
599+ Nothing -> throwError $ PandocCouldNotFindDataFileError fname
600+ Just contents -> return contents
601+ where makeCanonical = Posix.joinPath . transformPathParts . splitDirectories
602+ transformPathParts = reverse . foldl go []
603+ go as "." = as
604+ go (_:as) ".." = as
605+ go as x = x : as
606+#else
607+ getDataFileName fname' >>= checkExistence >>= readFileStrict
608+ where fname' = if fname == "MANUAL.txt" then fname else "data" </> fname
609+
610+checkExistence :: PandocMonad m => FilePath -> m FilePath
611+checkExistence fn = do
612+ exists <- fileExists fn
613+ if exists
614+ then return fn
615+ else throwError $ PandocCouldNotFindDataFileError fn
616+#endif
617+
435618withPaths :: PandocMonad m => [FilePath] -> (FilePath -> m a) -> FilePath -> m a
436619withPaths [] _ fp = throwError $ PandocResourceNotFound fp
437620withPaths (p:ps) action fp =
@@ -491,9 +674,8 @@ writeMedia dir mediabag subpath = do
491674 Nothing -> throwError $ PandocResourceNotFound subpath
492675 Just (_, bs) -> do
493676 report $ Extracting fullpath
494− liftIO $ do
495− createDirectoryIfMissing True $ takeDirectory fullpath
496− BL.writeFile fullpath bs
677+ liftIOError (createDirectoryIfMissing True) (takeDirectory fullpath)
678+ liftIOError (\p -> BL.writeFile p bs) fullpath
497679
498680adjustImagePath :: FilePath -> [FilePath] -> Inline -> Inline
499681adjustImagePath dir paths (Image attr lab (src, tit))
@@ -624,28 +806,17 @@ instance PandocMonad PandocPure where
624806 case infoFileContents <$> getFileInfo fp fps of
625807 Just bs -> return bs
626808 Nothing -> throwError $ PandocResourceNotFound fp
627− readDefaultDataFile "reference.docx" =
628− (B.concat . BL.toChunks . fromArchive) <$> getsPureState stReferenceDocx
629− readDefaultDataFile "reference.odt" =
630− (B.concat . BL.toChunks . fromArchive) <$> getsPureState stReferenceODT
631− readDefaultDataFile fname = do
632− let fname' = if fname == "MANUAL.txt" then fname else "data" </> fname
633− readFileStrict fname'
634− readDataFile fname = do
635− datadir <- getUserDataDir
636− case datadir of
637− Just userDir -> do
638− userDirFiles <- getsPureState stUserDataFiles
639− case infoFileContents <$> getFileInfo (userDir </> fname)
640− userDirFiles of
641− Just bs -> return bs
642− Nothing -> readDefaultDataFile fname
643− Nothing -> readDefaultDataFile fname
644809
645810 glob s = do
646811 FileTree ftmap <- getsPureState stFiles
647812 return $ filter (match (compile s)) $ M.keys ftmap
648813
814+ fileExists fp = do
815+ fps <- getsPureState stFiles
816+ case getFileInfo fp fps of
817+ Nothing -> return False
818+ Just _ -> return True
819+
649820 getModificationTime fp = do
650821 fps <- getsPureState stFiles
651822 case infoFileMTime <$> getFileInfo fp fps of
@@ -667,9 +838,8 @@ instance PandocMonad m => PandocMonad (ParsecT s st m) where
667838 openURL = lift . openURL
668839 readFileLazy = lift . readFileLazy
669840 readFileStrict = lift . readFileStrict
670− readDefaultDataFile = lift . readDefaultDataFile
671− readDataFile = lift . readDataFile
672841 glob = lift . glob
842+ fileExists = lift . fileExists
673843 getModificationTime = lift . getModificationTime
674844 getCommonState = lift getCommonState
675845 putCommonState = lift . putCommonState
@@ -696,9 +866,8 @@ instance PandocMonad m => PandocMonad (ReaderT r m) where
696866 openURL = lift . openURL
697867 readFileLazy = lift . readFileLazy
698868 readFileStrict = lift . readFileStrict
699− readDefaultDataFile = lift . readDefaultDataFile
700− readDataFile = lift . readDataFile
701869 glob = lift . glob
870+ fileExists = lift . fileExists
702871 getModificationTime = lift . getModificationTime
703872 getCommonState = lift getCommonState
704873 putCommonState = lift . putCommonState
@@ -713,9 +882,8 @@ instance (PandocMonad m, Monoid w) => PandocMonad (WriterT w m) where
713882 openURL = lift . openURL
714883 readFileLazy = lift . readFileLazy
715884 readFileStrict = lift . readFileStrict
716− readDefaultDataFile = lift . readDefaultDataFile
717− readDataFile = lift . readDataFile
718885 glob = lift . glob
886+ fileExists = lift . fileExists
719887 getModificationTime = lift . getModificationTime
720888 getCommonState = lift getCommonState
721889 putCommonState = lift . putCommonState
@@ -730,9 +898,8 @@ instance (PandocMonad m, Monoid w) => PandocMonad (RWST r w st m) where
730898 openURL = lift . openURL
731899 readFileLazy = lift . readFileLazy
732900 readFileStrict = lift . readFileStrict
733− readDefaultDataFile = lift . readDefaultDataFile
734− readDataFile = lift . readDataFile
735901 glob = lift . glob
902+ fileExists = lift . fileExists
736903 getModificationTime = lift . getModificationTime
737904 getCommonState = lift getCommonState
738905 putCommonState = lift . putCommonState
@@ -747,9 +914,8 @@ instance PandocMonad m => PandocMonad (StateT st m) where
747914 openURL = lift . openURL
748915 readFileLazy = lift . readFileLazy
749916 readFileStrict = lift . readFileStrict
750− readDefaultDataFile = lift . readDefaultDataFile
751− readDataFile = lift . readDataFile
752917 glob = lift . glob
918+ fileExists = lift . fileExists
753919 getModificationTime = lift . getModificationTime
754920 getCommonState = lift getCommonState
755921 putCommonState = lift . putCommonState