forked from marko/svitak
Rework the reader around a clean BookInfo interface and lazy chapter loading. Types/parser: - BookInfo now splits into loadStructure (cheap: spine refs + TOC tree) and renderChapter (one chapter on demand). EpubEnv carries the parsed archive. - resolveZipPath collapses ./.. and strips #fragments (normalise does not), so relative hrefs like ../Images/x.jpg resolve; used for chapters and images. - decodeEntry tolerates encodings: UTF-8/UTF-16 BOMs, Latin-1 fallback instead of throwing on invalid bytes. - Skip spine items with no manifest entry instead of throwing an error. - Scan the zip for a .opf if container.xml is missing. - Image inlining rewrites the src URL string, not the whole <img> tag (scalpel re-serialises tags, so a whole-tag replace silently missed self-closing imgs). Navigation: - Hierarchical TOC: ncxToc (EPUB 2), navToc (EPUB 3), guideToc (<guide> fallback). Nested scrapers use atDepth 1 to take only direct children. UI: - Streaming: render the opened chapter on demand, cache it, preload neighbours so arrow paging is instant with a placeholder that covers uncached jumps. - Sidebar shows the TOC tree with CSS styling. Clicks scroll to #fragments. - wrapHtml wraps each body in a full HTML document for reliable WebKit render. Test: 'svitak-test <book> <idx>' dumps one rendered chapter. cabal test stanza fixed to list all needed modules/deps.
74 lines
2.5 KiB
Haskell
74 lines
2.5 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
-- | Manual dump tool (not automated tests). Opens a real EPUB, prints the
|
|
-- resolved chapter list (rendering each on demand to show content sizes) and
|
|
-- the TOC tree:
|
|
--
|
|
-- > cabal run svitak-test -- path/to/book.epub
|
|
module Main (main) where
|
|
|
|
import qualified Data.Text as T
|
|
import EpubParser (openEpub)
|
|
import System.Environment (getArgs)
|
|
import System.Exit (exitFailure)
|
|
import Types
|
|
( BookInfo (..),
|
|
BookStructure (..),
|
|
Chapter (..),
|
|
ChapterIndex (..),
|
|
ChapterRef (..),
|
|
InternalPath (..),
|
|
TocEntry (..),
|
|
)
|
|
|
|
main :: IO ()
|
|
main = do
|
|
args <- getArgs
|
|
case args of
|
|
[path] -> run path
|
|
[path, n] -> dumpOne path (read n)
|
|
_ -> putStrLn "usage: svitak-test <book.epub> [chapter-index]" >> exitFailure
|
|
|
|
-- | Print the rendered HTML of a single chapter (for debugging image inlining).
|
|
dumpOne :: FilePath -> Int -> IO ()
|
|
dumpOne path n = do
|
|
Right env <- openEpub path
|
|
Right (BookStructure refs _) <- loadStructure env
|
|
Right ch <- renderChapter env (refs !! n)
|
|
putStrLn (T.unpack (T.take 800 (chapterHtml ch)))
|
|
|
|
run :: FilePath -> IO ()
|
|
run path = do
|
|
opened <- openEpub path
|
|
case opened of
|
|
Left err -> putStrLn ("open failed: " ++ err) >> exitFailure
|
|
Right env -> do
|
|
putStrLn ("Title: " ++ T.unpack (bookTitle env))
|
|
putStrLn ("Authors: " ++ show (map T.unpack (bookAuthors env)))
|
|
structure <- loadStructure env
|
|
case structure of
|
|
Left err -> putStrLn ("structure failed: " ++ err) >> exitFailure
|
|
Right (BookStructure refs toc) -> do
|
|
putStrLn ("\n== Chapters (" ++ show (length refs) ++ ") ==")
|
|
mapM_ (dumpChapter env) refs
|
|
putStrLn "\n== TOC tree =="
|
|
mapM_ (dumpToc 0) toc
|
|
|
|
dumpChapter :: (BookInfo a) => a -> ChapterRef -> IO ()
|
|
dumpChapter env ref = do
|
|
rendered <- renderChapter env ref
|
|
let size = case rendered of
|
|
Left err -> "ERR " ++ err
|
|
Right c -> show (T.length (chapterHtml c)) ++ " chars"
|
|
putStrLn (" [" ++ show idx ++ "] " ++ T.unpack (refTitle ref) ++ " (" ++ path ++ ", " ++ size ++ ")")
|
|
where
|
|
ChapterIndex idx = refIndex ref
|
|
InternalPath path = refPath ref
|
|
|
|
dumpToc :: Int -> TocEntry -> IO ()
|
|
dumpToc depth t = do
|
|
putStrLn (replicate (depth * 2) ' ' ++ "- " ++ T.unpack (tocLabel t) ++ " -> " ++ target ++ frag)
|
|
mapM_ (dumpToc (depth + 1)) (tocChildren t)
|
|
where
|
|
InternalPath target = tocTarget t
|
|
frag = if T.null (tocFragment t) then "" else "#" ++ T.unpack (tocFragment t)
|