{-# LANGUAGE OverloadedStrings #-} module Data.CSS.Style.Selector.Interpret( compile, SelectorFunc, InterpretedRuleStore(..) ) where import Data.CSS.Style.Common import Data.Text (unpack) import Data.List import Data.Maybe type SelectorFunc = Element -> Bool type AttrsFunc = [Attribute] -> Bool compile :: Selector -> SelectorFunc compile (Element sel) = compileInner sel compile (Child upSel sel) = direct parent (compile upSel) $ compileInner sel compile (Descendant up sel) = indirect parent (compile up) $ compileInner sel compile (Adjacent up sel) = direct previous (compile up) $ compileInner sel compile (Sibling up sel) = indirect previous (compile up) $ compileInner sel compileInner :: [SimpleSelector] -> SelectorFunc compileInner sel = compileInner' $ lowerInner sel compileInner' :: (Maybe Text, [(Text, String -> Bool)]) -> SelectorFunc compileInner' (Just tag, attrs) = testTag tag $ testAttrs (compileAttrs $ sortAttrs attrs) matched compileInner' (Nothing, attrs) = testAttrs (compileAttrs $ sortAttrs attrs) matched compileAttrs :: [(Text, String -> Bool)] -> AttrsFunc compileAttrs ((tag, test):attrs) = testAttr tag test $ compileAttrs attrs compileAttrs [] = matched lowerInner :: [SimpleSelector] -> (Maybe Text, [(Text, String -> Bool)]) lowerInner (Tag tag:sel) = (Just tag, snd $ lowerInner sel) lowerInner (Id i:s) = (tag, ("id", hasWord $ unpack i):attrs) where (tag, attrs) = lowerInner s lowerInner (Class c:s) = (tag, ("class", hasWord $ unpack c):attrs) where (tag, attrs) = lowerInner s lowerInner (Property prop test:s) = (tag, (prop, compileAttrTest test):attrs) where (tag, attrs) = lowerInner s -- psuedos, TODO handle argumented psuedoclasses. lowerInner (Psuedoclass c _:s) = (tag, ("", hasWord $ unpack c):attrs) where (tag, attrs) = lowerInner s lowerInner [] = (Nothing, []) compileAttrTest :: PropertyTest -> String -> Bool compileAttrTest Exists = matched compileAttrTest (Equals val) = (== (unpack val)) compileAttrTest (Suffix val) = isSuffixOf $ unpack val compileAttrTest (Prefix val) = isPrefixOf $ unpack val compileAttrTest (Substring val) = isInfixOf $ unpack val compileAttrTest (Include val) = hasWord $ unpack val compileAttrTest (Dash val) = hasLang $ unpack val sortAttrs :: [(Text, b)] -> [(Text, b)] sortAttrs = sortBy compareAttrs where compareAttrs x y = fst x `compare` fst y -------- ---- Runtime -------- testTag :: Text -> SelectorFunc -> SelectorFunc testTag tag success el | name el == tag = success el | otherwise = False testAttrs :: AttrsFunc -> SelectorFunc -> SelectorFunc testAttrs attrsTest success el | attrsTest $ attributes el = success el | otherwise = False direct :: (Element -> Maybe Element) -> SelectorFunc -> SelectorFunc -> SelectorFunc direct traverser upTest test el | Just up <- traverser el = test el && upTest up | otherwise = False indirect :: (Element -> Maybe Element) -> SelectorFunc -> SelectorFunc -> SelectorFunc indirect traverser upTest test el | Nothing <- traverser el = False | not $ test el = False | upTest (fromJust $ traverser el) = True | otherwise = indirect traverser upTest test $ fromJust $ traverser el matched :: t -> Bool matched _ = True testAttr :: Text -> (String -> Bool) -> AttrsFunc -> AttrsFunc testAttr expected test next attrs@(Attribute attr value : attrs') | attr < expected = testAttr expected test next attrs' | attr > expected = False | attr == expected && test value = next attrs | otherwise = False testAttr _ _ _ [] = False hasWord :: String -> String -> Bool hasWord expected value = expected `elem` words value hasLang :: [Char] -> [Char] -> Bool hasLang expected value = expected == value || isPrefixOf (expected ++ "-") value -------- ---- RuleStore wrapper -------- data InterpretedRuleStore inner = InterpretedRuleStore inner instance RuleStore inner => RuleStore (InterpretedRuleStore inner) where new = InterpretedRuleStore new addStyleRule (InterpretedRuleStore self) priority rule = InterpretedRuleStore $ addStyleRule self priority $ rule { compiledSelector = compile $ selector rule } lookupRules (InterpretedRuleStore self) el = filter call $ lookupRules self el where call (StyleRule' _ test _) = test el