zenacy-html-2.0.0: src/Zenacy/HTML/Internal/Query.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | A basic query facility.
module Zenacy.HTML.Internal.Query
( HTMLQuery
, htmlQueryRun
, htmlQueryExec
, htmlQueryTry
, htmlQueryStop
, htmlQueryCont
, htmlQuerySucc
, htmlQueryZipper
, htmlQueryNode
, htmlQueryFirst
, htmlQueryLast
, htmlQueryNext
, htmlQueryPrev
, htmlQueryUp
, htmlQueryTest
, htmlQueryName
, htmlQueryIsFirst
, htmlQueryIsLast
, htmlQuerySave
, htmlQueryGet
, htmlQueryGetZipper
, htmlQuerySrc
, htmlQueryAttr
, htmlQueryAttrVal
, htmlQueryId
, htmlQueryHasClass
, htmlQueryOnly
) where
import Prelude
import Zenacy.HTML.Internal.HTML
import Zenacy.HTML.Internal.Oper
import Zenacy.HTML.Internal.Zip
import Control.Monad.State
( MonadState
, State
, evalState
, modify
, gets
)
import Control.Monad.Trans.Maybe
( MaybeT(..)
, runMaybeT
)
import Data.Bool
( bool
)
import Data.IntMap
( IntMap
)
import qualified Data.IntMap as IntMap
( fromList
, lookup
, insert
)
import Data.Maybe
( fromMaybe
, isNothing
)
import Data.Text
( Text
)
-- | Defines the query state.
type QueryState = (HTMLZipper, IntMap HTMLZipper)
-- | Defines the type for a query.
newtype HTMLQuery a = HTMLQuery { htmlQueryState :: MaybeT (State QueryState) a }
deriving (Functor, Applicative, Monad, MonadState QueryState)
-- | Runs a query and returns a result.
htmlQueryRun :: HTMLNode -> HTMLQuery a -> Maybe a
htmlQueryRun x q = evalState (runMaybeT $ htmlQueryState q) s
where
z = htmlZip x
s = (z, IntMap.fromList [(0,z)])
-- | Same as run with the arguments flipped.
htmlQueryExec :: HTMLQuery a -> HTMLNode -> Maybe a
htmlQueryExec = flip htmlQueryRun
-- | Same as run with the arguments flipped.
htmlQueryTry :: HTMLQuery HTMLNode -> HTMLNode -> HTMLNode
htmlQueryTry q x = fromMaybe x $ htmlQueryRun x q
-- | Wraps a value as a query result.
htmlQueryWrap :: Maybe a -> HTMLQuery a
htmlQueryWrap = HTMLQuery . MaybeT . pure
-- | Returns a result that stops the query.
htmlQueryStop :: HTMLQuery a
htmlQueryStop = htmlQueryWrap $ Nothing
-- | Returns a result that continues the query.
htmlQueryCont :: HTMLQuery ()
htmlQueryCont = htmlQuerySucc ()
-- | Returns a successful query result.
htmlQuerySucc :: a -> HTMLQuery a
htmlQuerySucc = htmlQueryWrap . Just
-- | Gets the current query zipper.
htmlQueryZipper :: HTMLQuery HTMLZipper
htmlQueryZipper = gets fst
-- | Gets the current query node.
htmlQueryNode :: HTMLQuery HTMLNode
htmlQueryNode = htmlZipNode <$> htmlQueryZipper
-- | Performs a query step with a zipper operation.
withZip :: (HTMLZipper -> Maybe HTMLZipper) -> HTMLQuery ()
withZip f = do
z <- htmlQueryZipper
case f z of
Nothing ->
htmlQueryStop
Just z' -> do
modify $ \(_, m) -> (z', m)
htmlQueryCont
-- | Moves the query to the first child node.
htmlQueryFirst :: HTMLQuery ()
htmlQueryFirst = withZip htmlZipFirst
-- | Moves the query to the last child node.
htmlQueryLast :: HTMLQuery ()
htmlQueryLast = withZip htmlZipLast
-- | Moves the query to the next sibling node.
htmlQueryNext :: HTMLQuery ()
htmlQueryNext = withZip htmlZipNext
-- | Moves the query to the previous sibling node.
htmlQueryPrev :: HTMLQuery ()
htmlQueryPrev = withZip htmlZipPrev
-- | Moves the query to the parent node.
htmlQueryUp :: HTMLQuery ()
htmlQueryUp = withZip htmlZipUp
-- | Evaluates a test result and continues the query if true.
htmlQueryTest :: Bool -> HTMLQuery ()
htmlQueryTest = bool htmlQueryStop htmlQueryCont
-- | Tests the current element name.
htmlQueryName :: Text -> HTMLQuery ()
htmlQueryName x = do
n <- htmlQueryNode
htmlQueryTest $ htmlElemName n == x
-- | Tests the current node to see if it is the first sibling.
htmlQueryIsFirst :: HTMLQuery ()
htmlQueryIsFirst = do
z <- htmlQueryZipper
htmlQueryTest $ isNothing $ htmlZipPrev z
-- | Tests the current node to see if it is the last sibling.
htmlQueryIsLast :: HTMLQuery ()
htmlQueryIsLast = do
z <- htmlQueryZipper
htmlQueryTest $ isNothing $ htmlZipNext z
-- | Saves the current query state.
htmlQuerySave :: Int -> HTMLQuery ()
htmlQuerySave x = do
modify $ \(z, m) -> (z, IntMap.insert x z m)
htmlQueryCont
-- | Gets a saved query node.
htmlQueryGet :: Int -> HTMLQuery HTMLNode
htmlQueryGet x = htmlZipNode <$> htmlQueryGetZipper x
-- | Gets a saved query zipper.
htmlQueryGetZipper :: Int -> HTMLQuery HTMLZipper
htmlQueryGetZipper x = do
m <- gets snd
case IntMap.lookup x m of
Just z -> htmlQuerySucc z
Nothing -> htmlQueryStop
-- | Gets the source input node.
htmlQuerySrc :: HTMLQuery HTMLNode
htmlQuerySrc = htmlQueryGet 0
-- | Tests if the current node has an attribute.
htmlQueryAttr :: Text -> HTMLQuery ()
htmlQueryAttr x = htmlQueryNode >>= htmlQueryTest . htmlElemHasAttrName x
-- | Tests if the current node has an attribute value.
htmlQueryAttrVal :: Text -> Text -> HTMLQuery ()
htmlQueryAttrVal n v = htmlQueryNode >>= htmlQueryTest . htmlElemHasAttrVal n v
-- | Tests if the current node has an id.
htmlQueryId :: Text -> HTMLQuery ()
htmlQueryId x = htmlQueryNode >>= htmlQueryTest . htmlElemHasID x
-- | Tests if the current node has a class.
htmlQueryHasClass :: Text -> HTMLQuery ()
htmlQueryHasClass x = htmlQueryNode >>= htmlQueryTest . htmlElemClassesContains x
-- | Moves to the child and require that it is the only child.
htmlQueryOnly :: Text -> HTMLQuery ()
htmlQueryOnly x = htmlQueryFirst >> htmlQueryName x >> htmlQueryIsLast