{-# OPTIONS_HADDOCK hide #-}
module Text.HTML.Scalpel.Internal.Select (
select
) where
import Text.HTML.Scalpel.Internal.Select.Types
import Data.List
import qualified Text.HTML.TagSoup as TagSoup
import qualified Text.StringLike as TagSoup
-- | The 'select' function takes a 'Selectable' value and a list of
-- 'TagSoup.Tag's and returns a list of every subsequence of the given list of
-- Tags that matches the given selector.
select :: (TagSoup.StringLike str, Selectable str s)
=> s -> [TagSoup.Tag str] -> [[TagSoup.Tag str]]
select s = selectNodes nodes
where (MkSelector nodes) = toSelector s
selectNodes :: TagSoup.StringLike str
=> [SelectNode str] -> [TagSoup.Tag str] -> [[TagSoup.Tag str]]
selectNodes nodes tags = head' $ reverse $ results
where results = [concatMap (selectNode s) ts | s <- nodes
| ts <- [tags] : results]
head' [] = []
head' (x:_) = x
selectNode :: TagSoup.StringLike str
=> SelectNode str -> [TagSoup.Tag str] -> [[TagSoup.Tag str]]
selectNode (SelectNode node attributes) tags = concatMap extractTagBlock nodes
where nodes = filter (checkTag node attributes) $ tails tags
selectNode (SelectAny attributes) tags = concatMap extractTagBlock nodes
where nodes = filter (checkPreds attributes) $ tails tags
-- Given a tag name and a list of attribute predicates return a function that
-- returns true if a given tag matches the supplied name and predicates.
checkTag :: TagSoup.StringLike str
=> str -> [AttributePredicate str] -> [TagSoup.Tag str] -> Bool
checkTag name preds tags@((TagSoup.TagOpen str _):_)
= name == str && checkPreds preds tags
checkTag _ _ _ = False
checkPreds :: TagSoup.StringLike str
=> [AttributePredicate str] -> [TagSoup.Tag str] -> Bool
checkPreds preds ((TagSoup.TagOpen _ attrs):_)
= and [or [p attr | attr <- attrs] | p <- preds]
checkPreds _ _ = False
-- Given a list of tags, return the prefix that of the tags up to the closing
-- tag that corresponds to the initial tag.
extractTagBlock :: TagSoup.StringLike str
=> [TagSoup.Tag str] -> [[TagSoup.Tag str]]
extractTagBlock [] = []
extractTagBlock (openTag : tags)
| not $ TagSoup.isTagOpen openTag = []
| otherwise = map (openTag :)
$ splitBlock (getTagName openTag) 0 tags
splitBlock _ _ [] = []
splitBlock name depth (tag : tags)
| depth == 0 && TagSoup.isTagCloseName name tag = [[tag]]
| TagSoup.isTagCloseName name tag = tag_ $ splitBlock name (depth - 1) tags
| TagSoup.isTagOpenName name tag = tag_ $ splitBlock name (depth + 1) tags
| otherwise = tag_ $ splitBlock name depth tags
where tag_ = map (tag :)
getTagName :: TagSoup.StringLike str => TagSoup.Tag str -> str
getTagName (TagSoup.TagOpen name _) = name
getTagName (TagSoup.TagClose name) = name
getTagName _ = undefined