packages feed

epub-metadata 2.1.0 → 2.2.0.0

raw patch · 7 files changed

+157/−139 lines, 7 filesdep +directorydep +filepathPVP ok

version bump matches the API change (PVP)

Dependencies added: directory, filepath

API changes (from Hackage documentation)

- Codec.Epub.IO: opfContents :: (MonadError String m, MonadIO m) => FilePath -> m String
+ Codec.Epub.Archive: mkEpubArchive :: FilePath -> IO Archive
+ Codec.Epub.Archive: readArchive :: FilePath -> IO Archive
+ Codec.Epub.Archive: writeArchive :: FilePath -> Archive -> IO ()
+ Codec.Epub.IO: opfContentsFromBS :: (MonadError String m, MonadIO m) => ByteString -> m (FilePath, String)
+ Codec.Epub.IO: opfContentsFromDir :: (MonadError String m, MonadIO m) => FilePath -> m (FilePath, String)
+ Codec.Epub.IO: opfContentsFromZip :: (MonadError String m, MonadIO m) => FilePath -> m (FilePath, String)
+ Codec.Epub.IO: removeDoctype :: String -> String
+ Codec.Epub.IO: removeEncoding :: String -> String
- Codec.Epub.Opf.Parse: parseXmlToOpf :: MonadIO m => String -> m [Package]
+ Codec.Epub.Opf.Parse: parseXmlToOpf :: (MonadIO m, MonadError String m) => String -> m Package

Files

epub-metadata.cabal view
@@ -1,5 +1,5 @@ name:                epub-metadata-version:             2.1.0+version:             2.2.0.0 cabal-version:       >= 1.2 build-type:          Simple license:             BSD3@@ -7,20 +7,16 @@ copyright:           2010, 2011 Dino Morelli author:              Dino Morelli maintainer:          Dino Morelli <dino@ui3.info>-stability:           experimental+stability:           stable homepage:            http://ui3.info/d/proj/epub-metadata.html-synopsis:            Library and utility for parsing and manipulating ePub OPF package data-description:         Library and utility for parsing and manipulating ePub -                     OPF package data. An attempt has been made here to -                     very thoroughly implement the OPF Package Document -                     specification. Also included is a command-line -                     utility to dump OPF package data to stdout in a -                     human-readable form.+synopsis:            Library for parsing and manipulating ePub files and OPF package data+description:         Library for parsing and manipulating ePub files and OPF package data. An attempt has been made here to very thoroughly implement the OPF Package Document specification. category:            Codec, Text tested-with:         GHC >= 6.12.3  library-   exposed-modules:  Codec.Epub.IO,+   exposed-modules:  Codec.Epub.Archive+                     Codec.Epub.IO,                      Codec.Epub.Opf.Common,                      Codec.Epub.Opf.Format.Guide,                      Codec.Epub.Opf.Format.Manifest,@@ -36,11 +32,6 @@                      Codec.Epub.Opf.Parse    hs-source-dirs:   src    build-depends:    base >= 3 && < 5, bytestring, containers,-                     hxt >= 9, mtl, regex-compat, zip-archive-   ghc-options:      -Wall--executable           epubmeta-   main-is:          Codec/Epub/Opf/Cli/epubmeta.hs-   hs-source-dirs:   src-   build-depends:    base >= 3 && < 5+                     directory, filepath, hxt >= 9, mtl, regex-compat,+                     zip-archive    ghc-options:      -Wall
+ src/Codec/Epub/Archive.hs view
@@ -0,0 +1,58 @@+-- Copyright: 2011 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++module Codec.Epub.Archive+   ( mkEpubArchive+   , readArchive+   , writeArchive+   )+   where++import Codec.Archive.Zip+import Control.Monad+import qualified Data.ByteString.Lazy as B+import Data.List+import System.Directory+import System.FilePath+++{- Recursively get a list of all files starting with the supplied+   parent directory. Excluding the directories themselves and ANY +   dotfiles.+-}+getRecursiveContents :: FilePath -> IO [FilePath]+getRecursiveContents parent = do+   fullContents <- getDirectoryContents parent+   let contents = filter (not . isPrefixOf ".") fullContents+   paths <- forM contents $ \name -> do+      let path = parent </> name+      isDirectory <- doesDirectoryExist path+      if isDirectory+         then getRecursiveContents path+         else return [path]+   return $ concat paths+++{- | Construct a zip Archive containing epub book data from the +     specified directory+-}+mkEpubArchive :: FilePath -> IO Archive+mkEpubArchive rootDir = do+   setCurrentDirectory rootDir++   allFiles <- getRecursiveContents "."++   flip (addFilesToArchive []) ["mimetype"] >=>+      flip (addFilesToArchive [OptRecursive]) allFiles+      $ emptyArchive+++-- | Read a zip Archive from disk+readArchive :: FilePath -> IO Archive+readArchive = fmap toArchive . B.readFile+++-- | Write a zip Archive to disk using the specified filename+writeArchive :: FilePath -> Archive -> IO ()+writeArchive zipPath = (B.writeFile zipPath) . fromArchive
src/Codec/Epub/IO.hs view
@@ -7,13 +7,20 @@ {- | Functions for doing some disk IO with ePub documents -} module Codec.Epub.IO-   ( opfContents )+   ( opfContentsFromZip+   , opfContentsFromBS+   , opfContentsFromDir+   , removeEncoding+   , removeDoctype+   )    where  import Codec.Archive.Zip import Control.Arrow.ListArrows ( (>>>), deep ) import Control.Monad.Error import qualified Data.ByteString.Lazy.Char8 as B+import System.Directory+import System.FilePath import Text.Regex import Text.XML.HXT.Arrow.XmlArrow ( getAttrValue, hasName, isElem ) import Text.XML.HXT.Arrow.XmlState ( no, runX, withValidate )@@ -32,6 +39,21 @@    (mkRegexWithOpts "<!DOCTYPE [^>]*>" False True)) ""  +locateRootFile :: (MonadIO m, MonadError String m) =>+   FilePath -> String -> m FilePath+locateRootFile containerPath containerDoc = do+   result <- liftIO $ runX (+      readString [withValidate no] containerDoc+      >>> deep (isElem >>> hasName "rootfile")+      >>> getAttrValue "full-path"+      )++   case result of+      (p : []) -> return p+      _        -> throwError $+         "ERROR: rootfile full-path missing from " ++ containerPath++ -- | Extract a file from a zip archive throwing an error on failure fileFromArchive :: MonadError String m =>    FilePath -> Archive -> m String@@ -42,33 +64,57 @@       (return . B.unpack . fromEntry) mbEntry  --- | Get the contents of the OPF Package Document from an ePub file-opfContents :: (MonadError String m, MonadIO m)-   => FilePath    -- ^ path to ePub zip file-   -> m String    -- ^ contents of the OPF Package Document-opfContents zipPath = do+-- | Get the contents of the OPF Package Document from a ByteString+opfContentsFromBS :: (MonadError String m, MonadIO m)+   => B.ByteString            -- ^ contents of the zip file+   -> m (FilePath, String)    -- ^ path and contents of the OPF Package Document+opfContentsFromBS bytes = do+   let archive = toArchive bytes+    {- We need to first extract the container.xml file       It's required to have a certain path and name in the epub       and contains the path to what we really want, the .opf file.    -}-   zipFileBytes <- liftIO $ B.readFile zipPath-   let archive = toArchive zipFileBytes-    let containerPath = "META-INF/container.xml"    containerDoc <- fileFromArchive containerPath archive -   result <- liftIO $ runX (-      readString [withValidate no] containerDoc-      >>> deep (isElem >>> hasName "rootfile")-      >>> getAttrValue "full-path"-      )--   rootPath <- case result of-      (p : []) -> return p-      _        -> throwError $-         "ERROR: rootfile full-path missing from " ++ containerPath+   rootPath <- locateRootFile containerPath containerDoc     -- Now that we have the path to the .opf file, extract it-   rootDoc <- fileFromArchive rootPath archive+   rootContents <- fileFromArchive rootPath archive -   return . removeEncoding . removeDoctype $ rootDoc+   return (rootPath, rootContents)+++-- | Get the contents of the OPF Package Document from an ePub file+opfContentsFromZip :: (MonadError String m, MonadIO m)+   => FilePath                -- ^ path to ePub zip file+   -> m (FilePath, String)    -- ^ path and contents of the OPF Package Document+opfContentsFromZip zipPath = do+   {- Lazily read this file into a ByteString, send to +      opfContentsFromBS+   -}+   zipFileBytes <- liftIO $ B.readFile zipPath+   opfContentsFromBS zipFileBytes+++-- | Get the contents of the OPF Package Document from an ePub file+opfContentsFromDir :: (MonadError String m, MonadIO m)+   => FilePath                -- ^ directory path+   -> m (FilePath, String)    -- ^ path and contents of the OPF Package Document+opfContentsFromDir dir = do+   {- We need to first extract the container.xml file+      It's required to have a certain path and name in the epub+      and contains the path to what we really want, the .opf file.+   -}+   liftIO $ setCurrentDirectory dir++   let containerPath = "META-INF/container.xml"+   containerDoc <- liftIO $ readFile containerPath++   rootPath <- locateRootFile (dir </> containerPath) containerDoc++   -- Now that we have the path to the .opf file, load it+   rootContents <- liftIO $ readFile rootPath++   return (rootPath, rootContents)
− src/Codec/Epub/Opf/Cli/Opts.hs
@@ -1,56 +0,0 @@--- Copyright: 2010, 2011 Dino Morelli--- License: BSD3 (see LICENSE)--- Author: Dino Morelli <dino@ui3.info>--module Codec.Epub.Opf.Cli.Opts-   ( Options (..)-   , parseOpts, usageText-   )-   where--import System.Console.GetOpt---data Options = Options-   { optHelp :: Bool-   , optVerbose :: Bool-   }---defaultOptions :: Options-defaultOptions = Options-   { optHelp = False-   , optVerbose = False-   }---options :: [OptDescr (Options -> Options)]-options =-   [ Option ['h'] ["help"] -      (NoArg (\opts -> opts { optHelp = True } ))-      "This help text"-   , Option ['v'] ["verbose"]-      (NoArg (\opts -> opts { optVerbose = True } )) -      "Display all OPF package info, including manifest, spine and guide"-   ]---parseOpts :: [String] -> IO (Options, [String])-parseOpts argv = -   case getOpt Permute options argv of-      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)-      (_,_,errs) -> ioError $ userError (concat errs ++ usageText)---usageText :: String-usageText = (usageInfo header options) ++ "\n" ++ footer-   where-      header = init $ unlines-         [ "Usage: epubmeta [OPTIONS] EPUBFILE"-         , "Examine EPUB OPF package data"-         , ""-         , "Options:"-         ]-      footer = init $ unlines-         [ "Version 2.1.0  Dino Morelli <dino@ui3.info>"-         ]
− src/Codec/Epub/Opf/Cli/epubmeta.hs
@@ -1,25 +0,0 @@--- Copyright: 2010, 2011 Dino Morelli--- License: BSD3 (see LICENSE)--- Author: Dino Morelli <dino@ui3.info>--import Control.Monad.Error-import System.Environment ( getArgs )-import System.Exit--import Codec.Epub.Opf.Cli.Opts-import Codec.Epub.Opf.Format.Package-import Codec.Epub.Opf.Parse---main :: IO ()-main = do-   (opts, paths) <- getArgs >>= parseOpts--   when ((optHelp opts) || (null paths)) $ do-      putStrLn usageText-      exitWith $ ExitFailure 1--   let zipPath = head paths-   result <- runErrorT $ parseEpubOpf zipPath--   putStr $ either id (formatPackage (optVerbose opts)) result
src/Codec/Epub/Opf/Parse.hs view
@@ -250,22 +250,28 @@ {- | Extract the ePub OPF Package data contained in the supplied     XML string -}-parseXmlToOpf :: (MonadIO m) => String -> m [Package]-parseXmlToOpf contents =-   liftIO $ runX (-      readString [withValidate no] contents+parseXmlToOpf :: (MonadIO m, MonadError String m) =>+   String -> m Package+parseXmlToOpf contents = do+   {- Improper encoding and schema declarations have been causing+      havok with this parse, cruelly strip them out. -}+   let cleanedContents = removeEncoding . removeDoctype $ contents+   +   result <- liftIO $ runX (+      readString [withValidate no] cleanedContents       >>> propagateNamespaces       >>> getBookData       ) +   case result of+      (p : []) -> return p+      _        -> throwError+         "ERROR: Parse didn't result in a single document metadata" + -- | Given the path to an ePub file, extract the OPF Package data parseEpubOpf :: (MonadIO m, MonadError String m) =>    FilePath -> m Package parseEpubOpf zipPath = do-   result <- opfContents zipPath >>= parseXmlToOpf--   case result of-      (em : []) -> return em-      _         -> throwError-         "ERROR: Parse didn't result in a single document metadata"+   (_, contents) <- opfContentsFromZip zipPath+   parseXmlToOpf contents
testsuite/runtests.hs view
@@ -2,6 +2,7 @@ -- License: BSD3 (see LICENSE) -- Author: Dino Morelli <dino@ui3.info> +import Control.Monad.Error import System.FilePath import Test.HUnit ( Counts, Test (..), assertEqual, runTestTT ) import Test.HUnit.Base ( Assertion )@@ -27,9 +28,9 @@ testFull :: Test testFull = TestCase $ do    xmlString <- readFile $ "testsuite" </> "testFull.opf"-   actual <- parseXmlToOpf xmlString+   actual <- runErrorT $ parseXmlToOpf xmlString    let expected =-         [ Package+         Right Package             { opVersion = "2.0"             , opUniqueId = "isbn"             , opMeta = Metadata@@ -118,7 +119,6 @@                   }                ]             }-         ]    assertEqual "very full" expected actual  @@ -127,10 +127,10 @@ -} testMinimal :: Test testMinimal = TestCase $ do-   xmlString <- readFile $ "testsuite" </> "testMinimal.opf"-   actual <- parseXmlToOpf xmlString+   xmlString <- liftIO $ readFile $ "testsuite" </> "testMinimal.opf"+   actual <- runErrorT $ parseXmlToOpf xmlString    let expected = -         [ Package +         Right Package              { opVersion = "2.0"             , opUniqueId = "isbn"             , opMeta = Metadata @@ -160,7 +160,6 @@             , opSpine = Spine {spineToc = "ncx", spineItemrefs = []}             , opGuide = []             }-         ]    assertEqual "minimal" expected actual  @@ -170,9 +169,9 @@ testMissingAll :: Test testMissingAll = TestCase $ do    xmlString <- readFile $ "testsuite" </> "testMissingAll.opf"-   actual <- parseXmlToOpf xmlString+   actual <- runErrorT $ parseXmlToOpf xmlString    let expected =-         [ Package +         Right Package              { opVersion = ""             , opUniqueId = ""             , opMeta = emptyMetadata@@ -180,5 +179,4 @@             , opSpine = Spine {spineToc = "", spineItemrefs = []}             , opGuide = []             }-         ]    assertEqual "missing all" expected actual