epub-metadata (empty) → 1.0.2
raw patch · 16 files changed
+1033/−0 lines, 16 filesdep +HSHdep +basedep +hxtsetup-changed
Dependencies added: HSH, base, hxt, mtl
Files
- LICENSE +31/−0
- README +107/−0
- Setup.hs +39/−0
- TODO +1/−0
- epub-metadata.cabal +36/−0
- src/Codec/Epub/IO.hs +57/−0
- src/Codec/Epub/Opf/Metadata.hs +92/−0
- src/Codec/Epub/Opf/Metadata/Format.hs +131/−0
- src/Codec/Epub/Opf/Metadata/Parse.hs +210/−0
- src/Codec/Epub/Opf/epub-meta.hs +24/−0
- testsuite/runtests.hs +118/−0
- testsuite/testFull.opf +39/−0
- testsuite/testMinimal.opf +17/−0
- testsuite/testMissingAll.opf +11/−0
- util/gentags.sh +3/−0
- util/prefs/boring +117/−0
+ LICENSE view
@@ -0,0 +1,31 @@+Copyright Dino Morelli 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Dino Morelli nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ README view
@@ -0,0 +1,107 @@+-----+Building:++ Easy with cabal-install, of course:++ $ cabal install epub-metadata++ Or the conventional way:++ $ runhaskell Setup.hs configure+ $ runhaskell Setup.hs build+ $ runhaskell Setup.hs test+ $ runhaskell Setup.hs haddock+ $ runhaskell Setup.hs install+++-----+Why was this done?++ The motivation for this project grew out of my desire to take charge+ of missing or incorrect ePub metadata in books I have purchased. I+ started out using the Calibre open source tools for examining this+ info. Limitations and incomplete implementation of those tools led+ me here to build a more complete implementation in the programming+ language that I love beyond all others.+++-----+Why didn't I just use existing solutions?++ - Calibre ebook-meta utility++ I experienced various problems using this software, such as:++ Incomplete and in some cases incorrect handling of tags that can+ exist more than once, particularly when they are differentiated+ using attributes according to the spec.++ Unable to display many fields in the OPF Package Document metadata+ specification. Unable to manipulate data that is represented as+ attributes of tags in the OPF spec.++ Astonishingly slow performance. The command-line tool in this+ new Haskell project is more than 45 times faster at parsing+ and displaying ePub metadata. I'm going to blame Python here for+ Calibre's performance. This has had a big impact on projects where+ I've been processing hundreds of ePubs in batch operations.++ To be fair, an effort is being made in Calibre to work with both+ ePub and Sony LRF book documents. That is going to naturally require+ a lowest-common-denominator approach. My focus here was to work+ with ePub only, and thoroughly support the OPF specification.+++ - epub on Hackage, EPUB E-Book construction support library++ The focus of this project seems to be with building new documents,+ not parsing existing files. And there is a specific attempt to+ do more than the metadata, to gather up the content and other+ metafiles that make up an ePub for creation.++ Examining Codec.Ebook.OPF.Types, most of the metadata fields+ from the OPF Package Document spec are missing or aren't modeled+ thoroughly. I felt to contribute to this project, I would have+ had to significantly rip up the types and redesign them.++ At this time I felt it was a better solution for me to start fresh+ with modelling these types and code to manipulate them. That said,+ I would be very interested in combining the epub and epub-metadata+ projects at some point in some way that makes sense.+++ - zip-archive on Hackage++ I started out using zip-archive for this work but soon found that+ it has problems with some poorly-made ePub files. Nearly 300 or 40%+ of the books I have are unreadable at this time with zip-archive.++ As ugly as it sounds, I found the most reliable solution right+ now is to use the unzip shell utility to extract the relevant XML+ documents from ePub books. This is why the dependency on HSH. I'm+ not thrilled with this situation and would like to find time to+ dig into the zip file spec and submit patches to zip-archive.++ But also note that epub-meta is obscenely fast even with invoking+ a shell for unzipping. Haskell FTW!+++-----+A word about the version numbering scheme:++ 4-part: major.minor.status.build+ 3-part: major.status.build++ status:+ 0 alpha+ 1 beta+ 2 release candidate+ 3 release++ examples:+ 1.3.0.2 v1.3 alpha build 2+ 1.2.1.0 v1.2 beta build 0+ 4.2.24 v4 release candidate build 24+ 2.10.3.5 v2.10 release build 5 (say they were bug fixes)+ 1.5.2.20090818 Can even use a date for build+ v1.5 release candidate 2009-08-18 build
+ Setup.hs view
@@ -0,0 +1,39 @@+#! /usr/bin/env runhaskell++-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import Control.Monad ( unless )+import Distribution.Simple+import System.Cmd ( system )+import System.Directory ( createDirectoryIfMissing )+import System.FilePath+import System.Posix.Files ( createSymbolicLink, fileExist )+++main = defaultMainWithHooks (simpleUserHooks + { postBuild = customPostBuild+ , runTests = testRunner+ } )++ where+ -- Create symlink to the binary after build for developer + -- convenience+ customPostBuild _ _ _ _ = do+ let binDir = "bin"+ let binName = "epub-meta"+ let destPath = binDir </> binName++ createDirectoryIfMissing True binDir++ linkExists <- fileExist destPath+ unless linkExists $ do+ createSymbolicLink+ (".." </> "dist" </> "build" </> binName </> binName)+ destPath++ -- Target for running all unit tests+ testRunner _ _ _ _ = do+ system $ "runhaskell -isrc testsuite/runtests.hs"+ return ()
+ TODO view
@@ -0,0 +1,1 @@+- Better errors on parse failure, this will emerge along with more unit testing
+ epub-metadata.cabal view
@@ -0,0 +1,36 @@+name: epub-metadata+version: 1.0.2+cabal-version: >= 1.2+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: 2010 Dino Morelli+author: Dino Morelli+maintainer: Dino Morelli <dino@ui3.info>+stability: experimental+homepage: http://ui3.info/d/proj/epub-metadata.html+synopsis: Library and utility for parsing and manipulating + ePub metadata+description: Library for parsing and manipulating ePub OPF + metadata. An attempt has been made here to very + thoroughly implement the metadata portion of the OPF + Package Document specification. Also included is a + command-line utility to dump ePub metadata to stdout + in a human-readable form.+category: Codec, Text+tested-with: GHC>=6.12.1++library+ exposed-modules: Codec.Epub.IO,+ Codec.Epub.Opf.Metadata,+ Codec.Epub.Opf.Metadata.Format,+ Codec.Epub.Opf.Metadata.Parse+ hs-source-dirs: src+ build-depends: base >= 3 && < 5, HSH, hxt, mtl+ ghc-options: -Wall++executable epub-meta+ main-is: Codec/Epub/Opf/epub-meta.hs+ hs-source-dirs: src+ build-depends: base >= 3 && < 5+ ghc-options: -Wall
+ src/Codec/Epub/IO.hs view
@@ -0,0 +1,57 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE FlexibleContexts #-}++{- | Functions for doing some disk IO with ePub documents++ Note that these functions do their work by using the external + unzip utility.+-}+module Codec.Epub.IO+ ( extractFileFromZip, opfPath )+ where++import Control.Monad.Error+import HSH.Command+import Text.Printf+import Text.XML.HXT.Arrow+++{- | Extract a file from a zipfile.+ This is here because ePub files are really just zip files.+-}+extractFileFromZip :: (MonadIO m, MonadError [Char] m)+ => FilePath -- ^ path to zip file+ -> FilePath -- ^ path within zip file to extract+ -> m String -- ^ contents of expected file+extractFileFromZip zipPath filePath = do+ let dearchiver = "unzip"+ result <- liftIO $ tryEC $ run+ ((printf "%s -p %s %s" dearchiver zipPath filePath) :: String)+ case result of+ Left ps -> throwError $+ printf "[ERROR %s zip file: %s path in zip: %s status: %s]"+ dearchiver zipPath filePath (show ps)+ Right output -> return output+++-- | Get the path within an ePub file to the OPF Package Document+opfPath :: (MonadError String m, MonadIO m)+ => FilePath -- ^ path to ePub zip file+ -> m String -- ^ path within ePub to the OPF Package Document+opfPath zipPath = do+ containerContents <- extractFileFromZip zipPath+ "META-INF/container.xml"++ result <- liftIO $ runX (+ readString [(a_validate, v_0)] containerContents+ >>> deep (isElem >>> hasName "rootfile")+ >>> getAttrValue "full-path"+ )++ case result of+ (p : []) -> return p+ _ -> throwError+ "ERROR: rootfile full-path missing from META-INF/container.xml"
+ src/Codec/Epub/Opf/Metadata.hs view
@@ -0,0 +1,92 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{- | Data types for working with the metadata of ePub documents++ These data types were constructed by studying the IDPF OPF + specification for ePub documents found here:++ <http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html>+-}+module Codec.Epub.Opf.Metadata+ ( OPFPackage (..)+ , EMTitle (..)+ , EMCreator (..)+ , EMDate (..)+ , EMId (..)+ , EpubMeta (..)+ , emptyEpubMeta+ )+ where+++{- | opf:package tag++ Note that we are not yet storing the data that comes after+ \/package\/metadata in an OPF Package Document. But that may+ be added at a later time.+-}+data OPFPackage = OPFPackage+ { opVersion :: String -- ^ version attr+ , opUniqueId :: String -- ^ unique-identifier attr+ , opMeta :: EpubMeta -- ^ metadata child element contents+ }+ deriving (Eq, Show)+++-- | dc:title tag, xml:lang attr, content+data EMTitle = EMTitle (Maybe String) String+ deriving (Eq, Show)++-- | dc:creator tag, opf:role attr, opf:file-as attr, content+data EMCreator = EMCreator (Maybe String) (Maybe String) String+ deriving (Eq, Show)++-- | dc:date tag, opf:event attr, content+data EMDate = EMDate (Maybe String) String+ deriving (Eq, Show)++-- | dc:identifier tag, id attr, opf:scheme attr, content+data EMId = EMId String (Maybe String) String+ deriving (Eq, Show)++-- | opf:metadata tag+data EpubMeta = EpubMeta+ { emTitles :: [EMTitle] -- ^ at least one required+ , emCreators :: [EMCreator]+ , emContributors :: [EMCreator]+ , emSubjects :: [String] -- ^ dc:subject tags+ , emDescription :: Maybe String -- ^ dc:description tags+ , emPublisher :: Maybe String -- ^ dc:publisher tag+ , emDates :: [EMDate]+ , emType :: Maybe String -- ^ dc:type tag+ , emFormat :: Maybe String -- ^ dc:format tag+ , emIds :: [EMId] -- ^ at least one required+ , emSource :: Maybe String -- ^ dc:source tag+ , emLangs :: [String] -- ^ dc:language tags, at least one required+ , emRelation :: Maybe String -- ^ dc:relation tag+ , emCoverage :: Maybe String -- ^ dc:coverage tag+ , emRights :: Maybe String -- ^ dc:rights tag+ }+ deriving (Eq, Show)++-- | Note: This isn't valid as-is, some required values are empty lists!+emptyEpubMeta :: EpubMeta+emptyEpubMeta = EpubMeta+ { emTitles = [] -- one required+ , emCreators = []+ , emContributors = []+ , emSubjects = []+ , emDescription = Nothing+ , emPublisher = Nothing+ , emDates = []+ , emType = Nothing+ , emFormat = Nothing+ , emIds = [] -- one required+ , emSource = Nothing+ , emLangs = [] -- one required+ , emRelation = Nothing+ , emCoverage = Nothing+ , emRights = Nothing+ }
+ src/Codec/Epub/Opf/Metadata/Format.hs view
@@ -0,0 +1,131 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++-- | Module for pretty-printing ePub metadata info+module Codec.Epub.Opf.Metadata.Format+ ( opfToString+ )+ where++import Text.Printf++import Codec.Epub.Opf.Metadata+++formatSubline :: String -> Maybe String -> String+formatSubline _ Nothing = ""+formatSubline key (Just value) = printf " %s: %s\n" key value+++packageToString :: (String, String) -> String+packageToString (version, uniqueId) =+ "package\n" +++ (formatSubline "version" (Just version)) +++ (formatSubline "unique-identifier" (Just uniqueId))+++titleToString :: EMTitle -> String+titleToString (EMTitle Nothing title) = printf "title: %s\n" title+titleToString (EMTitle lang title) =+ "title\n" +++ (formatSubline "lang" lang) +++ (formatSubline "title" (Just title))+++creatorToString :: EMCreator -> String+creatorToString (EMCreator Nothing Nothing creator) =+ printf "creator: %s\n" creator+creatorToString (EMCreator role fileAs creator) =+ "creator\n" +++ (formatSubline "role" role) +++ (formatSubline "file-as" fileAs) +++ (formatSubline "creator" (Just creator))+++contributorToString :: EMCreator -> String+contributorToString (EMCreator Nothing Nothing contributor) =+ printf "contributor: %s\n" contributor+contributorToString (EMCreator role fileAs contributor) =+ "contributor\n" +++ (formatSubline "role" role) +++ (formatSubline "file-as" fileAs) +++ (formatSubline "creator" (Just contributor))+++subjectToString :: String -> String+subjectToString = printf "subject: %s\n"+++descriptionToString :: Maybe String -> String+descriptionToString = maybe "" (printf "description: %s\n")+++publisherToString :: Maybe String -> String+publisherToString = maybe "" (printf "publisher: %s\n")+++dateToString :: EMDate -> String+dateToString (EMDate Nothing date) =+ printf "date: %s\n" date+dateToString (EMDate event date) =+ "date\n" +++ (formatSubline "event" event) +++ (formatSubline "date" (Just date))+++typeToString :: Maybe String -> String+typeToString = maybe "" (printf "type: %s\n")+++formatToString :: Maybe String -> String+formatToString = maybe "" (printf "format: %s\n")+++idToString :: EMId -> String+idToString (EMId idVal scheme content) =+ "identifier\n" +++ (formatSubline "id" (Just idVal)) +++ (formatSubline "scheme" scheme) +++ (formatSubline "identifier" (Just content))+++sourceToString :: Maybe String -> String+sourceToString = maybe "" (printf "source: %s\n")+++langToString :: String -> String+langToString = printf "language: %s\n"+++relationToString :: Maybe String -> String+relationToString = maybe "" (printf "relation: %s\n")+++coverageToString :: Maybe String -> String+coverageToString = maybe "" (printf "coverage: %s\n")+++rightsToString :: Maybe String -> String+rightsToString = maybe "" (printf "rights: %s\n")+++-- | Format an ePub metadata into a String+opfToString :: OPFPackage -> String+opfToString (OPFPackage v u em) = concat $+ [packageToString (v, u)] +++ (map titleToString $ emTitles em) +++ (map creatorToString $ emCreators em) +++ (map contributorToString $ emContributors em) +++ (map dateToString $ emDates em) +++ [typeToString . emType $ em] +++ [formatToString . emFormat $ em] +++ (map idToString $ emIds em) +++ [sourceToString . emSource $ em] +++ (map subjectToString $ emSubjects em) +++ [descriptionToString . emDescription $ em] +++ [publisherToString . emPublisher $ em] +++ (map langToString $ emLangs em) +++ [relationToString . emRelation $ em] +++ [coverageToString . emCoverage $ em] +++ [rightsToString . emRights $ em]
+ src/Codec/Epub/Opf/Metadata/Parse.hs view
@@ -0,0 +1,210 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++{-# LANGUAGE Arrows #-}+{-# LANGUAGE FlexibleContexts #-}++-- | Module for extracting the metadata from an ePub file+module Codec.Epub.Opf.Metadata.Parse+ ( parseXmlToOpf+ , parseEpubOpf+ )+ where++import Control.Applicative+import Control.Monad.Error+import Data.Tree.NTree.TypeDefs ( NTree )+import Prelude hiding ( cos )+import Text.XML.HXT.Arrow++import Codec.Epub.IO+import Codec.Epub.Opf.Metadata+++-- HXT helpers++atTag :: (ArrowXml a) => String -> a (NTree XNode) XmlTree+atTag tag = deep (isElem >>> hasName tag)++atQTag :: (ArrowXml a) => QName -> a (NTree XNode) XmlTree+atQTag tag = deep (isElem >>> hasQName tag)++text :: (ArrowXml a) => a (NTree XNode) String+text = getChildren >>> getText++notNullA :: (ArrowList a) => a [b] [b]+notNullA = isA $ not . null+++mbQTagText :: (ArrowXml a) => QName -> a (NTree XNode) (Maybe String)+mbQTagText tag =+ ( atQTag tag >>>+ text >>> notNullA >>> arr Just )+ `orElse`+ (constA Nothing)+++mbGetAttrValue :: (ArrowXml a) =>+ String -> a XmlTree (Maybe String)+mbGetAttrValue n =+ (getAttrValue n >>> notNullA >>> arr Just)+ `orElse` (constA Nothing)++mbGetQAttrValue :: (ArrowXml a) =>+ QName -> a XmlTree (Maybe String)+mbGetQAttrValue qn =+ (getQAttrValue qn >>> notNullA >>> arr Just)+ `orElse` (constA Nothing)+++{- ePub parsing helpers++ Note that these URIs could conceivably change in the future+ Is it ok that they're hardcoded like this?++ Well, ok, the xml namespace URI will probably never change.+-}++dcName, opfName, xmlName :: String -> QName+dcName local = mkQName "dc" local "http://purl.org/dc/elements/1.1/"+opfName local = mkQName "opf" local "http://www.idpf.org/2007/opf"+xmlName local = mkQName "xml" local "http://www.w3.org/XML/1998/namespace"+++getPackage :: (ArrowXml a) => a (NTree XNode) (String, String)+getPackage = atTag "package" >>>+ proc x -> do+ v <- getAttrValue "version" -< x+ u <- getAttrValue "unique-identifier" -< x+ returnA -< (v, u)+++getTitle :: (ArrowXml a) => a (NTree XNode) EMTitle+getTitle = atQTag (dcName "title") >>>+ proc x -> do+ l <- mbGetQAttrValue (xmlName "lang") -< x+ c <- text -< x+ returnA -< EMTitle l c+++{- Since creators and contributors have the same exact XML structure,+ this arrow is used to get either of them+-}+getCreator :: (ArrowXml a) => String -> a (NTree XNode) EMCreator+getCreator tag = atQTag (dcName tag) >>> ( unwrapArrow $ EMCreator+ <$> (WrapArrow $ mbGetQAttrValue (opfName "role"))+ <*> (WrapArrow $ mbGetQAttrValue (opfName "file-as"))+ <*> (WrapArrow $ text)+ )+++getSubject :: (ArrowXml a) => a (NTree XNode) String+getSubject = atQTag (dcName "subject") >>> text+++getDescription :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getDescription = mbQTagText $ dcName "description"+++getPublisher :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getPublisher = mbQTagText $ dcName "publisher"+++getDate :: (ArrowXml a) => a (NTree XNode) EMDate+getDate = atQTag (dcName "date") >>>+ proc x -> do+ e <- mbGetQAttrValue (opfName "event") -< x+ c <- text -< x+ returnA -< EMDate e c+++getType :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getType = mbQTagText $ dcName "type"+++getFormat :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getFormat = mbQTagText $ dcName "format"+++getId :: (ArrowXml a) => a (NTree XNode) EMId+getId = atQTag (dcName "identifier") >>>+ proc x -> do+ mbi <- mbGetAttrValue "id" -< x+ s <- mbGetQAttrValue (opfName "scheme") -< x+ c <- text -< x+ let i = maybe "[WARNING: missing required id attribute]" id mbi+ returnA -< EMId i s c+++getSource :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getSource = mbQTagText $ dcName "source"+++getLang :: (ArrowXml a) => a (NTree XNode) String+getLang = atQTag (dcName "language") >>> text+++getRelation :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getRelation = mbQTagText $ dcName "relation"+++getCoverage :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getCoverage = mbQTagText $ dcName "coverage"+++getRights :: (ArrowXml a) => a (NTree XNode) (Maybe String)+getRights = mbQTagText $ dcName "rights"+++getMeta :: (ArrowXml a) => a (NTree XNode) EpubMeta+getMeta = atTag "metadata" >>> ( unwrapArrow $ EpubMeta+ <$> (WrapArrow $ listA getTitle)+ <*> (WrapArrow $ listA $ getCreator "creator")+ <*> (WrapArrow $ listA $ getCreator "contributor")+ <*> (WrapArrow $ listA getSubject)+ <*> (WrapArrow $ getDescription)+ <*> (WrapArrow $ getPublisher)+ <*> (WrapArrow $ listA getDate)+ <*> (WrapArrow $ getType)+ <*> (WrapArrow $ getFormat)+ <*> (WrapArrow $ listA getId)+ <*> (WrapArrow $ getSource)+ <*> (WrapArrow $ listA getLang)+ <*> (WrapArrow $ getRelation)+ <*> (WrapArrow $ getCoverage)+ <*> (WrapArrow $ getRights)+ )+++getBookData :: (ArrowXml a) => a (NTree XNode) OPFPackage+getBookData = + proc x -> do+ (v, u) <- getPackage -< x+ m <- getMeta -< x+ returnA -< OPFPackage v u m+++{- | Extract the ePub metadata contained in the OPF Package Document + contained in the supplied string+-}+parseXmlToOpf :: (MonadIO m) => String -> m [OPFPackage]+parseXmlToOpf opfContents =+ liftIO $ runX (+ readString [(a_validate, v_0)] opfContents+ >>> propagateNamespaces+ >>> getBookData+ )+++-- | Given the path to an ePub file, extract the metadata+parseEpubOpf :: (MonadIO m, MonadError String m) =>+ FilePath -> m OPFPackage+parseEpubOpf zipPath = do+ opfContents <- extractFileFromZip zipPath =<< opfPath zipPath+ result <- parseXmlToOpf opfContents++ case result of+ (em : []) -> return em+ _ -> throwError+ "ERROR: we didn't come up with a single EpubMeta"
+ src/Codec/Epub/Opf/epub-meta.hs view
@@ -0,0 +1,24 @@+-- Copyright: 2010 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.Metadata.Format+import Codec.Epub.Opf.Metadata.Parse+++main :: IO ()+main = do+ as <- getArgs++ when (length as /= 1) $ do+ putStrLn "usage: epub-meta EPUBFILE"+ exitWith $ ExitFailure 1++ let zipPath = head as+ result <- runErrorT $ parseEpubOpf zipPath++ putStr $ either (++ "\n") opfToString result
+ testsuite/runtests.hs view
@@ -0,0 +1,118 @@+-- Copyright: 2010 Dino Morelli+-- License: BSD3 (see LICENSE)+-- Author: Dino Morelli <dino@ui3.info>++import System.FilePath+import Test.HUnit ( Counts, Test (..), assertEqual, runTestTT )+import Test.HUnit.Base ( Assertion )++import Codec.Epub.Opf.Metadata+import Codec.Epub.Opf.Metadata.Parse+++main = runTestTT tests >> return ()+++tests :: Test+tests = TestList+ [ testFull+ , testMinimal+ , testMissingAll+ ]+++{- A fairly comprehensive test containing all possible things+ Not complete at this time because the library can't parse it all yet!+-}+testFull :: Test+testFull = TestCase $ do+ xmlString <- readFile $ "testsuite" </> "testFull.opf"+ actual <- parseXmlToOpf xmlString+ let expected = [ OPFPackage "2.0" "isbn" ( EpubMeta+ { emTitles =+ [ EMTitle Nothing "Title Of This Book"+ , EMTitle (Just "fr") "Titre De Ce Livre"+ ]+ , emCreators =+ [ EMCreator+ (Just "aut")+ (Just "Wiggins, Josephine B.")+ "Josephine B. Wiggins"+ , EMCreator+ (Just "aut")+ Nothing+ "Horatio Cromwell"+ , EMCreator+ Nothing+ Nothing+ "Natalia Jenkins"+ ]+ , emContributors =+ [ EMCreator+ (Just "ill")+ (Just "Knickerbocker, Reginald Q.")+ "Reginald Q. Knickerbocker"+ , EMCreator+ (Just "edt")+ Nothing+ "Beverly Abercrombie"+ ]+ , emSubjects = ["Fiction", "Science Fiction"]+ , emDescription = Just "This document is a stub used for unit testing. It is missing the rest of the tags that normally occur after metadata."+ , emPublisher = Just "Fictional Books Ltd."+ , emDates =+ [ EMDate (Just "published") "2010"+ , EMDate (Just "created") "2010-05-07"+ , EMDate (Just "modified") "2010-05-08T10:20:57"+ , EMDate Nothing "2009-08-03T16:22:20"+ ]+ , emType = Just "test OPF Package Document"+ , emFormat = Just "ePub publication"+ , emIds =+ [ EMId "isbn" (Just "ISBN") "1-82057-821-9"+ , EMId "other" Nothing "1386506873266"]+ , emSource = Just "document source"+ , emLangs = ["en-us"]+ , emRelation = Just "document relation"+ , emCoverage = Just "coverage information"+ , emRights = Just "Copyright: 2010 Dino Morelli, License: BSD3"+ } ) ]+ assertEqual "very full" expected actual+++{- Test the absolute minimum set of fields allowed while remaining + compliant with the spec+-}+testMinimal :: Test+testMinimal = TestCase $ do+ xmlString <- readFile $ "testsuite" </> "testMinimal.opf"+ actual <- parseXmlToOpf xmlString+ let expected = [ OPFPackage "2.0" "isbn" ( EpubMeta+ { emTitles = [EMTitle Nothing "Title Of This Book"]+ , emCreators = []+ , emContributors = []+ , emSubjects = []+ , emDescription = Nothing+ , emPublisher = Nothing+ , emDates = []+ , emType = Nothing+ , emFormat = Nothing+ , emIds = [EMId "isbn" (Just "ISBN") "1-82057-821-9"]+ , emSource = Nothing+ , emLangs = ["en-us"]+ , emRelation = Nothing+ , emCoverage = Nothing+ , emRights = Nothing+ } ) ]+ assertEqual "minimal" expected actual+++{- Test data missing everything important: package version and + unique-identifier attributes, title, identifier and language tags+-}+testMissingAll :: Test+testMissingAll = TestCase $ do+ xmlString <- readFile $ "testsuite" </> "testMissingAll.opf"+ actual <- parseXmlToOpf xmlString+ let expected = [ OPFPackage "" "" emptyEpubMeta ]+ assertEqual "missing all" expected actual
+ testsuite/testFull.opf view
@@ -0,0 +1,39 @@+<?xml version="1.0" encoding="UTF-8"?>+<package+ xmlns="http://www.idpf.org/2007/opf"+ version="2.0"+ unique-identifier="isbn">+ <metadata+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:opf="http://www.idpf.org/2007/opf"+ xmlns:xml="http://www.w3.org/XML/1998/namespace">+ <dc:title>Title Of This Book</dc:title>+ <dc:title xml:lang="fr">Titre De Ce Livre</dc:title>+ <dc:creator opf:role="aut" opf:file-as="Wiggins, Josephine B.">Josephine B. Wiggins</dc:creator>+ <dc:creator opf:role="aut">Horatio Cromwell</dc:creator>+ <dc:creator>Natalia Jenkins</dc:creator>+ <dc:contributor opf:role="ill" opf:file-as="Knickerbocker, Reginald Q.">Reginald Q. Knickerbocker</dc:contributor>+ <dc:contributor opf:role="edt">Beverly Abercrombie</dc:contributor>+ <dc:description>This document is a stub used for unit testing. It is missing the rest of the tags that normally occur after metadata.</dc:description>+ <dc:identifier id="isbn" opf:scheme="ISBN">1-82057-821-9</dc:identifier>+ <dc:date opf:event="published">2010</dc:date>+ <dc:date opf:event="created">2010-05-07</dc:date>+ <dc:date opf:event="modified">2010-05-08T10:20:57</dc:date>+ <dc:date>2009-08-03T16:22:20</dc:date>+ <dc:language>en-us</dc:language>+ <dc:publisher>Fictional Books Ltd.</dc:publisher>+ <dc:identifier id="other">1386506873266</dc:identifier>+ <dc:subject>Fiction</dc:subject>+ <dc:subject>Science Fiction</dc:subject>+ <dc:type>test OPF Package Document</dc:type>+ <dc:format>ePub publication</dc:format>+ <dc:source>document source</dc:source>+ <dc:relation>document relation</dc:relation>+ <dc:coverage>coverage information</dc:coverage>+ <dc:rights>Copyright: 2010 Dino Morelli, License: BSD3</dc:rights>+ <meta name="series_index" content="1"/>+ </metadata>++ <!-- This document is a stub used for unit testing. It is missing + the rest of the tags that normally occur after metadata -->+</package>
+ testsuite/testMinimal.opf view
@@ -0,0 +1,17 @@+<?xml version="1.0" encoding="UTF-8"?>+<package+ xmlns="http://www.idpf.org/2007/opf"+ version="2.0"+ unique-identifier="isbn">+ <metadata+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:opf="http://www.idpf.org/2007/opf"+ xmlns:xml="http://www.w3.org/XML/1998/namespace">+ <dc:title>Title Of This Book</dc:title>+ <dc:identifier id="isbn" opf:scheme="ISBN">1-82057-821-9</dc:identifier>+ <dc:language>en-us</dc:language>+ </metadata>++ <!-- This document is a stub used for unit testing. It is missing + the rest of the tags that normally occur after metadata -->+</package>
+ testsuite/testMissingAll.opf view
@@ -0,0 +1,11 @@+<?xml version="1.0" encoding="UTF-8"?>+<package xmlns="http://www.idpf.org/2007/opf">+ <metadata+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:opf="http://www.idpf.org/2007/opf"+ xmlns:xml="http://www.w3.org/XML/1998/namespace">+ </metadata>++ <!-- This document is a stub used for unit testing. It is missing + the rest of the tags that normally occur after metadata -->+</package>
+ util/gentags.sh view
@@ -0,0 +1,3 @@+#! /bin/sh++find src -regex '.*\..?hs' | xargs hasktags -c
+ util/prefs/boring view
@@ -0,0 +1,117 @@+# Boring file regexps:++### compiler and interpreter intermediate files+# haskell (ghc) interfaces+\.hi$+\.hi-boot$+\.o-boot$+# object files+\.o$+\.o\.cmd$+# profiling haskell+\.p_hi$+\.p_o$+# haskell program coverage resp. profiling info+\.tix$+\.prof$+# fortran module files+\.mod$+# linux kernel+\.ko\.cmd$+\.mod\.c$+(^|/)\.tmp_versions($|/)+# *.ko files aren't boring by default because they might+# be Korean translations rather than kernel modules+# \.ko$+# python, emacs, java byte code+\.py[co]$+\.elc$+\.class$+# objects and libraries; lo and la are libtool things+\.(obj|a|exe|so|lo|la)$+# compiled zsh configuration files+\.zwc$+# Common LISP output files for CLISP and CMUCL+\.(fas|fasl|sparcf|x86f)$++### build and packaging systems+# cabal intermediates+\.installed-pkg-config+\.setup-config+# standard cabal build dir, might not be boring for everybody+# ^dist(/|$)+# autotools+(^|/)autom4te\.cache($|/)+(^|/)config\.(log|status)$+# microsoft web expression, visual studio metadata directories+\_vti_cnf$+\_vti_pvt$+# gentoo tools+\.revdep-rebuild.*+# generated dependencies+^\.depend$++### version control systems+# cvs+(^|/)CVS($|/)+\.cvsignore$+# cvs, emacs locks+^\.#+# rcs+(^|/)RCS($|/)+,v$+# subversion+(^|/)\.svn($|/)+# mercurial+(^|/)\.hg($|/)+# git+(^|/)\.git($|/)+# bzr+\.bzr$+# sccs+(^|/)SCCS($|/)+# darcs+(^|/)_darcs($|/)+(^|/)\.darcsrepo($|/)+^\.darcs-temp-mail$+-darcs-backup[[:digit:]]+$+# gnu arch+(^|/)(\+|,)+(^|/)vssver\.scc$+\.swp$+(^|/)MT($|/)+(^|/)\{arch\}($|/)+(^|/).arch-ids($|/)+# bitkeeper+(^|/)BitKeeper($|/)+(^|/)ChangeSet($|/)++### miscellaneous+# backup files+~$+\.bak$+\.BAK$+# patch originals and rejects+\.orig$+\.rej$+# X server+\..serverauth.*+# image spam+\#+(^|/)Thumbs\.db$+# vi, emacs tags+(^|/)(tags|TAGS)$+#(^|/)\.[^/]+# core dumps+(^|/|\.)core$+# partial broken files (KIO copy operations)+\.part$+# waf files, see http://code.google.com/p/waf/+(^|/)\.waf-[[:digit:].]+-[[:digit:]]+($|/)+(^|/)\.lock-wscript$+# mac os finder+(^|/)\.DS_Store$+# cabal+(^|/)dist($|/)++(^|/)bin($|/)