~alcinnz/haskell-stylist

5ff74dcf36fd4b87d91b50bf820c7d70eaeb12ef — Adrian Cochrane 4 years ago 086da47
Parse namespace selectors.
M src/Data/CSS/Style/Common.hs => src/Data/CSS/Style/Common.hs +2 -0
@@ 20,6 20,8 @@ data Element = ElementNode {
    previous :: Maybe Element,
    -- | The element's name.
    name :: Text,
    -- | The element's namespace.
    namespace :: Text,
    -- | The element's attributes, in sorted order.
    attributes :: [Attribute]
}

M src/Data/CSS/Style/Selector/Index.hs => src/Data/CSS/Style/Selector/Index.hs +7 -6
@@ 58,7 58,7 @@ selectorKey :: [SimpleSelector] -> Maybe SimpleSelector
selectorKey (tok@(Tag _) : _) = Just tok
selectorKey (tok@(Id _) : _) = Just tok
selectorKey (tok@(Class _) : _) = Just tok
selectorKey (Property prop _ : _) = Just $ Property prop Exists
selectorKey (Property _ prop _ : _) = Just $ Property Nothing prop Exists
selectorKey (_ : tokens) = selectorKey tokens
selectorKey [] = Nothing



@@ 70,12 70,12 @@ testsForElement element =
    (Tag $ name element) : (testsForAttributes $ attributes element)
testsForAttributes (Attribute "class" value:attrs) =
    (Prelude.map (\s -> Class $ pack s) $ words value) ++
        (Property "class" Exists : testsForAttributes attrs)
        (Property Nothing "class" Exists : testsForAttributes attrs)
testsForAttributes (Attribute "id" value:attrs) =
    (Prelude.map (\s -> Id $ pack s) $ words value) ++
        (Property "id" Exists : testsForAttributes attrs)
        (Property Nothing "id" Exists : testsForAttributes attrs)
testsForAttributes (Attribute elName _:attrs) =
    Property elName Exists : testsForAttributes attrs
    Property Nothing elName Exists : testsForAttributes attrs
testsForAttributes [] = []

-- Implement hashable for SimpleSelector here because it proved challenging to automatically derive it.


@@ 83,10 83,11 @@ instance Hashable SimpleSelector where
    hashWithSalt seed (Tag tag) = seed `hashWithSalt` (0::Int) `hashWithSalt` unpack tag
    hashWithSalt seed (Id i) = seed `hashWithSalt` (1::Int) `hashWithSalt` unpack i
    hashWithSalt seed (Class class_) = seed `hashWithSalt` (2::Int) `hashWithSalt` unpack class_
    hashWithSalt seed (Property prop test) =
        seed `hashWithSalt` (3::Int) `hashWithSalt` unpack prop `hashWithSalt` test
    hashWithSalt seed (Property ns prop test) =
        seed `hashWithSalt` (3::Int) `hashWithSalt` unpack <$> ns `hashWithSalt` unpack prop `hashWithSalt` test
    hashWithSalt seed (Psuedoclass p args) =
        seed `hashWithSalt` (4::Int) `hashWithSalt` p `hashWithSalt` serialize args
    hashWithSalt seed (Namespace ns) = seed `hashWithSalt` (5::Int) `hashWithSalt` unpack ns

instance Hashable PropertyTest where
    hashWithSalt seed Exists = seed `hashWithSalt` (0::Int)

M src/Data/CSS/Style/Selector/Interpret.hs => src/Data/CSS/Style/Selector/Interpret.hs +6 -2
@@ 21,7 21,7 @@ import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))
type SelectorFunc = Element -> Bool
type AttrsFunc = [Attribute] -> Bool
-- Mostly here for the sake of pseudoclasses.
data IL = Tagname Text | Fail | Recursive Bool [Selector] | Nth Bool Integer Integer
data IL = Tagname Text | NS Text | Fail | Recursive Bool [Selector] | Nth Bool Integer Integer

-- | Converts a parsed CSS selector into a callable function.
compile :: Selector -> SelectorFunc


@@ 35,6 35,7 @@ compileInner :: [SimpleSelector] -> SelectorFunc
compileInner sel = compileInner' $ lowerInner sel
compileInner' :: ([IL], [(Text, String -> Bool)]) -> SelectorFunc
compileInner' ((Tagname tag:tests), attrs) = testTag tag $ compileInner' (tests, attrs)
compileInner' ((NS ns:tests), attrs) = testNS ns $ compileInner' (tests, attrs)
compileInner' ((Fail:_), _) = \_ -> False
compileInner' ((Recursive negate' sels:tests), attrs) =
    recursiveSelect negate' (map compile sels) $ compileInner' (tests, attrs)


@@ 48,10 49,11 @@ compileAttrs ((tag, test):attrs) = testAttr tag test $ compileAttrs attrs
compileAttrs [] = matched

lowerInner :: [SimpleSelector] -> ([IL], [(Text, String -> Bool)])
lowerInner (Namespace ns:sel) = (NS ns:tests, attrs) where (tests, attrs) = lowerInner sel
lowerInner (Tag tag:sel) = (Tagname tag:tests, attrs) where (tests, attrs) = lowerInner sel
lowerInner (Id i:s) = (tests, ("id", hasWord $ unpack i):attrs) where (tests, attrs) = lowerInner s
lowerInner (Class c:s) = (tests, ("class", hasWord $ unpack c):attrs) where (tests, attrs) = lowerInner s
lowerInner (Property prop test:s) = (tests, (prop, compileAttrTest test):attrs)
lowerInner (Property _ prop test:s) = (tests, (prop, compileAttrTest test):attrs)
    where (tests, attrs) = lowerInner s
-- psuedos, TODO handle argumented psuedoclasses.
lowerInner (Psuedoclass c args:s)


@@ 86,6 88,8 @@ sortAttrs = sortBy compareAttrs where compareAttrs x y = fst x `compare` fst y
testTag :: Text -> SelectorFunc -> SelectorFunc
testTag tag success el | name el == tag = success el
    | otherwise = False
testNS ns success el | namespace el == ns = success el
    | otherwise = False
testAttrs :: AttrsFunc -> SelectorFunc -> SelectorFunc
testAttrs attrsTest success el | attrsTest $ attributes el = success el
    | otherwise = False

M src/Data/CSS/Style/Selector/Specificity.hs => src/Data/CSS/Style/Selector/Specificity.hs +2 -1
@@ 19,13 19,14 @@ computeSpecificity "" (Sibling upSel sel) = computeSpecificity "" upSel `add` co
computeSpecificity _ sel = computeSpecificity "" sel `add` (0, 0, 1)

computeSpecificity' :: [SimpleSelector] -> Vec
computeSpecificity' (Namespace _:sel) = computeSpecificity' sel
computeSpecificity' (Tag _:sel) = computeSpecificity' sel `add` (0, 0, 1)
computeSpecificity' (Class _:sel) = computeSpecificity' sel `add` (0, 1, 0)
computeSpecificity' (Psuedoclass c args:sel)
    | c `elem` ["not", "is"], (sels, []) <- parseSelectors args =
        computeSpecificity' sel `add` maximum (map (computeSpecificity "") sels)
computeSpecificity' (Psuedoclass _ _:sel) = computeSpecificity' sel `add` (0, 1, 0)
computeSpecificity' (Property _ _:sel) = computeSpecificity' sel `add` (0, 1, 0)
computeSpecificity' (Property _ _ _:sel) = computeSpecificity' sel `add` (0, 1, 0)
computeSpecificity' (Id _:sel) = computeSpecificity' sel `add` (1, 0, 0)
computeSpecificity' [] = (0, 0, 0)


M src/Data/CSS/Syntax/Selector.hs => src/Data/CSS/Syntax/Selector.hs +7 -2
@@ 19,9 19,10 @@ data Selector = Element [SimpleSelector] -- ^ Selects a single element.
    deriving (Show, Eq)
-- | An individual test comprising a CSS stylesheet.
data SimpleSelector = Tag Text -- ^ Matches a tagname, e.g. "a"
    | Namespace Text
    | Id Text -- ^ Matches the "id" attribute, e.g. "#header"
    | Class Text -- ^ Matches the "class" attribute, e.g. ".ad"
    | Property Text PropertyTest -- ^ Matches a specified property
    | Property (Maybe Text) Text PropertyTest -- ^ Matches a specified property
    | Psuedoclass Text [Token] -- ^ Matches psuedoclasses provided by the caller (via a nameless property).
    deriving (Show, Eq)
-- | How should a property be matched.


@@ 49,13 50,17 @@ parseSelector' op tokens = (op:selector, tokens')
    where (selector, tokens') = parseSelector tokens

parseSelector :: Parser [SimpleSelector]
parseSelector (Ident ns:Delim '|':tokens) = parseSelector' (Namespace ns) tokens
parseSelector (Delim '*':tokens) = parseSelector tokens
parseSelector (Ident tag:tokens) = parseSelector' (Tag tag) tokens
parseSelector (Hash _ i:tokens) = parseSelector' (Id i) tokens
parseSelector (Delim '.':Ident class_:tokens) = parseSelector' (Class class_) tokens
parseSelector (LeftSquareBracket:Ident ns:Delim '|':Ident prop:tokens) =
        concatP appendPropertySel parsePropertySel parseSelector tokens
    where appendPropertySel test selector = Property (Just ns) prop test : selector
parseSelector (LeftSquareBracket:Ident prop:tokens) =
        concatP appendPropertySel parsePropertySel parseSelector tokens
    where appendPropertySel test selector = Property prop test : selector
    where appendPropertySel test selector = Property Nothing prop test : selector
parseSelector (Colon:Ident p:ts) = parseSelector' (Psuedoclass p []) ts
parseSelector (Colon:Function fn:tokens) =
        concatP appendPseudo scanBlock parseSelector tokens

M test/Test.hs => test/Test.hs +24 -10
@@ 68,7 68,7 @@ spec = do
                    StyleRule (Element [Id "id"]) [] ""
                ]
            parse emptyStyle "[attr] {}" `shouldBe` TrivialStyleSheet [
                    StyleRule (Element [Property "attr" Exists]) [] ""
                    StyleRule (Element [Property Nothing "attr" Exists]) [] ""
                ]
            parse emptyStyle "a , b {}" `shouldBe` TrivialStyleSheet [
                    StyleRule (Element [Tag "b"]) [] "",


@@ 99,6 99,7 @@ spec = do
            let index = addStyleRule styleIndex 0 $ styleRule' sampleRule
            let element = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [


@@ 109,6 110,7 @@ spec = do
            }
            let element2 = ElementNode {
                name = "b",
                namespace = "",
                parent = Just element,
                previous = Just element, -- Invalid tree, oh well.
                attributes = []


@@ 126,7 128,7 @@ spec = do
            rulesForElement index2 element `shouldBe` [rule2]
            rulesForElement index2 element2 `shouldBe` []

            let rule3 = StyleRule (Element [Property "href" $ Prefix "https://"]) [("color", [Ident "green"])] ""
            let rule3 = StyleRule (Element [Property Nothing "href" $ Prefix "https://"]) [("color", [Ident "green"])] ""
            let index3 = addStyleRule styleIndex 0 $ styleRule' rule3
            rulesForElement index3 element `shouldBe` [rule3]
            rulesForElement index3 element2 `shouldBe` []


@@ 134,6 136,7 @@ spec = do
        it "Correctly evaluates selectors" $ do
            let parentEl = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [


@@ 145,12 148,14 @@ spec = do
            }
            let sibling = ElementNode {
                name = "img",
                namespace = "",
                parent = Just parentEl,
                previous = Nothing,
                attributes = []
            }
            let child = ElementNode {
                name = "b",
                namespace = "",
                parent = Just parentEl,
                previous = Just sibling,
                attributes = []


@@ 171,42 176,42 @@ spec = do
            selector3 sibling `shouldBe` False
            selector3 child `shouldBe` False

            let selector4 = compile (Element [Property "lang" Exists])
            let selector4 = compile (Element [Property Nothing "lang" Exists])
            selector4 parentEl `shouldBe` True
            selector4 sibling `shouldBe` False
            selector4 child `shouldBe` False

            let selector5 = compile (Element [Property "class" $ Include "secure"])
            let selector5 = compile (Element [Property Nothing "class" $ Include "secure"])
            selector5 parentEl `shouldBe` True
            selector5 sibling `shouldBe` False
            selector5 child `shouldBe` False

            let selector6 = compile (Element [Property "href" $ Prefix "https://"])
            let selector6 = compile (Element [Property Nothing "href" $ Prefix "https://"])
            selector6 parentEl `shouldBe` True
            selector6 sibling `shouldBe` False
            selector6 child `shouldBe` False

            let selector7 = compile (Element [Property "href" $ Suffix ".html"])
            let selector7 = compile (Element [Property Nothing "href" $ Suffix ".html"])
            selector7 parentEl `shouldBe` True
            selector7 sibling `shouldBe` False
            selector7 child `shouldBe` False

            let selector8 = compile (Element [Property "href" $ Substring ".geek.nz"])
            let selector8 = compile (Element [Property Nothing "href" $ Substring ".geek.nz"])
            selector8 parentEl `shouldBe` True
            selector8 sibling `shouldBe` False
            selector8 child `shouldBe` False

            let selector9 = compile (Element [Property "lang" $ Dash "en"])
            let selector9 = compile (Element [Property Nothing "lang" $ Dash "en"])
            selector9 parentEl `shouldBe` True
            selector9 sibling `shouldBe` False
            selector9 child `shouldBe` False

            let selectorA = compile (Element [Property "lang" $ Dash "en-US"])
            let selectorA = compile (Element [Property Nothing "lang" $ Dash "en-US"])
            selectorA parentEl `shouldBe` True
            selectorA sibling `shouldBe` False
            selectorA child `shouldBe` False

            let selectorB = compile (Element [Property "lang" $ Dash "en-UK"])
            let selectorB = compile (Element [Property Nothing "lang" $ Dash "en-UK"])
            selectorB parentEl `shouldBe` False
            selectorB sibling `shouldBe` False
            selectorB child `shouldBe` False


@@ 236,6 241,7 @@ spec = do
        it "respects selector specificity" $ do
            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [Attribute "class" "link"]


@@ 246,6 252,7 @@ spec = do
        it "respects syntax order" $ do
            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [Attribute "class" "link"]


@@ 260,6 267,7 @@ spec = do
        it "respects stylesheet precedence" $ do
            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [Attribute "class" "link"]


@@ 271,6 279,7 @@ spec = do

            let el' = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [Attribute "class" "link"]


@@ 282,6 291,7 @@ spec = do
        it "respects overrides" $ do
            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = [Attribute "class" "link"]


@@ 324,6 334,7 @@ spec = do

            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = []


@@ 334,6 345,7 @@ spec = do
        it "applies within element" $ do
            let el = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = []


@@ 346,12 358,14 @@ spec = do
        it "inherits" $ do
            let parent = ElementNode {
                name = "a",
                namespace = "",
                parent = Nothing,
                previous = Nothing,
                attributes = []
            }
            let el = ElementNode {
                name = "b",
                namespace = "",
                parent = Just parent,
                previous = Nothing,
                attributes = []