A src/Data/CSS/Style.hs => src/Data/CSS/Style.hs +77 -0
@@ 0,0 1,77 @@
+module Data.CSS.Style(
+ QueryableStyleSheet(..), queryableStyleSheet,
+ queryRules,
+ PropertyParser(..), cascade
+ ) where
+
+import Data.CSS.Style.Selector.Index
+import Data.CSS.Style.Selector.Interpret
+import Data.CSS.Style.Selector.Specificity
+import Data.CSS.Style.Importance
+import Data.CSS.Style.Common
+
+-- TODO do performance tests to decide beside between strict/lazy,
+-- or is another Map implementation better?
+import Data.HashMap.Strict
+import Data.CSS.Syntax.Tokens
+
+type QueryableStyleSheet parser = QueryableStyleSheet' (ImportanceSplitter (
+ PropertyExpander parser (OrderedRuleStore (InterpretedRuleStore StyleIndex))
+ )) parser
+
+data QueryableStyleSheet' store parser = QueryableStyleSheet' {
+ store :: store,
+ parser :: parser,
+ priority :: Int -- author vs user agent vs user styles
+}
+
+queryableStyleSheet :: PropertyParser p => QueryableStyleSheet p
+queryableStyleSheet = QueryableStyleSheet' {store = new, parser = temp, priority = 0}
+
+instance (RuleStore s, PropertyParser p) => StyleSheet (QueryableStyleSheet' s p) where
+ addRule self@(QueryableStyleSheet' store _ priority) rule = self {
+ store = addStyleRule store priority $ styleRule' rule
+ }
+
+queryRules (QueryableStyleSheet' store _ _) el = lookupRules store el
+
+--------
+---- Cascade
+--------
+
+cascadeRules rules = cascadeProperties $ concat $ Prelude.map properties rules
+
+cascadeProperties ((name, value):props) = insert name value $ cascadeProperties props
+
+--------
+---- Dispatch to property definitions
+--------
+
+class PropertyParser a where
+ temp :: a
+ shorthand :: a -> Text -> [Token] -> [(Text, [Token])]
+ shorthand self name value | Just _ <- longhand self self name value = [(name, value)]
+ | otherwise = []
+ -- longhand parent self name value
+ longhand :: a -> a -> Text -> [Token] -> Maybe a
+
+cascade :: PropertyParser p => QueryableStyleSheet p -> Element -> p -> p
+cascade self el parent = dispatch parent parent $ toList $ cascadeRules $ queryRules self el
+
+dispatch parent child (name, value):props
+ | Just child' <- longhand parent child name value = dispatch parent child' props
+ | otherwise = dispatch parent child props
+
+--- 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 parser rule = rule {inner = StyleRule selector $ expandProperties parser properties}
+ where (StyleRule selector properties) = inner rule
+expandProperties parser ((name, value):props) =
+ shorthand parser name value ++ expandProperties parser props
+expandProperties _ [] = []
A src/Data/CSS/Style/Common.hs => src/Data/CSS/Style/Common.hs +44 -0
@@ 0,0 1,44 @@
+module Data.CSS.Style.Common(
+ RuleStore(..), StyleRule'(..), selector, properties, styleRule',
+ Element(..), Attribute(..),
+ -- Re-exports
+ Text(..), StyleRule(..), Selector(..), SimpleSelector(..), PropertyTest(..)
+ ) where
+
+import Data.CSS.Syntax.StyleSheet
+import Data.CSS.Syntax.Selector
+
+import Data.Text.Internal (Text(..))
+
+data Element = ElementNode {
+ parent :: Maybe Element,
+ previous :: Maybe Element,
+ name :: Text,
+ attributes :: [Attribute] -- in sorted order.
+}
+data Attribute = Attribute Text String
+
+class RuleStore a where
+ new :: a
+ addStyleRule :: a -> Int -> StyleRule' -> a
+ lookupRules :: a -> Element -> [StyleRule']
+
+type SelectorFunc = Element -> Bool
+data StyleRule' = StyleRule' {
+ inner :: StyleRule,
+ compiledSelector :: SelectorFunc,
+ rank :: (Int, (Int, Int, Int), Int) -- This reads ugly, but oh well.
+}
+styleRule' rule = StyleRule' {
+ inner = rule,
+ compiledSelector = \_ -> True,
+ rank = (0, (0, 0, 0), 0)
+}
+
+instance Eq StyleRule' where
+ a == b = inner a == inner b
+instance Show StyleRule' where show a = show $ inner a
+instance Ord StyleRule' where compare x y = rank x `compare` rank y
+
+selector rule | StyleRule selector _ <- inner rule = selector
+properties rule | StyleRule _ properties <- inner rule = properties
A src/Data/CSS/Style/Importance.hs => src/Data/CSS/Style/Importance.hs +28 -0
@@ 0,0 1,28 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Data.CSS.Style.Importance (
+ splitProperties, ImportanceSplitter(..)
+ ) where
+
+import Data.CSS.Syntax.Tokens
+import Data.CSS.Style.Common
+
+type Property = (Text, [Token])
+splitProperties :: [Property] -> ([Property], [Property])
+splitProperties (prop@(name, value):rest)
+ | (Ident "important":Delim '!':value') <- reverse value =
+ (unimportant, (name, reverse value'):important)
+ | otherwise = (prop:unimportant, important)
+ where (unimportant, important) = splitProperties rest
+
+data ImportanceSplitter a = ImportanceSplitter a
+instance RuleStore inner => RuleStore (ImportanceSplitter inner) where
+ new = ImportanceSplitter new
+ addStyleRule (ImportanceSplitter self) priority rule =
+ ImportanceSplitter $ addStyleRule (
+ addStyleRule self (negate priority) $ buildRule important
+ ) priority $ buildRule unimportant
+ where
+ (important, unimportant) = splitProperties properties
+ (StyleRule selector properties) = inner rule
+ buildRule properties = rule {inner = StyleRule selector properties}
+ lookupRules (ImportanceSplitter self) el = lookupRules self el
A src/Data/CSS/Style/Selector/Index.hs => src/Data/CSS/Style/Selector/Index.hs +86 -0
@@ 0,0 1,86 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Data.CSS.Style.Selector.Index (
+ StyleIndex(..),
+ rulesForElement
+ ) where
+
+-- TODO do performance tests to decide beside between strict/lazy.
+import Data.HashMap.Strict
+import Data.List (nub)
+import Data.CSS.Style.Common
+
+import Data.Hashable
+import Data.Text (unpack, pack)
+
+data StyleIndex = StyleIndex {
+ indexed :: HashMap SimpleSelector [StyleRule'],
+ unindexed :: [StyleRule']
+}
+
+lookup' :: SimpleSelector -> HashMap SimpleSelector [a] -> [a]
+lookup' = lookupDefault []
+
+instance RuleStore StyleIndex where
+ new = StyleIndex {indexed = empty, unindexed = []}
+ addStyleRule self _ rule | [] == properties rule = self
+ | otherwise = addRuleForSelector self rule $ simpleSelector $ selector rule
+ lookupRules self element = nub $ Prelude.foldr (++) [] rules
+ where
+ get key = lookup' key index
+ index = indexed self
+ rules = unindexed self : Prelude.map get (testsForElement element)
+
+rulesForElement :: StyleIndex -> Element -> [StyleRule] -- For testing
+rulesForElement self element = Prelude.map inner $ lookupRules self element
+
+---
+
+simpleSelector (Element s) = s
+simpleSelector (Child _ s) = s
+simpleSelector (Descendant _ s) = s
+simpleSelector (Adjacent _ s) = s
+simpleSelector (Sibling _ s) = s
+
+addRuleForSelector self rule [] = self {unindexed = rule : unindexed self}
+addRuleForSelector self rule selector = self {
+ indexed = insert key (rule : lookup' key index) index
+ } where
+ key = selectorKey selector
+ index = indexed self
+
+selectorKey (tok@(Tag _) : _) = tok
+selectorKey (tok@(Id _) : _) = tok
+selectorKey (tok@(Class _) : _) = tok
+selectorKey (Property prop _ : _) = Property prop Exists
+
+----
+
+testsForElement :: Element -> [SimpleSelector]
+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)
+testsForAttributes (Attribute "id" value:attrs) =
+ (Prelude.map (\s -> Id $ pack s) $ words value) ++
+ (Property "id" Exists : testsForAttributes attrs)
+testsForAttributes (Attribute name _:attrs) =
+ Property name Exists : testsForAttributes attrs
+testsForAttributes [] = []
+
+-- Implement hashable for SimpleSelector here because it proved challenging to automatically derive it.
+instance Hashable SimpleSelector where
+ hashWithSalt seed (Tag tag) = seed `hashWithSalt` (0::Int) `hashWithSalt` unpack tag
+ hashWithSalt seed (Id id) = seed `hashWithSalt` (1::Int) `hashWithSalt` unpack id
+ 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
+
+instance Hashable PropertyTest where
+ hashWithSalt seed Exists = seed `hashWithSalt` (0::Int)
+ hashWithSalt seed (Equals val) = seed `hashWithSalt` (1::Int) `hashWithSalt` unpack val
+ hashWithSalt seed (Suffix val) = seed `hashWithSalt` (2::Int) `hashWithSalt` unpack val
+ hashWithSalt seed (Prefix val) = seed `hashWithSalt` (3::Int) `hashWithSalt` unpack val
+ hashWithSalt seed (Substring val) = seed `hashWithSalt` (4::Int) `hashWithSalt` unpack val
+ hashWithSalt seed (Include val) = seed `hashWithSalt` (5::Int) `hashWithSalt` unpack val
+ hashWithSalt seed (Dash val) = seed `hashWithSalt` (6::Int) `hashWithSalt` unpack val
A src/Data/CSS/Style/Selector/Interpret.hs => src/Data/CSS/Style/Selector/Interpret.hs +89 -0
@@ 0,0 1,89 @@
+{-# 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 selector) = compileInner selector
+compile (Child upSelector selector) = direct parent (compile upSelector) $ compileInner selector
+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 selector = compileInner' $ lowerInner selector
+compileInner' :: (Maybe Text, [(Text, String -> Bool)]) -> SelectorFunc
+compileInner' (Just tag, attributes) = testTag tag $ testAttrs (compileAttrs $ sortAttrs attributes) matched
+compileInner' (Nothing, attributes) = testAttrs (compileAttrs $ sortAttrs attributes) matched
+compileAttrs :: [(Text, String -> Bool)] -> AttrsFunc
+compileAttrs ((name, test):attrs) = testAttr name test $ compileAttrs attrs
+compileAttrs [] = matched
+
+lowerInner (Tag tag:selector) = (Just tag, snd $ lowerInner selector)
+lowerInner (Id id:s) = (tag, ("id", hasWord $ unpack id):tail) where (tag, tail) = lowerInner s
+lowerInner (Class c:s) = (tag, ("class", hasWord $ unpack c):tail) where (tag, tail) = lowerInner s
+lowerInner (Property name test:s) = (tag, (name, compileAttrTest test):tail)
+ where (tag, tail) = lowerInner s
+lowerInner [] = (Nothing, [])
+
+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 = 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 traverser upTest test el
+ | Just parent <- traverser el = test el && upTest parent
+ | 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 _ = True
+
+testAttr :: Text -> (String -> Bool) -> AttrsFunc -> AttrsFunc
+testAttr expected test next attrs@(Attribute name value : attrs')
+ | name < expected = testAttr expected test next attrs'
+ | name > expected = False
+ | name == expected && test value = next attrs
+ | otherwise = False
+testAttr _ _ _ [] = False
+
+hasWord expected value = expected `elem` words value
+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
A src/Data/CSS/Style/Selector/Specificity.hs => src/Data/CSS/Style/Selector/Specificity.hs +36 -0
@@ 0,0 1,36 @@
+module Data.CSS.Style.Selector.Specificity(
+ OrderedRuleStore(..)
+ ) where
+
+import Stylish.Parse.Selector
+import Data.CSS.Style.Common
+import Data.List
+
+computeSpecificity :: Selector -> (Int, Int, Int)
+computeSpecificity (Element selector) = computeSpecificity' selector
+computeSpecificity (Child upSel sel) = computeSpecificity upSel `add` computeSpecificity' sel
+computeSpecificity (Descendant upSel sel) = computeSpecificity upSel `add` computeSpecificity' sel
+computeSpecificity (Adjacent upSel sel) = computeSpecificity upSel `add` computeSpecificity' sel
+computeSpecificity (Sibling upSel sel) = computeSpecificity upSel `add` computeSpecificity' sel
+
+computeSpecificity' (Tag _:sel) = computeSpecificity' sel `add` (0, 0, 1)
+computeSpecificity' (Class _: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)
+
+add :: (Int, Int, Int) -> (Int, Int, Int) -> (Int, Int, Int)
+add (a, b, c) (x, y, z) = (a + x, b + y, c + z)
+
+---
+
+data OrderedRuleStore inner = OrderedRuleStore inner Int
+
+instance RuleStore inner => RuleStore (OrderedRuleStore inner) where
+ new = OrderedRuleStore new 0
+ addStyleRule (OrderedRuleStore self count) priority rule = OrderedRuleStore (
+ addStyleRule self priority $ rule {
+ rank = (priority, computeSpecificity $ selector rule, count)
+ }
+ ) (count + 1)
+ lookupRules (OrderedRuleStore self _) el = sort $ lookupRules self el
A src/Data/CSS/Syntax/Selector.hs => src/Data/CSS/Syntax/Selector.hs +75 -0
@@ 0,0 1,75 @@
+module Data.CSS.Syntax.Selector(
+ Selector(..), SimpleSelector(..), PropertyTest(..),
+ parseSelectors
+ ) where
+
+import Data.CSS.Syntax.Tokens
+import Data.CSS.Syntax.StylishUtils
+
+import Data.Text.Internal (Text(..))
+
+-- type Selector = [SimpleSelector]
+data Selector = Element [SimpleSelector] |
+ Child Selector [SimpleSelector] | Descendant Selector [SimpleSelector] |
+ Adjacent Selector [SimpleSelector] | Sibling Selector [SimpleSelector]
+ deriving (Show, Eq)
+data SimpleSelector = Tag Text | Id Text | Class Text | Property Text PropertyTest
+ deriving (Show, Eq)
+data PropertyTest = Exists | Equals Text | Suffix Text | Prefix Text | Substring Text |
+ Include Text | Dash Text
+ deriving (Show, Eq)
+
+parseSelectors :: [Token] -> ([Selector], [Token])
+parseSelectors tokens = concatP (:) parseCompound parseSelectorsTail $ skipSpace tokens
+parseSelectorsTail (Comma:tokens) = parseSelectors tokens
+parseSelectorsTail tokens = ([], tokens)
+parseCompound tokens = parseCombinators (Element selector) tokens'
+ where (selector, tokens') = parseSelector tokens
+
+parseSelector' op tokens = (op:selector, tokens')
+ where (selector, tokens') = parseSelector tokens
+
+parseSelector (Delim '*':tokens) = parseSelector tokens
+parseSelector (Ident tag:tokens) = parseSelector' (Tag tag) tokens
+parseSelector (Hash _ id:tokens) = parseSelector' (Id id) tokens
+parseSelector (Delim '.':Ident class_:tokens) = parseSelector' (Class class_) tokens
+parseSelector (LeftSquareBracket:Ident prop:tokens) =
+ concatP appendPropertySel parsePropertySel parseSelector tokens
+ where appendPropertySel test selector = Property prop test : selector
+parseSelector tokens = ([], tokens)
+
+parseCombinators' selector tokens = parseCombinators selector' tokens'
+ where (selector', tokens') = parseCombinator selector tokens
+parseCombinators selector (Whitespace:tokens) = parseCombinators' selector tokens
+parseCombinators selector tokens@(Delim c:_) = parseCombinators' selector tokens
+parseCombinators selector tokens = (selector, tokens)
+
+parseCombinator' cb selector tokens = (cb selector selector', tokens')
+ where (selector', tokens') = parseSelector $ skipSpace tokens
+parseCombinator :: Selector -> [Token] -> (Selector, [Token])
+parseCombinator selector (Whitespace:tokens) = parseCombinator selector tokens
+parseCombinator selector (Delim '>':tokens) = parseCombinator' Child selector tokens
+parseCombinator selector (Delim '~':tokens) = parseCombinator' Sibling selector tokens
+parseCombinator selector (Delim '+':tokens) = parseCombinator' Adjacent selector tokens
+-- Take special care to avoid adding a trailing Descendant when not needed.
+parseCombinator selector tokens@(LeftCurlyBracket:_) = (selector, tokens)
+parseCombinator selector tokens@(RightCurlyBracket:_) = (selector, tokens)
+parseCombinator selector tokens@(RightSquareBracket:_) = (selector, tokens)
+parseCombinator selector tokens@(Comma:_) = (selector, tokens)
+
+parseCombinator selector tokens@(RightParen:_) = (selector, tokens)
+parseCombinator selector [] = (selector, [])
+
+parseCombinator selector tokens = parseCombinator' Descendant selector tokens
+
+parsePropertySel (RightSquareBracket:tokens) = (Exists, tokens)
+parsePropertySel (Delim '=':tokens) = parsePropertyVal (Equals) tokens
+parsePropertySel (SuffixMatch:tokens) = parsePropertyVal (Suffix) tokens
+parsePropertySel (PrefixMatch:tokens) = parsePropertyVal (Prefix) tokens
+parsePropertySel (SubstringMatch:tokens) = parsePropertyVal (Substring) tokens
+parsePropertySel (IncludeMatch:tokens) = parsePropertyVal (Include) tokens
+parsePropertySel (DashMatch:tokens) = parsePropertyVal (Dash) tokens
+parsePropertySel tokens = (Exists, skipBlock tokens)
+
+parsePropertyVal wrapper (Ident val:RightSquareBracket:tokens) = (wrapper val, tokens)
+parsePropertyVal wrapper (String val:RightSquareBracket:tokens) = (wrapper val, tokens)
A src/Data/CSS/Syntax/StyleSheet.hs => src/Data/CSS/Syntax/StyleSheet.hs +96 -0
@@ 0,0 1,96 @@
+module Data.CSS.Syntax.StyleSheet (
+ parse, TrivialStyleSheet(..),
+ StyleSheet(..), skipAtRule,
+ StyleRule(..)
+ ) where
+
+import Data.CSS.Syntax.Tokens
+import Data.CSS.Syntax.Selector
+import Data.CSS.Syntax.StylishUtils
+
+import Data.Text.Internal (Text(..))
+
+--------
+---- Output type class
+--------
+class StyleSheet s where
+ addRule :: s -> StyleRule -> s
+ addAtRule :: s -> Text -> [Token] -> (s, [Token])
+ addAtRule self _ tokens = (self, skipAtRule tokens)
+
+addRules self (selector:selectors, properties) = addRules self' (selectors, properties)
+ where self' = addRule self $ StyleRule selector properties
+addRules self ([], _) = self
+
+data StyleRule = StyleRule Selector [(Text, [Token])] deriving (Show, Eq)
+
+data TrivialStyleSheet = TrivialStyleSheet [StyleRule] deriving (Show, Eq)
+instance StyleSheet TrivialStyleSheet where
+ addRule (TrivialStyleSheet self) rule = TrivialStyleSheet $ rule:self
+
+--------
+---- Basic parsing
+--------
+parse :: StyleSheet s => s -> Text -> s
+parse stylesheet source = parse' stylesheet $ tokenize source
+
+-- Things to skip.
+parse' stylesheet (Whitespace:tokens) = parse' stylesheet tokens
+parse' stylesheet (CDO:tokens) = parse' stylesheet tokens
+parse' stylesheet (CDC:tokens) = parse' stylesheet tokens
+parse' stylesheet (Comma:tokens) = parse' stylesheet tokens -- TODO issue warnings.
+
+parse' stylesheet [] = stylesheet
+
+parse' stylesheet (AtKeyword kind:tokens) = parse' stylesheet' tokens'
+ where (stylesheet', tokens') = addAtRule stylesheet kind tokens
+parse' stylesheet tokens = parse' (addRules stylesheet rule) tokens'
+ where (rule, tokens') = concatP (,) parseSelectors parseProperties tokens
+
+--------
+---- Property parsing
+--------
+parseProperties (LeftCurlyBracket:tokens) = parseProperties' tokens
+parseProperties (Whitespace:tokens) = parseProperties tokens
+parseProperties [] = ([], [])
+
+parseProperties' (Whitespace:tokens) = parseProperties' tokens
+parseProperties' (Ident name:tokens)
+ | Colon:tokens' <- skipSpace tokens =
+ concatP appendProp scanValue parseProperties' tokens'
+ where appendProp value tail = (name, value):tail
+parseProperties' (RightCurlyBracket:tokens) = ([], tokens)
+parseProperties' [] = ([], [])
+parseProperties' tokens = parseProperties' (skipValue tokens)
+
+--------
+---- Skipping/Scanning utilities
+--------
+skipAtRule :: [Token] -> [Token]
+skipAtRule (Semicolon:tokens) = tokens
+skipAtRule (LeftCurlyBracket:tokens) = skipBlock tokens
+
+skipAtRule (LeftParen:tokens) = skipAtRule $ skipBlock tokens
+skipAtRule (LeftSquareBracket:tokens) = skipAtRule $ skipBlock tokens
+-- To ensure parens are balanced, should already be handled.
+skipAtRule (RightCurlyBracket:tokens) = RightCurlyBracket:tokens
+skipAtRule (RightParen:tokens) = RightParen:tokens
+skipAtRule (RightSquareBracket:tokens) = RightSquareBracket:tokens
+
+skipAtRule (_:tokens) = skipAtRule tokens
+skipAtRule [] = []
+
+scanValue (Semicolon:tokens) = ([], tokens)
+scanValue (Whitespace:tokens) = scanValue tokens
+
+scanValue (LeftCurlyBracket:tokens) = scanInner tokens scanValue
+scanValue (LeftParen:tokens) = scanInner tokens scanValue
+scanValue (LeftSquareBracket:tokens) = scanInner tokens scanValue
+-- To ensure parens are balanced, should already be handled.
+scanValue (RightCurlyBracket:tokens) = ([], RightCurlyBracket:tokens)
+scanValue (RightParen:tokens) = ([], RightParen:tokens)
+scanValue (RightSquareBracket:tokens) = ([], RightSquareBracket:tokens)
+
+scanValue tokens = capture scanValue tokens
+
+skipValue tokens = snd $ scanValue tokens
A src/Data/CSS/Syntax/StylishUtil.hs => src/Data/CSS/Syntax/StylishUtil.hs +34 -0
@@ 0,0 1,34 @@
+module Data.CSS.Syntax.StylishUtils(
+ concatP, capture, skipSpace,
+ scanBlock, skipBlock, scanInner
+ ) where
+
+import Data.CSS.Syntax.Tokens
+
+concatP join left right tokens = (join x y, remainder)
+ where
+ (x, tokens') = left tokens
+ (y, remainder) = right tokens'
+
+capture cb (token:tokens) = (token:captured, tokens')
+ where (captured, tokens') = cb tokens
+capture _ [] = ([], [])
+
+skipSpace (Whitespace:tokens) = skipSpace tokens
+skipSpace tokens = tokens
+
+-- TODO assert closing tags are correct
+-- But what should the error recovery be?
+scanBlock (RightCurlyBracket:tokens) = ([RightCurlyBracket], tokens)
+scanBlock (RightParen:tokens) = ([RightParen], tokens)
+scanBlock (RightSquareBracket:tokens) = ([RightSquareBracket], tokens)
+
+scanBlock (LeftCurlyBracket:tokens) = scanInner tokens scanBlock
+scanBlock (LeftParen:tokens) = scanInner tokens scanBlock
+scanBlock (LeftSquareBracket:tokens) = scanInner tokens scanBlock
+
+scanBlock tokens = capture scanBlock tokens
+
+skipBlock tokens = snd $ scanBlock tokens
+
+scanInner tokens cb = concatP (++) scanBlock cb tokens