svitak/test/Spec.hs
Marko Andjelic 2011d638b0
refactor: streaming loader, hierarchical TOC, parser robustness
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.
2026-06-26 03:45:29 +01:00

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)