~alcinnz/haskell-stylist

ref: e4585f4a4b180fe20d62e804b35b100398e717ee haskell-stylist/src/Data/CSS/Style.hs -rw-r--r-- 6.7 KiB
e4585f4a — Adrian Cochrane Add more version bounds! 1 year, 6 months ago
                                                                                
6344dc8e Adrian Cochrane
186cbffa Adrian Cochrane
c1fca3d5 Adrian Cochrane
00ed62a1 Adrian Cochrane
c1fca3d5 Adrian Cochrane
d5d20109 Adrian Cochrane
3bd6ae52 Adrian Cochrane
c1fca3d5 Adrian Cochrane
086da474 Adrian Cochrane
c1fca3d5 Adrian Cochrane
2128054b Adrian Cochrane
6344dc8e Adrian Cochrane
c1fca3d5 Adrian Cochrane
d5d20109 Adrian Cochrane
6e0ed711 Adrian Cochrane
d5d20109 Adrian Cochrane
e65bf563 Adrian Cochrane
d5d20109 Adrian Cochrane
e65bf563 Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
c1fca3d5 Adrian Cochrane
086da474 Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
6e0ed711 Adrian Cochrane
afa21a79 Adrian Cochrane
6e0ed711 Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
c1fca3d5 Adrian Cochrane
6e0ed711 Adrian Cochrane
afa21a79 Adrian Cochrane
c1fca3d5 Adrian Cochrane
afa21a79 Adrian Cochrane
4bdb4bef Adrian Cochrane
00ed62a1 Adrian Cochrane
c1fca3d5 Adrian Cochrane
afa21a79 Adrian Cochrane
6e0ed711 Adrian Cochrane
afa21a79 Adrian Cochrane
6e0ed711 Adrian Cochrane
c1fca3d5 Adrian Cochrane
6344dc8e Adrian Cochrane
2277b56b Adrian Cochrane
6344dc8e Adrian Cochrane
4bdb4bef Adrian Cochrane
c1fca3d5 Adrian Cochrane
2277b56b Adrian Cochrane
6344dc8e Adrian Cochrane
c1fca3d5 Adrian Cochrane
e65bf563 Adrian Cochrane
6344dc8e Adrian Cochrane
e65bf563 Adrian Cochrane
c1fca3d5 Adrian Cochrane
00ed62a1 Adrian Cochrane
6344dc8e Adrian Cochrane
00ed62a1 Adrian Cochrane
c1fca3d5 Adrian Cochrane
d5d20109 Adrian Cochrane
2277b56b Adrian Cochrane
d5d20109 Adrian Cochrane
67f348bb Adrian Cochrane
d5d20109 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
{-# LANGUAGE OverloadedStrings #-}
-- | Queries computed styles out of a specially-parsed CSS stylesheet.
-- See in particular `QueryableStyleSheet`, `queryRules`, & `cascade'`.
module Data.CSS.Style(
        QueryableStyleSheet, QueryableStyleSheet'(..), queryableStyleSheet,
        queryRules,
        PropertyParser(..), cascade, cascade', VarParser(..),
        TrivialPropertyParser(..),
        Element(..), Attribute(..)
    ) where

import Data.CSS.Style.Selector.Index
import Data.CSS.Style.Selector.Interpret
import Data.CSS.Style.Selector.Specificity
import Data.CSS.Style.Selector.LowerWhere
import Data.CSS.Style.Importance
import Data.CSS.Style.Common
import qualified Data.CSS.Style.Cascade as Cascade
import Data.CSS.Style.Cascade (PropertyParser(..), TrivialPropertyParser, Props)

import Data.CSS.Syntax.Tokens (Token(..))
import Data.CSS.Syntax.StyleSheet (StyleSheet(..), skipAtRule)
import Data.CSS.Syntax.AtLayer as AtLayer

import Data.HashMap.Strict (HashMap, lookupDefault, fromList)
import qualified Data.HashMap.Strict as HM
import Data.Text (isPrefixOf)
import Data.List (elemIndex)
import Data.Maybe (fromMaybe)

-- | A parsed CSS stylesheet from which you can query styles to match an element.
type QueryableStyleSheet parser = QueryableStyleSheet' (ImportanceSplitter (
        PropertyExpander parser (
            OrderedRuleStore (WhereLowerer (InterpretedRuleStore StyleIndex))
        )
    )) parser

-- | More generic version of `QueryableStyleSheet`.
data QueryableStyleSheet' store parser = QueryableStyleSheet' {
    -- | Internal datastructure for efficient style lookup.
    store :: store,
    -- | The "PropertyParser" to use for property syntax validation.
    parser :: parser,
    -- | Whether author, useragent, or user styles are currently being parsed.
    -- The tail of this list indicates which Cascade Layer is active.
    priorities :: [Int], -- author vs user agent vs user styles, incorporates Cascade Layers
    -- | Parse data for @layer, to give webdevs explicit control over the cascade.
    layers :: AtLayer.Tree,
    --- | The name of the @layer we're within.
    layerNamespace :: [Text]
}

-- | Constructs an empty QueryableStyleSheet'.
queryableStyleSheet :: PropertyParser p => QueryableStyleSheet p
queryableStyleSheet = QueryableStyleSheet' {
    store = new, parser = temp, layers = AtLayer.emptyTree,
    priorities = [0], layerNamespace = [] }

instance (RuleStore s, PropertyParser p) => StyleSheet (QueryableStyleSheet' s p) where
    setPriorities vs self = self { priorities = vs }
    addRule self@(QueryableStyleSheet' store' _ priority' _ _) rule = self {
            store = addStyleRule store' priority' $ styleRule' rule
        }
    addAtRule self@QueryableStyleSheet' { layerNamespace = ns, layers = layers_, priorities = v:_ }
            "layer" toks =
        case parseAtLayer ns toks layers_ $ \ns' path -> self {
            priorities = v : path, layerNamespace = ns'
        } of
            (layers', Just self', toks') -> (self { store = store self', layers = layers' }, toks')
            (layers', Nothing, toks') -> (self { layers = layers' }, toks')
    addAtRule self _ toks = (self, skipAtRule toks)

--- Reexpose cascade methods
-- | Looks up style rules matching the specified element, grouped by psuedoelement.
queryRules :: (PropertyParser p, RuleStore s) =>
    QueryableStyleSheet' s p -> Element -> HashMap Text [StyleRule']
queryRules (QueryableStyleSheet' store' _ _ _ _) = Cascade.query store'

-- | Selects used property values from the given style rules,
-- & populates into a new `PropertyParser` inheriting from the one given.
cascade' :: PropertyParser p => [StyleRule'] -> Props -> p -> p
cascade' = Cascade.cascade

-- | Facade over `queryRules` & `cascade'`.
-- Instead of exposing pseudoelements to callers it exposes pseudoelements to
-- the `PropertyParser` implementation.
cascade :: PropertyParser p => QueryableStyleSheet p -> Element -> Props -> p -> p
cascade self el props style = HM.foldlWithKey' applyPseudoEl (cascade' (
        lookupDefault [] "" rules) props style) rules
  where
    rules = queryRules self el
    applyPseudoEl :: PropertyParser p => p -> Text -> [StyleRule'] -> p
    applyPseudoEl self' "" _ = self'
    applyPseudoEl self' pseudo props' = pseudoEl self' pseudo cb
        where cb parent' base' = Cascade.cascadeWithParent props' [] parent' $
                                    fromMaybe (inherit parent') base'

--- Verify syntax during parsing, so invalid properties don't interfere with cascade.
data PropertyExpander parser inner = PropertyExpander parser inner
instance (PropertyParser parser, RuleStore inner) => RuleStore (PropertyExpander parser inner) where
    new = PropertyExpander temp new
    addStyleRule (PropertyExpander parser' inner') priority' rule =
        PropertyExpander parser' $ addStyleRule inner' priority' $ expandRule parser' rule
    lookupRules (PropertyExpander _ inner') el = lookupRules inner' el

expandRule :: PropertyParser t => t -> StyleRule' -> StyleRule'
expandRule parser' rule = rule {inner = StyleRule sel (expandProperties parser' props) psuedo}
    where (StyleRule sel props psuedo) = inner rule
expandProperties :: PropertyParser t => t -> [(Text, [Token])] -> [(Text, [Token])]
expandProperties parser' ((key, value):props) =
        shorthand parser' key value ++ expandProperties parser' props
expandProperties _ [] = []

--------
---- var()
--------
-- | `PropertyParser` that lowers var() calls before forwarding to another.
data VarParser a = VarParser {vars :: Props, innerParser :: a}

instance PropertyParser p => PropertyParser (VarParser p) where
    temp = VarParser [] temp
    inherit (VarParser vars' self) = VarParser vars' $ inherit self
    priority (VarParser _ self) = priority self

    shorthand self name' value
        | Function "var" `elem` value || "--" `isPrefixOf` name' = [(name', value)] -- Fail during inheritance...
        | otherwise = shorthand (innerParser self) name' value
    longhand parent' self@(VarParser vars' inner') name' value
        | Function "var" `elem` value = resolveVars value (fromList vars') >>= longhand parent' self name'
        | otherwise = VarParser vars' <$> longhand (innerParser parent') inner' name' value

    getVars = vars
    setVars v self = self {vars = v}

resolveVars :: [Token] -> HashMap Text [Token] -> Maybe [Token]
resolveVars (Function "var":Ident var:RightParen:toks) ctxt = (lookupDefault [] var ctxt ++) <$> resolveVars toks ctxt
resolveVars (Function "var":Ident var:Comma:toks) ctxt
    | Just i <- RightParen `elemIndex` toks, (fallback, RightParen:toks') <- i `splitAt` toks =
        (lookupDefault fallback var ctxt ++) <$> resolveVars toks' ctxt
resolveVars (Function "var":_) _ = Nothing
resolveVars (tok:toks) ctxt = (tok:) <$> resolveVars toks ctxt
resolveVars [] _ = Just []