{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# OPTIONS_HADDOCK hide #-}

module Text.HTML.Scalpel.Internal.Select.Types (
    Selector (..)
,   AttributePredicate (..)
,   checkPred
,   AttributeName (..)
,   matchKey
,   anyAttrPredicate
,   TagName (..)
,   SelectNode (..)
,   tagSelector
,   anySelector
,   textSelector
,   toSelectNode
,   SelectSettings (..)
,   defaultSelectSettings
) where

import Data.Char (toLower)
import Data.String (IsString, fromString)

import qualified Text.HTML.TagSoup as TagSoup
import qualified Text.StringLike as TagSoup
import qualified Data.Text as T


-- | The 'AttributeName' type can be used when creating 'Selector's to specify
-- the name of an attribute of a tag.
data AttributeName = AnyAttribute | AttributeString String

matchKey :: TagSoup.StringLike str => AttributeName -> str -> Bool
matchKey :: forall str. StringLike str => AttributeName -> str -> Bool
matchKey (AttributeString String
s) = ((forall a. IsString a => String -> a
TagSoup.fromString forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
s) forall a. Eq a => a -> a -> Bool
==)
matchKey AttributeName
AnyAttribute = forall a b. a -> b -> a
const Bool
True

instance IsString AttributeName where
    fromString :: String -> AttributeName
fromString = String -> AttributeName
AttributeString

-- | An 'AttributePredicate' is a method that takes a 'TagSoup.Attribute' and
-- returns a 'Bool' indicating if the given attribute matches a predicate.
data AttributePredicate
        = MkAttributePredicate
                (forall str. TagSoup.StringLike str => [TagSoup.Attribute str]
                                                    -> Bool)

checkPred :: TagSoup.StringLike str
          => AttributePredicate -> [TagSoup.Attribute str] -> Bool
checkPred :: forall str.
StringLike str =>
AttributePredicate -> [Attribute str] -> Bool
checkPred (MkAttributePredicate forall str. StringLike str => [Attribute str] -> Bool
p) = forall str. StringLike str => [Attribute str] -> Bool
p

-- | Creates an 'AttributePredicate' from a predicate function of a single
-- attribute that matches if any one of the attributes matches the predicate.
anyAttrPredicate :: (forall str. TagSoup.StringLike str => (str, str) -> Bool)
                 -> AttributePredicate
anyAttrPredicate :: (forall str. StringLike str => (str, str) -> Bool)
-> AttributePredicate
anyAttrPredicate forall str. StringLike str => (str, str) -> Bool
p = (forall str. StringLike str => [Attribute str] -> Bool)
-> AttributePredicate
MkAttributePredicate forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any forall str. StringLike str => (str, str) -> Bool
p

-- | 'Selector' defines a selection of an HTML DOM tree to be operated on by
-- a web scraper. The selection includes the opening tag that matches the
-- selection, all of the inner tags, and the corresponding closing tag.
newtype Selector = MkSelector [(SelectNode, SelectSettings)]

-- | 'SelectSettings' defines additional criteria for a Selector that must be
-- satisfied in addition to the SelectNode. This includes criteria that are
-- dependent on the context of the current node, for example the depth in
-- relation to the previously matched SelectNode.
data SelectSettings = SelectSettings {
  -- | The required depth of the current select node in relation to the
  -- previously matched SelectNode.
  SelectSettings -> Maybe Int
selectSettingsDepth :: Maybe Int
}

defaultSelectSettings :: SelectSettings
defaultSelectSettings :: SelectSettings
defaultSelectSettings = SelectSettings {
  selectSettingsDepth :: Maybe Int
selectSettingsDepth = forall a. Maybe a
Nothing
}

tagSelector :: String -> Selector
tagSelector :: String -> Selector
tagSelector String
tag = [(SelectNode, SelectSettings)] -> Selector
MkSelector [
    (TagName -> [AttributePredicate] -> SelectNode
toSelectNode (String -> TagName
TagString String
tag) [], SelectSettings
defaultSelectSettings)
  ]

-- | A selector which will match any node (including tags and bare text).
anySelector :: Selector
anySelector :: Selector
anySelector = [(SelectNode, SelectSettings)] -> Selector
MkSelector [([AttributePredicate] -> SelectNode
SelectAny [], SelectSettings
defaultSelectSettings)]

-- | A selector which will match all text nodes.
textSelector :: Selector
textSelector :: Selector
textSelector = [(SelectNode, SelectSettings)] -> Selector
MkSelector [(SelectNode
SelectText, SelectSettings
defaultSelectSettings)]

instance IsString Selector where
  fromString :: String -> Selector
fromString = String -> Selector
tagSelector

data SelectNode = SelectNode !T.Text [AttributePredicate]
                | SelectAny [AttributePredicate]
                | SelectText

-- | The 'TagName' type is used when creating a 'Selector' to specify the name
-- of a tag.
data TagName = AnyTag | TagString String

instance IsString TagName where
    fromString :: String -> TagName
fromString = String -> TagName
TagString

toSelectNode :: TagName -> [AttributePredicate] -> SelectNode
toSelectNode :: TagName -> [AttributePredicate] -> SelectNode
toSelectNode TagName
AnyTag = [AttributePredicate] -> SelectNode
SelectAny
toSelectNode (TagString String
str) = Text -> [AttributePredicate] -> SelectNode
SelectNode forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. IsString a => String -> a
TagSoup.fromString forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
str