{-# LANGUAGE TupleSections #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE CPP #-}
{-# OPTIONS_HADDOCK hide #-}
module Text.HTML.Scalpel.Internal.Serial (
SerialScraper
, SerialScraperT
, inSerial
, stepBack
, stepNext
, seekBack
, seekNext
, untilBack
, untilNext
) where
import Text.HTML.Scalpel.Internal.Scrape
import Text.HTML.Scalpel.Internal.Select
import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Except (MonadError)
import Control.Monad.Cont (MonadCont)
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Control.Monad.Writer (MonadWriter)
import Data.Bifunctor
import Data.Functor.Identity
import Data.List.PointedList (PointedList)
import Data.Maybe
import Prelude hiding (until)
import qualified Control.Monad.Fail as Fail
import qualified Data.List.PointedList as PointedList
import qualified Data.Tree as Tree
import qualified Text.StringLike as TagSoup
type SpecZipper str = PointedList (Maybe (TagSpec str))
type SerialScraper str a = SerialScraperT str Identity a
newtype SerialScraperT str m a =
MkSerialScraper (StateT (SpecZipper str) (MaybeT m) a)
deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadFix,
MonadIO, MonadCont, MonadError e, MonadReader r, MonadWriter w)
#if MIN_VERSION_base(4,9,0)
deriving instance Monad m => Fail.MonadFail (SerialScraperT str m)
#else
instance Fail.MonadFail m => Fail.MonadFail (SerialScraperT str m) where
fail = lift . Fail.fail
#endif
instance MonadTrans (SerialScraperT str) where
lift op = MkSerialScraper . lift . lift $ op
instance MonadState s m => MonadState s (SerialScraperT str m) where
get = MkSerialScraper (lift . lift $ get)
put = MkSerialScraper . lift . lift . put
inSerial :: (TagSoup.StringLike str, Monad m)
=> SerialScraperT str m a -> ScraperT str m a
inSerial (MkSerialScraper serialScraper) = MkScraper $ ReaderT $ scraper
where
scraper spec@(vec, root : _, ctx)
| ctxInChroot ctx = evalStateT serialScraper
(toZipper (vec, Tree.subForest root, ctx))
| otherwise = evalStateT serialScraper (toZipper spec)
scraper _ = empty
toZipper (vector, forest, context) =
zipperFromList $ map ((vector, , context) . return) forest
zipperFromList :: TagSoup.StringLike str => [TagSpec str] -> SpecZipper str
zipperFromList = PointedList.insertLeft Nothing
. foldr (PointedList.insertLeft . Just)
(PointedList.singleton Nothing)
stepWith :: (TagSoup.StringLike str, Monad m)
=> (SpecZipper str -> Maybe (SpecZipper str))
-> ScraperT str m b
-> SerialScraperT str m b
stepWith moveList (MkScraper (ReaderT scraper)) = MkSerialScraper . StateT $
\zipper -> do
zipper' <- maybeT $ moveList zipper
focus <- maybeT $ PointedList._focus zipper'
value <- scraper focus
return (value, zipper')
stepBack :: (TagSoup.StringLike str, Monad m) => ScraperT str m a -> SerialScraperT str m a
stepBack = stepWith PointedList.previous
stepNext :: (TagSoup.StringLike str, Monad m)
=> ScraperT str m a -> SerialScraperT str m a
stepNext = stepWith PointedList.next
seekWith :: (TagSoup.StringLike str, Monad m)
=> (SpecZipper str -> Maybe (SpecZipper str))
-> ScraperT str m b
-> SerialScraperT str m b
seekWith moveList (MkScraper (ReaderT scraper)) = MkSerialScraper (StateT go)
where
go zipper = do zipper' <- maybeT $ moveList zipper
runScraper zipper' <|> go zipper'
runScraper zipper = do
focus <- maybeT $ PointedList._focus zipper
value <- scraper focus
return (value, zipper)
seekBack :: (TagSoup.StringLike str, Monad m)
=> ScraperT str m a -> SerialScraperT str m a
seekBack = seekWith PointedList.previous
seekNext :: (TagSoup.StringLike str, Monad m)
=> ScraperT str m a -> SerialScraperT str m a
seekNext = seekWith PointedList.next
untilWith :: (TagSoup.StringLike str, Monad m)
=> (SpecZipper str -> Maybe (SpecZipper str))
-> (Maybe (TagSpec str) -> SpecZipper str -> SpecZipper str)
-> ScraperT str m a
-> SerialScraperT str m b
-> SerialScraperT str m b
untilWith moveList appendNode (MkScraper (ReaderT until)) (MkSerialScraper scraper) =
MkSerialScraper $ do
inner <- StateT split
lift (evalStateT scraper (appendNode Nothing inner))
where
split zipper =
do zipper' <- maybeT $ moveList zipper
spec <- maybeT $ PointedList._focus zipper'
do until spec
return (PointedList.singleton Nothing, zipper)
<|> (first (appendNode (Just spec)) `liftM` split zipper')
<|> return (PointedList.singleton Nothing, zipper)
untilBack :: (TagSoup.StringLike str, Monad m)
=> ScraperT str m a -> SerialScraperT str m b -> SerialScraperT str m b
untilBack = untilWith PointedList.previous PointedList.insertRight
untilNext :: (TagSoup.StringLike str, Monad m)
=> ScraperT str m a -> SerialScraperT str m b -> SerialScraperT str m b
untilNext = untilWith PointedList.next PointedList.insertLeft
maybeT :: Monad m => Maybe a -> MaybeT m a
maybeT = MaybeT . return