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.
71 lines
2.4 KiB
Haskell
71 lines
2.4 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
-- | Scrapers that turn a table-of-contents document into a /tree/ of raw
|
|
-- entries. Two formats are supported:
|
|
--
|
|
-- * EPUB 2 — an NCX file with nested @\<navPoint\>@s.
|
|
-- * EPUB 3 — a nav document with nested @\<ol\>@\/@\<li\>@ lists.
|
|
--
|
|
-- Hrefs are left relative here; 'EpubParser' resolves them against the TOC
|
|
-- document's location. We use 'atDepth' so each recursion level only picks up
|
|
-- its /direct/ children (otherwise scalpel's descendant matching would flatten
|
|
-- the hierarchy and duplicate nested nodes).
|
|
module Navigation
|
|
( RawToc (..),
|
|
navDocHref,
|
|
ncxToc,
|
|
navToc,
|
|
guideToc,
|
|
)
|
|
where
|
|
|
|
import Control.Applicative ((<|>))
|
|
import qualified Data.Text as T
|
|
import Text.HTML.Scalpel (Scraper, atDepth, attr, chroot, chroots, scrapeStringLike, text, (@:), (@=))
|
|
|
|
-- | A TOC node with an as-yet-unresolved (document-relative) href.
|
|
data RawToc = RawToc
|
|
{ rawLabel :: T.Text,
|
|
rawHref :: FilePath,
|
|
rawChildren :: [RawToc]
|
|
}
|
|
deriving (Show)
|
|
|
|
-- | The EPUB 3 navigation document's href, declared in the OPF manifest as the
|
|
-- item carrying @properties="nav"@.
|
|
navDocHref :: String -> Maybe FilePath
|
|
navDocHref opf =
|
|
scrapeStringLike opf (attr "href" ("item" @: ["properties" @= "nav"]))
|
|
|
|
-- | EPUB 2: walk the @\<navMap\>@ of nested @\<navPoint\>@s.
|
|
ncxToc :: Scraper T.Text [RawToc]
|
|
ncxToc = chroot "navMap" points
|
|
where
|
|
points = chroots ("navPoint" `atDepth` 1) node
|
|
node =
|
|
RawToc
|
|
<$> (T.strip <$> text ("navLabel" `atDepth` 1))
|
|
<*> (T.unpack <$> attr "src" ("content" `atDepth` 1))
|
|
<*> points
|
|
|
|
-- | EPUB 3: walk @\<nav epub:type="toc"\>@'s nested @\<ol\>@\/@\<li\>@ lists.
|
|
navToc :: Scraper T.Text [RawToc]
|
|
navToc = chroot ("nav" @: ["epub:type" @= "toc"]) (chroot "ol" items)
|
|
where
|
|
items = chroots ("li" `atDepth` 1) node
|
|
node =
|
|
RawToc
|
|
<$> (T.strip <$> text ("a" `atDepth` 1))
|
|
<*> (T.unpack <$> attr "href" ("a" `atDepth` 1))
|
|
<*> (chroot ("ol" `atDepth` 1) items <|> pure [])
|
|
|
|
-- | EPUB 2 @\<guide\>@: a flat list of @\<reference\>@s (cover, toc, start, …).
|
|
-- Used as a last-resort TOC when there's no NCX or nav document.
|
|
guideToc :: Scraper T.Text [RawToc]
|
|
guideToc = chroot "guide" $ chroots "reference" node
|
|
where
|
|
node =
|
|
RawToc
|
|
<$> (T.strip <$> attr "title" "reference")
|
|
<*> (T.unpack <$> attr "href" "reference")
|
|
<*> pure []
|