~alcinnz/haskell-stylist

ref: 7a1b17010b6dc2f7326fa89bb7ce2662228a6f20 haskell-stylist/src/Data/CSS/Preprocessor/PsuedoClasses.hs -rw-r--r-- 6.0 KiB
7a1b1701 — Adrian Cochrane Extract parser, datatypes, & traits into separate decoupling hackage. 2 years ago
                                                                                
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
51a99e75 Adrian Cochrane
3df8d43c Adrian Cochrane
5c97dd0e Adrian Cochrane
15b2412e Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
3df8d43c Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
3df8d43c Adrian Cochrane
86efaacd Adrian Cochrane
15b2412e Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
ed0c2ba1 Adrian Cochrane
3df8d43c Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
51a99e75 Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
51a99e75 Adrian Cochrane
86efaacd Adrian Cochrane
cae4f2e0 Adrian Cochrane
86efaacd Adrian Cochrane
5a53c0de Adrian Cochrane
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
{-# LANGUAGE OverloadedStrings #-}
-- | Lowers psuedoclasses to rawer syntactic forms.
module Data.CSS.Preprocessor.PsuedoClasses(LowerPsuedoClasses(..),
    psuedoClassesFilter, htmlPsuedoFilter,
    addRewrite, addRewrite', addPsuedoEl, addNamespace, addTest, PropertyTest) where

import Data.CSS.Syntax.StyleSheet
import Data.CSS.Syntax.Selector
import Data.CSS.Syntax.Tokens

import Data.Text as Txt hiding (elem)
import Data.Maybe (fromMaybe, listToMaybe)
import Data.HashMap.Lazy as HM
import Data.Function ((&))

--------
---- 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 `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"]

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"