{-# LANGUAGE LambdaCase #-} -- | -- Module : Text.Taggy.DOM -- Copyright : (c) 2014 Alp Mestanogullari, Vikram Verma -- License : BSD3 -- Maintainer : alpmestan@gmail.com -- Stability : experimental -- -- Many useful combinators for querying 'Element's -- of a DOM tree. module Text.Taggy.Combinators (hasName, hasAttr, getAttr, innerText, (//), (/&), (/*), trees, subtrees) where import Prelude hiding (lookup) import Data.Monoid (mconcat) import Control.Monad (ap, (<=<)) import Data.Text (Text) import Text.Taggy.DOM (Element(..), Node(..), AttrName, AttrValue) import Data.HashMap.Strict (lookup, keys) -- | Does the given 'Element' have -- the given name? hasName :: Element -> Text -> Bool hasName = (==) . eltName -- | Does the given element have -- an attribute with the given name (or /key/) hasAttr :: Element -> AttrName -> Bool hasAttr = flip elem . keys . eltAttrs -- | Get the value for the given attribute name -- in the given 'Element'. Returns 'Nothing' if -- the provided 'Element' doesn't have an attribute -- with that name. getAttr :: Element -> AttrName -> Maybe AttrValue getAttr = flip lookup . eltAttrs -- | Get all the bits of raw text present -- everywhere below the given 'Element' -- in the DOM tree. innerText :: Element -> Text innerText = mconcat . map getContent . eltChildren where getContent = \case { NodeElement e -> innerText e; NodeContent x -> x } -- | Filter an element and its children to those -- satisfying a given predicate. (//) :: Element -> (Element -> Bool) -> [Element] (//) = flip filter . trees -- | Given a sequence of predicates, filter an element -- and its children, selecting only those subtrees who -- match the provided predicate for each point. -- -- >>> let element = (\(NodeElement e) -> e) . head . domify . taggyWith False $ "foobaz" -- >>> element /& [const False] -- [] -- >>> element /& [flip hasAttr "class", flip hasName "quux"] -- [Element "quux" "" ""] (/&) :: Element -> [(Element -> Bool)] -> [Element] (/&) element [] = [element] (/&) element (x:xs) = (/& xs) <=< filter x . catElements $ eltChildren element -- | Filter from all subtrees (including the one -- with the target as its root), those matching the -- given sequence of predicates. (/*) :: Element -> [(Element -> Bool)] -> [Element] (/*) element selector = concat . filter (not.null) . map (/& selector) $ trees element -- | Extracts all subtrees of its target, including the target. trees :: Element -> [Element] trees = ap (:) subtrees -- | Extracts all subtrees of its target, excluding the target. subtrees :: Element -> [Element] subtrees = ap (:) subtrees <=< catElements . eltChildren isElement :: Node -> Bool isElement = \case { NodeElement _ -> True; _ -> False } unsafeFromElement :: Node -> Element unsafeFromElement (NodeElement e) = e unsafeFromElement _ = error "unsafeFromElement isn't well-defined, use with caution. ;-)" catElements :: [Node] -> [Element] catElements = map unsafeFromElement . filter isElement