svitak/app/EpubParser.hs
2026-01-24 01:15:24 +00:00

65 lines
2.2 KiB
Haskell

module EpubParser where
import Codec.Archive.Zip (toArchive, findEntryByPath, fromEntry, Archive)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as C8
import Text.HTML.TagSoup (parseTags, Tag)
import Text.HTML.Scalpel (scrape, text, tagSelector, attr, attrs)
import System.FilePath (takeDirectory)
import Control.Monad.Reader
import qualified Data.Text as T
import Data.Maybe (fromMaybe)
type EpubAction a = ReaderT EpubEnv IO a
data EpubEnv = EpubEnv
{ title :: T.Text
, author :: T.Text
, rootPrefix :: FilePath
, spine :: [T.Text]
} deriving (Show)
myEnv :: Archive -> [Tag String] -> EpubEnv
myEnv archive tags =
let
opfPath = fromMaybe "" (getOpfPath archive)
prefix = getRootPrefix opfPath
in
EpubEnv
{ title = getTagText "dc:title" tags
, author = getTagText "dc:creator" tags
, rootPrefix = prefix
, spine = getSpine tags
}
openEpub :: FilePath -> IO (Either String Archive)
openEpub path = toArchive <$> BL.readFile path >>= \archive -> maybe (pure $ Left "Error reading file") (\entry -> if fromEntry entry == C8.pack "application/epub+zip" then pure (Right archive) else pure (Left "Wrong mimetype")) (findEntryByPath "mimetype" archive)
getOpfPath :: Archive -> Maybe FilePath
getOpfPath archive = do
entry <- findEntryByPath "META-INF/container.xml" archive
let tags = parseTags $ C8.unpack (fromEntry entry)
scrape (attr "full-path" (tagSelector "rootfile")) tags
getOpfTags :: Archive -> FilePath -> [Tag String]
getOpfTags archive opfpath =
case findEntryByPath opfpath archive of
Just entry -> parseTags $ C8.unpack (fromEntry entry)
Nothing -> []
-- Makes it easier so we can just use this function and concatenate relative paths
getRootPrefix :: FilePath -> FilePath
getRootPrefix path =
let dir = takeDirectory path
in if dir == "." then "" else dir ++ "/"
getTagText :: String -> [Tag String] -> T.Text
getTagText tagName tags =
let scraper = text (tagSelector tagName)
in T.pack $ fromMaybe "" (scrape scraper tags)
getSpine :: [Tag String] -> [T.Text]
getSpine tags =
map T.pack . fromMaybe [] $ scrape (attrs "idref" (tagSelector "itemref")) tags