{-| Module : Data.Niagra.Selector Description : Selector type and combinator operators Copyright : (c) Nathaniel Symer, 2015 License : MIT Maintainer : nate@symer.io Stability : experimental Portability : POSIX Create & manipulate CSS selectors. -} {-# LANGUAGE OverloadedStrings #-} module Data.Niagra.Selector ( -- * Types Selector(..), -- * Builder buildSelector, -- * Operators (<||>), -- ** Selector Operators (.>.), (.+.), (.~.), (#), (!), (<:>), (<::>), -- ** Attribute Operators (|=|), (|~=|), (||=|), (|^=|), (|$=|), (|*=|), cls, ident, pseudoClass, pseudoType ) where import Data.Monoid import Data.List (intersperse) import qualified Data.String as S import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as TL import Data.Text.Lazy.Builder -- |A CSS selector data Selector = Child Selector Selector -- ^ @a > b@ | Precedence Selector Selector -- ^ @a ~ b@ | ImmediatePrecedence Selector Selector -- ^ @a + b@ | Descendant Selector Selector -- ^ @a b@ | PseudoClass Selector Text (Maybe Selector) -- ^ @a:hover, a:not(b)@ | PseudoType Selector Text (Maybe Selector) -- ^ @span::before, span::my-pseudotype(b)@ | AttrExistential Selector Text -- ^ @h2[foo]@ | AttrEquality Selector Text Text -- ^ @h2[foo="bar"]@ | AttrWhitespaceListContains Selector Text Text -- ^ @h2[foo~="bar"]@ | AttrHyphenListContains Selector Text Text -- ^ @h2[foo|="en"]@ | AttrBeginsWith Selector Text Text -- ^ @h2[foo^="bar"]@ | AttrEndsWith Selector Text Text -- ^ @h2[foo$="bar"]@ | AttrSubstring Selector Text Text -- ^ @h2[foo*="bar"]@ | Class Selector Text -- ^ @h2.myclass@ | Id Selector Text -- ^ @a#mylink@ | FontFace -- ^ @@font-face@ | SelectorList [Selector] -- ^ @a, h2, .myclass@ | Raw Text -- ^ plain string to be rendered-as is in CSS | Null -- ^ null string deriving (Eq,Show) instance S.IsString Selector where fromString = Raw . TL.pack -- |Serialize a 'Selector' into a 'Data.Text.Lazy.Builder' buildSelector :: Selector -> Builder buildSelector = f where between a e b = singleton a <> b <> singleton e parens = between '(' ')' bracketed = between '[' ']' curlyb = between '{' '}' quoted = between '"' '"' . fromLazyText attr e a v = bracketed $ fromLazyText a <> singleton e <> "=" <> quoted v f Null = mempty f (Raw v) = fromLazyText v f (Child a b) = f a <> ">" <> f b f (Descendant a b) = f a <> " " <> f b f (ImmediatePrecedence a b) = f a <> "+" <> f b f (Precedence a b) = f a <> "~" <> f b f (PseudoClass a n (Just b)) = f (PseudoClass a n Nothing) <> parens (f b) f (PseudoClass a n Nothing) = f a <> ":" <> fromLazyText n f (PseudoType a n (Just b)) = f (PseudoType a n Nothing) <> parens (f b) f (PseudoType a n Nothing) = f a <> "::" <> fromLazyText n f (Class a cls) = f a <> "." <> fromLazyText cls f (Id a i) = f a <> "#" <> fromLazyText i f (SelectorList xs) = mconcat $ map f $ intersperse "," xs f (AttrExistential s a) = f s <> bracketed (fromLazyText a) f (AttrEquality s a v) = f s <> bracketed (fromLazyText a <> "=" <> quoted v) f (AttrWhitespaceListContains s a v) = f s <> attr '~' a v f (AttrHyphenListContains s a v) = f s <> attr '|' a v f (AttrBeginsWith s a v) = f s <> attr '^' a v f (AttrEndsWith s a v) = f s <> attr '$' a v f (AttrSubstring s a v) = f s <> attr '*' a v f FontFace = "@font-face" -- TODO: write @instance Alternative Selector where ...@ -- use this alternative instance to OR Selectors for the following syntax: -- a,h2,h4{..} instance Monoid Selector where mempty = Null mappend Null x = x mappend x Null = x mappend (SelectorList xs) x = SelectorList $ x:xs mappend x (SelectorList xs) = SelectorList $ x:xs mappend a b = SelectorList [a,b] mconcat xs = SelectorList xs {- selector operators -} -- | Child selector. infixl 5 .>. (.>.) :: Selector -- ^ parent -> Selector -- ^ child -> Selector (.>.) = Child -- | immediate precedence. infixl 5 .+. (.+.) :: Selector -- ^ first sibling -> Selector -- ^ second sibling -> Selector (.+.) = ImmediatePrecedence -- |Match a pair of contiguous selectors. infixl 5 .~. (.~.) :: Selector -- ^ first selector -> Selector -- ^ second selector -> Selector (.~.) = Precedence -- |Match a descendant. infixl 5 .|. (.|.) :: Selector -- ^ ancestor -> Selector -- ^ descendant -> Selector (.|.) = Descendant -- |Add an id to a Selector. infixl 4 # (#) :: Selector -- ^ 'Selector' to add id to -> Text -- ^ id -> Selector (#) = Id -- |Add a class to a 'Selector'. infixl 4 ! (!) :: Selector -- ^ 'Selector' to add class to -> Text -- ^ class -> Selector (!) = Class -- |Add a pseudoclass to a 'Selector'. Does not -- allow for a parenthetial statement to be written -- as part of the pseudoclass. infixl 4 <:> (<:>) :: Selector -- ^ 'Selector' to add pseudoclass to -> Text -- ^ pseudoclass -> Selector (<:>) sel n = PseudoClass sel n Nothing -- |Create a pseudoclass. pseudoClass :: Text -- ^ the name of the pseudoclass -> Maybe Selector -- ^ maybe a parenthetical statement to follow the pseudoclass -> Selector pseudoClass = PseudoClass Null -- |Add a pseudotype to a 'Selector'. Does not -- allow for a parenthetial statement to be written -- as part of the pseudoclass. infixl 4 <::> (<::>) :: Selector -- ^ 'Selector' to add pseudotype to -> Text -- ^ pseudotype -> Selector (<::>) sel n = PseudoType sel n Nothing -- |Create a pseudotype. pseudoType :: Text -- ^ the name of the pseudotype -> Maybe Selector -- ^ maybe a parenthetical statement to follow the pseudotype -> Selector -- ^ pseudoType = PseudoType Null -- |Add aspect operator. Used to construct larger selectors -- from smaller ones. Often types, 'Selector's are constructed -- with the first argument set to 'Null', eg @Class Null "myclass"@. -- You can use this operator to create a selector like this: @h2.myclass@ -- by doing something like @(Raw "h2") \<||\> (Class Null "myclass")@ (which -- is equivalent to @Class (Raw "h2") "myclass"@). infixl 4 <||> (<||>) :: Selector -- selector to add aspect to -> Selector -- aspect -> Selector -- Null case (<||>) s Null = s (<||>) Null s = s -- "trait" cases (aspect modifies selector) (<||>) s (AttrExistential _ a) = AttrExistential s a (<||>) s (AttrEquality _ a b) = AttrEquality s a b (<||>) s (AttrWhitespaceListContains _ a l) = AttrWhitespaceListContains s a l (<||>) s (AttrHyphenListContains _ a l) = AttrHyphenListContains s a l (<||>) s (AttrBeginsWith _ a str) = AttrBeginsWith s a str (<||>) s (AttrEndsWith _ a str) = AttrEndsWith s a str (<||>) s (AttrSubstring _ a str) = AttrSubstring s a str (<||>) s (PseudoClass _ c m) = PseudoClass s c m (<||>) s (PseudoType _ c m) = PseudoType s c m (<||>) (SelectorList xs) a = SelectorList $ map (\s -> s <||> a) xs (<||>) s (Id _ i) = Id s i (<||>) s (Class _ c) = Class s c -- lineage case (<||>) s s' = Descendant s s' -- |Create a CSS @class@. cls :: Text -- ^ name of the @class@ -> Selector cls = Class Null -- |Create an CSS @id@. ident :: Text -- ^ name of the @id@ -> Selector ident = Id Null {- By-Attribute selector operators -} -- |Equality. infixl 3 |=| (|=|) :: Text -- ^ attribute name -> Text -- ^ desired value to test for equality -> Selector (|=|) = AttrEquality Null -- |Whitespace-separated list contains. infixl 3 |~=| (|~=|) :: Text -- ^ attribute name -> Text -- ^ value to be found in whitespace-separated list -> Selector (|~=|) = AttrWhitespaceListContains Null -- |Hyphen-separated list contains. infixl 3 ||=| (||=|) :: Text -- ^ attribute name -> Text -- ^ value to be found in hyphen-separated list -> Selector (||=|) = AttrHyphenListContains Null -- |Begins with. infixl 3 |^=| (|^=|) :: Text -- ^ attribute name -> Text -- ^ string beginning -> Selector (|^=|) = AttrBeginsWith Null -- |Ends with. infixl 3 |$=| (|$=|) :: Text -- ^ attribute name -> Text -- ^ string ending -> Selector (|$=|) = AttrEndsWith Null -- |Substring. infixl 3 |*=| (|*=|) :: Text -- ^ attribute name -> Text -- ^ substring in attribute -> Selector (|*=|) = AttrSubstring Null