content: -- | Combinators to match tags. Some people prefer to use @(~==)@ from
  --   "Text.HTML.TagSoup", others prefer these more structured combinators.
  --   Which you use is personal preference.
  --
  -- The functions below offer maximum flexibility for matching tags.
  -- Using 'tagOpen', for example, you can match all links or buttons that have the "btn" class.
  --
  -- For simple uses cases—like matching all comment tags, or matching opening @\<a>@ tags,
  -- use the tag identification functions in "Text.HTML.TagSoup#tag-identification".
  module Text.HTML.TagSoup.Match where
  
  import Text.HTML.TagSoup.Type (Tag(..), Attribute)
  import Data.List (tails)
  
  -- * Matching Tags
  
  -- | Match an opening tag
  --
  -- ==== __Examples__
  --
  -- /Matching an opening @\<a>@ tag with a @"btn"@ class:/
  --
  -- >>> let tag = TagOpen "a" [("class", "btn")]
  -- >>> tagOpen (== "a") (\attrs -> any (== ("class", "btn")) attrs) tag
  -- True
  tagOpen :: (str -> Bool) -> ([Attribute str] -> Bool) -> Tag str -> Bool
  tagOpen pName pAttrs (TagOpen name attrs) =
     pName name && pAttrs attrs
  tagOpen _ _ _ = False
  
  -- | Match a closing tag
  --
  -- ==== __Examples__
  --
  -- /Matching a closing @\<\/a>@ tag:/
  --
  -- >>> tagClose (== "a") (TagClose "a")
  -- True
  --
  -- >>> tagClose (== "a") (TagOpen "a" [])
  -- False
  tagClose :: (str -> Bool) -> Tag str -> Bool
  tagClose pName (TagClose name) = pName name
  tagClose _ _ = False
  
  -- | Match text tags
  --
  -- ==== __Examples__
  --
  -- /Match all text tags:/
  --
  -- >>> let tags = parseTags "<p>This is a paragraph</p>"
  -- [TagOpen "p" [],TagText "This is a paragraph",TagClose "p"]
  -- >>> filter (tagText (const True)) tags
  -- [TagText "This is a paragraph"]
  tagText :: (str -> Bool) -> Tag str -> Bool
  tagText p (TagText text) = p text
  tagText _ _ = False
  
  -- | Match comment tags
  --
  -- ==== __Examples__
  --
  -- /Matching comment tags that include an exclamation mark:/
  --
  -- >>> let tags = parseTags "<!--This is a comment-->"
  -- [TagComment "This is a comment!"]
  -- >>> all (tagComment (\s -> '!' `elem` s)) tags
  -- True
  tagComment :: (str -> Bool) -> Tag str -> Bool
  tagComment p (TagComment text) = p text
  tagComment _ _ = False
  
  
  -- | Match an opening tag's name literally
  --
  -- ==== __Examples__
  --
  -- /Matching @\<a>@ tags with the @id@ "foo":/
  --
  -- >>> let tag = TagOpen "a" [("id", "foo")]
  -- TagOpen "a" [("id","foo")]
  -- >>> tagOpenLit "a" (\attrs -> any (== ("id", "foo")) attrs) tag
  -- True
  --
  tagOpenLit :: Eq str => str -> ([Attribute str] -> Bool) -> Tag str -> Bool
  tagOpenLit name = tagOpen (name==)
  
  -- | Match a closing tag's name literally
  --
  -- ==== __Examples__
  --
  -- /Match a closing @\<a>@ tag:/
  --
  -- >>> tagCloseLit "a" (TagClose "a")
  -- True
  --
  -- >>> tagCloseLit "a" (TagClose "em")
  -- False
  tagCloseLit :: Eq str => str -> Tag str -> Bool
  tagCloseLit name = tagClose (name==)
  
  -- | Match an opening tag's name literally, and at least one of its attributes
  --
  -- ==== __Examples__
  --
  -- /Matching a @\<div>@ tag with the @id@ "foo":/
  --
  -- >>> tagOpenAttrLit "div" ("id", "foo") (TagOpen "div" [("id", "foo")])
  -- True
  tagOpenAttrLit :: Eq str => str -> Attribute str -> Tag str -> Bool
  tagOpenAttrLit name attr =
     tagOpenLit name (anyAttrLit attr)
  
  {- |
  Match a tag with given name, that contains an attribute
  with given name, that satisfies a predicate.
  If an attribute occurs multiple times,
  all occurrences are checked.
  
  ==== __Examples__
  
  /Matching an @\<a>@ tag with an ID that starts with "comment-":/
  
  >>> let commentTag = TagOpen "a" [("id", "comment-45678")]
  >>> tagOpenAttrNameLit "a" "id" (\idValue -> "comment-" `Data.List.isPrefixOf` idValue) commentTag
  True
  -}
  tagOpenAttrNameLit :: Eq str => str -> str -> (str -> Bool) -> Tag str -> Bool
  tagOpenAttrNameLit tagName attrName pAttrValue =
     tagOpenLit tagName
        (anyAttr (\(name,value) -> name==attrName && pAttrValue value))
  
  
  -- | Check if the 'Tag str' is 'TagOpen' and matches the given name
  --
  -- ==== __Examples__
  --
  -- /Matching an @\<a>@ tag:/
  --
  -- >>> tagOpenNameLit "a" (TagOpen "a" [])
  -- True
  --
  -- >>> tagOpenNameLit "a" (TagOpen "div" [])
  -- False
  tagOpenNameLit :: Eq str => str -> Tag str -> Bool
  tagOpenNameLit name = tagOpenLit name (const True)
  
  -- | Check if the 'Tag str' is 'TagClose' and matches the given name
  --
  -- ==== __Examples__
  --
  -- /Matching a closing @\<\/a>@ tag:/
  --
  -- >>> tagCloseNameLit "a" (TagClose "a")
  -- True
  --
  -- >>> tagCloseNameLit "a" (TagClose "div")
  -- False
  tagCloseNameLit :: Eq str => str -> Tag str -> Bool
  tagCloseNameLit name = tagCloseLit name
  
  
  -- * Matching attributes
  
  -- | Does any attribute name/value match the predicate.
  anyAttr :: ((str,str) -> Bool) -> [Attribute str] -> Bool
  anyAttr = any
  
  -- | Does any attribute name match the predicate.
  anyAttrName :: (str -> Bool) -> [Attribute str] -> Bool
  anyAttrName p = any (p . fst)
  
  -- | Does any attribute value match the predicate.
  anyAttrValue :: (str -> Bool) -> [Attribute str] -> Bool
  anyAttrValue p = any (p . snd)
  
  
  -- | Does any attribute name/value match.
  anyAttrLit :: Eq str => (str,str) -> [Attribute str] -> Bool
  anyAttrLit attr = anyAttr (attr==)
  
  -- | Does any attribute name match.
  anyAttrNameLit :: Eq str => str -> [Attribute str] -> Bool
  anyAttrNameLit name = anyAttrName (name==)
  
  -- | Does any attribute value match.
  anyAttrValueLit :: Eq str => str -> [Attribute str] -> Bool
  anyAttrValueLit value = anyAttrValue (value==)
  
  
  
  -- | Get the tags under tags with a given name where the attributes match some predicate.
  getTagContent :: Eq str => str -> ([Attribute str] -> Bool) -> [Tag str] -> [Tag str]
  getTagContent name pAttrs =
     takeWhile (not . tagCloseLit name) . drop 1 .
     head . sections (tagOpenLit name pAttrs)
      where sections p = filter (p . head) . init . tails
  