{-# LANGUAGE OverloadedStrings #-} -- | Lowers psuedoclasses to rawer syntactic forms. module Data.CSS.Preprocessor.PsuedoClasses(LowerPsuedoClasses(..), psuedoClassesFilter, htmlPsuedoFilter, addRewrite, addRewrite', addTest, addContains, PropertyTest, addPsuedoEl, addNamespace) where import Data.CSS.Syntax.StyleSheet import Data.CSS.Syntax.Selector import Data.CSS.Syntax.Tokens import Data.Text as Txt import Data.Maybe (fromMaybe, listToMaybe) import Data.HashMap.Lazy as HM import Data.Function ((&)) import Data.List as L (intercalate) -------- ---- core -------- type RewriteMap = HashMap Text ([Token] -> [SimpleSelector]) data LowerPsuedoClasses s = LowerPsuedoClasses { inner :: s, rewriteRules :: RewriteMap, psuedoEls :: [Text], namespaces :: HashMap Text Text } instance StyleSheet s => StyleSheet (LowerPsuedoClasses s) where setPriority p self = self { inner = setPriority p $ inner self } addRule self rule = self { inner = addRule (inner self) $ lowerRule self rule } addAtRule self "namespace" (Ident ns:toks) | (Url url:toks') <- skipSpace toks = (addNamespace ns url self, toks') addAtRule self name toks = let (inner', toks') = addAtRule (inner self) name toks in (self { inner = inner' }, toks') lowerRule :: LowerPsuedoClasses t -> StyleRule -> StyleRule lowerRule self@(LowerPsuedoClasses { psuedoEls = psuedos }) (StyleRule sel props "") | Just pseudo <- extractPseudoEl psuedos sel = lowerRule (addRewrite' pseudo [] self) $ StyleRule sel props pseudo lowerRule LowerPsuedoClasses { namespaces = ns, rewriteRules = rewrites } (StyleRule sel props pseudoel) = StyleRule (lowerSelector ns rewrites sel) props pseudoel extractPseudoEl :: [Text] -> Selector -> Maybe Text extractPseudoEl ps (Element sel) = extractPseudoEl' ps sel extractPseudoEl ps (Child _ sel) = extractPseudoEl' ps sel extractPseudoEl ps (Descendant _ sel) = extractPseudoEl' ps sel extractPseudoEl ps (Adjacent _ sel) = extractPseudoEl' ps sel extractPseudoEl ps (Sibling _ sel) = extractPseudoEl' ps sel extractPseudoEl' :: [Text] -> [SimpleSelector] -> Maybe Text extractPseudoEl' ps sel = listToMaybe [p | Psuedoclass p [] <- sel, p `Prelude.elem` ps] lowerSelector :: HashMap Text Text -> RewriteMap -> Selector -> Selector lowerSelector ns rewrites (Element sel') = Element $ lowerSelector' ns rewrites sel' lowerSelector ns rewrites (Child p sel') = Child (lowerSelector ns rewrites p) $ lowerSelector' ns rewrites sel' lowerSelector ns rewrites (Descendant p sel') = Descendant (lowerSelector ns rewrites p) $ lowerSelector' ns rewrites sel' lowerSelector ns rewrites (Adjacent sib sel') = Adjacent (lowerSelector ns rewrites sib) $ lowerSelector' ns rewrites sel' lowerSelector ns rewrites (Sibling sib sel') = Sibling (lowerSelector ns rewrites sib) $ lowerSelector' ns rewrites sel' lowerSelector' :: HashMap Text Text -> RewriteMap -> [SimpleSelector] -> [SimpleSelector] lowerSelector' namespaces' rewrites (Namespace ns:sels) = Namespace (fromMaybe "about:invalid" $ HM.lookup ns namespaces') : lowerSelector' namespaces' rewrites sels lowerSelector' ns rewrites (Psuedoclass name args:sels) | Just value <- name `HM.lookup` rewrites = value args ++ lowerSelector' ns rewrites sels lowerSelector' ns rewrites (sel:sels) = sel : lowerSelector' ns rewrites sels lowerSelector' _ _ [] = [] -------- ---- constructors -------- psuedoClassesFilter :: StyleSheet s => s -> LowerPsuedoClasses s psuedoClassesFilter s = LowerPsuedoClasses s HM.empty ["before", "after"] HM.empty addPsuedoEl :: Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s addPsuedoEl ps self = self { psuedoEls = psuedoEls self ++ Txt.words ps } addRewrite :: Text -> Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s addRewrite name sel self = addRewrite' name (tokenize sel) self addRewrite' :: Text -> [Token] -> LowerPsuedoClasses s -> LowerPsuedoClasses s addRewrite' name sel self = addTest' name (\args -> [Psuedoclass "where" $ spliceArgs sel args]) self where spliceArgs [] [] = [] spliceArgs (Ident "_":toks) (arg:args) = arg : spliceArgs toks args spliceArgs (tok:toks) args = tok : spliceArgs toks args spliceArgs _ _ = [Ident "\tfail"] addContains :: Text -> [Int] -> LowerPsuedoClasses s -> LowerPsuedoClasses s addContains name path self = addRewrite' name (L.intercalate [Comma] $ buildSelector path [Colon, Ident "root"]) self where buildSelector (p:ps) prefix = let prefix' = prefix ++ [Delim '>', Colon, Function "nth-child", num p, RightParen] in prefix' : buildSelector ps prefix' buildSelector [] _ = [] num x = Number (Txt.pack $ show x) $ NVInteger (toInteger x) addTest :: Text -> Maybe Text -> Text -> PropertyFunc -> LowerPsuedoClasses s -> LowerPsuedoClasses s addTest name ns attr test self = addTest' name (noArg [Property ns attr $ Callback test]) self where noArg sel [] = sel noArg _ _ = [Psuedoclass " fail" []] addTest' :: Text -> ([Token] -> [SimpleSelector]) -> LowerPsuedoClasses s -> LowerPsuedoClasses s addTest' name sel self = self {rewriteRules = insert name sel $ rewriteRules self } addNamespace :: Text -> Text -> LowerPsuedoClasses s -> LowerPsuedoClasses s addNamespace ns uri self = self { namespaces = insert ns uri $ namespaces self } htmlPsuedoFilter :: StyleSheet s => s -> LowerPsuedoClasses s htmlPsuedoFilter s = psuedoClassesFilter s & addRewrite "link" "[href]" & addRewrite "any-link" "[href]" & addRewrite "blank" "[value=''], :not([value])" & addRewrite "checked" "[checked]" & addRewrite "dir" "[dir=_], [dir=_] *" & addRewrite "disabled" "[disabled]" & addRewrite "enabled" ":not([disabled])" & addRewrite "first-child" ":nth-child(1)" & addRewrite "first-of-type" ":nth-of-type(1)" & addRewrite "indeterminate" "[indeterminate]" & addRewrite "lang" "[lang|=_], [lang|=_] *" & -- Not sure if I ever really want to support these, but might as well list them. -- Requires more data to be fed to the core CSS engine. addRewrite "last-child" ":nth-last-child(1)" & addRewrite "last-of-type" ":nth-last-of-type(1)" & addRewrite "only-child" ":nth-child(1):nth-last-child(1)" & addRewrite "only-of-type" ":nth-of-type(1):nth-last-of-type(1)" & -- No issue with remainder. addRewrite "optional" ":not([required])" & addRewrite "placeholder-shown" "[value=''][placeholder], [placeholder]:not([value])" & addRewrite "readonly" "[readonly], [disabled]" & addRewrite "read-write" ":not([readonly]):not([disabled])" & addRewrite "required" "[required]" & addRewrite "scope" ":root" & addRewrite "root" "html"