M README.md => README.md +1 -1
@@ 1,7 1,7 @@
# Haskell Stylist
Generic CSS style engine for Haskell, intended to aid the development of new browser engines.
-Stylish Haskell implements CSS selection and cascade (but not inheritance) independant of the CSS at-rules and properties understood by the caller. It is intended to ease the development of new browser engines, independant of their output targets.
+Haskell Stylist implements CSS selection and cascade (but not inheritance) independant of the CSS at-rules and properties understood by the caller. It is intended to ease the development of new browser engines, independant of their output targets.
For more interesting projects see: https://github.io/alcinnz/browser-engine-ganarchy/
M src/Data/CSS/Preprocessor/Conditions.hs => src/Data/CSS/Preprocessor/Conditions.hs +53 -5
@@ 16,6 16,7 @@ import Data.CSS.Syntax.StyleSheet
import Data.CSS.Syntax.Selector
import Data.CSS.Syntax.Tokens(Token(..))
import Data.CSS.Style (PropertyParser(..))
+import Data.CSS.Syntax.AtLayer as AtLayer
import Data.Text.Internal (Text(..))
import Data.Text (unpack)
@@ 36,15 37,22 @@ data ConditionalStyles p = ConditionalStyles {
-- | Queued style rules, to be evaluated later.
rules :: [ConditionalRule p],
-- | PropertyParser to test against for `@supports` rules.
- propertyParser :: p
+ propertyParser :: p,
+ -- | Known-named @layers.
+ layers :: AtLayer.Tree,
+ -- | The current @layer, for resolving nesting
+ layerNamespace :: [Text],
+ -- | The integral path to the current @layer, for resolving nesting
+ layerPath' :: [Int]
}
-- | Constructs an empty `ConditionalStyles`.
conditionalStyles :: PropertyParser p => URI -> String -> ConditionalStyles p
-conditionalStyles uri mediaDocument' = ConditionalStyles uri mediaDocument' False [] temp
+conditionalStyles uri mediaDocument' =
+ ConditionalStyles uri mediaDocument' False [] temp AtLayer.emptyTree [] [0]
-- | Style rules that can be queued in a `ConditionalStyles`.
-data ConditionalRule p = Priority Int | StyleRule' StyleRule | AtRule Text [Token] |
+data ConditionalRule p = Priority [Int] | StyleRule' StyleRule | AtRule Text [Token] |
External Query.Expr URI | Internal Query.Expr (ConditionalStyles p)
addRule' :: ConditionalStyles p -> ConditionalRule p -> ConditionalStyles p
@@ 60,7 68,7 @@ parseAtBlock self (_:toks) = parseAtBlock self toks
parseAtBlock self [] = (self, [])
instance PropertyParser p => StyleSheet (ConditionalStyles p) where
- setPriority x self = addRule' self $ Priority x
+ setPriorities x self = addRule' self { layerPath' = x } $ Priority x
addRule self rule = addRule' self $ StyleRule' rule
addAtRule self "document" (Whitespace:toks) = addAtRule self "document" toks
@@ 107,6 115,15 @@ instance PropertyParser p => StyleSheet (ConditionalStyles p) where
if evalSupports (propertyParser self) cond
then parseAtBlock self toks' else (self, skipAtRule toks')
+ addAtRule self@ConditionalStyles { layers = l, layerNamespace = ns, layerPath' = xs@(x:_) }
+ "layer" toks =
+ case parseAtLayer ns toks l $ \ns' path' -> setPriorities (x:path') self {
+ layerNamespace = ns'
+ } of
+ (layers', Just self', toks') ->
+ (setPriorities xs self { rules = rules self', layers = layers' }, toks')
+ (layers', Nothing, toks') -> (setPriorities xs self { layers = layers' }, toks')
+
addAtRule self rule tokens = let (block, rest) = scanAtRule tokens in
(addRule' self $ AtRule rule block, rest)
@@ 120,11 137,42 @@ testIsStyled styles = styles { isUnstyled = null $ rules styles }
--------
parseAtImport :: PropertyParser p => ConditionalStyles p -> Text ->
[Token] -> (ConditionalStyles p, [Token])
+parseAtImport self src (Whitespace:toks) = parseAtImport self src toks
+parseAtImport self src (Function "supports":toks)
+ | (cond, RightParen:toks') <- break (== RightParen) toks =
+ if evalSupports (propertyParser self) cond
+ then parseAtImport self src toks' else (self, skipAtRule toks')
+parseAtImport self@ConditionalStyles { layerNamespace = ns } src (Function "layer":toks)
+ | (layerToks, RightParen:toks') <- break (== RightParen) toks, validLayer layerToks =
+ parseAtImportInLayer self src (ns ++ [name | Ident name <- layerToks]) toks'
+ where
+ validLayer toks' = validLayer' (Delim '.':filter (/= Whitespace) toks')
+ validLayer' (Delim '.':Ident _:toks') = validLayer toks'
+ validLayer' [] = True
+ validLayer' _ = False
+parseAtImport self@ConditionalStyles { layers = l, layerNamespace = ns } src (Ident "layer":toks) =
+ parseAtImportInLayer self src (uniqueName ns l) toks
parseAtImport self src toks
| (cond, Semicolon:toks') <- Query.parse Semicolon toks, Just uri <- parseURI $ unpack src =
(addRule' self $ External cond uri, toks')
parseAtImport self _ toks = (self, skipAtRule toks)
+parseAtImportInLayer :: PropertyParser p => ConditionalStyles p -> Text -> [Text] ->
+ [Token] -> (ConditionalStyles p, [Token])
+parseAtImportInLayer self@ConditionalStyles {
+ layers = l, layerNamespace = ns, layerPath' = xs@(x:_)
+ } src layerName toks =
+ let (ret, toks') = parseAtImport self' src toks in (setPriorities xs ret, toks')
+ where
+ layers' = registerLayer layerName l
+ self' = setPriorities (x:layerPath layerName layers') self {
+ layers = layers',
+ layerNamespace = ns
+ }
+parseAtImportInLayer self src layerName toks = parseAtImportInLayer self {
+ layerPath' = [0]
+ } src layerName toks -- Shouldn't happen, recover gracefully.
+
-- | Returns `@import` URLs that need to be imported.
extractImports :: (Text -> Query.Datum) -> (Token -> Query.Datum) -> ConditionalStyles p -> [URI]
extractImports vars evalToken self =
@@ 157,7 205,7 @@ resolve :: StyleSheet s => (Text -> Query.Datum) -> (Token -> Query.Datum) ->
resolve v t styles self = resolve' v t (reverse $ rules self) styles
resolve' :: StyleSheet s => (Text -> Query.Datum) -> (Token -> Query.Datum) ->
[ConditionalRule p] -> s -> s
-resolve' v t (Priority x:rules') styles = resolve' v t rules' $ setPriority x styles
+resolve' v t (Priority x:rules') styles = resolve' v t rules' $ setPriorities x styles
resolve' v t (StyleRule' rule:rules') styles = resolve' v t rules' $ addRule styles rule
resolve' v t (AtRule name block:rules') styles = resolve' v t rules' $ fst $ addAtRule styles name block
resolve' v t (Internal cond block:rules') styles | Query.eval v t cond =
M src/Data/CSS/Preprocessor/PsuedoClasses.hs => src/Data/CSS/Preprocessor/PsuedoClasses.hs +16 -3
@@ 2,16 2,18 @@
-- | Lowers psuedoclasses to rawer syntactic forms.
module Data.CSS.Preprocessor.PsuedoClasses(LowerPsuedoClasses(..),
psuedoClassesFilter, htmlPsuedoFilter,
- addRewrite, addRewrite', addPsuedoEl, addNamespace, addTest, PropertyTest) where
+ addRewrite, addRewrite', addTest, addContains, PropertyTest,
+ addPsuedoEl, addNamespace) 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.Text as Txt
import Data.Maybe (fromMaybe, listToMaybe)
import Data.HashMap.Lazy as HM
import Data.Function ((&))
+import Data.List as L (intercalate)
--------
---- core
@@ 88,6 90,16 @@ addRewrite' name sel self =
spliceArgs (tok:toks) args = tok : spliceArgs toks args
spliceArgs _ _ = [Ident "\tfail"]
+addContains :: Text -> [Int] -> LowerPsuedoClasses s -> LowerPsuedoClasses s
+addContains name path self =
+ addRewrite' name (L.intercalate [Comma] $ buildSelector path [Colon, Ident "root"]) self
+ where
+ buildSelector (p:ps) prefix =
+ let prefix' = prefix ++ [Delim '>', Colon, Function "nth-child", num p, RightParen]
+ in prefix' : buildSelector ps prefix'
+ buildSelector [] _ = []
+ num x = Number (Txt.pack $ show x) $ NVInteger (toInteger x)
+
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
@@ 124,4 136,5 @@ htmlPsuedoFilter s = psuedoClassesFilter s &
addRewrite "readonly" "[readonly], [disabled]" &
addRewrite "read-write" ":not([readonly]):not([disabled])" &
addRewrite "required" "[required]" &
- addRewrite "scope" ":root"
+ addRewrite "scope" ":root" &
+ addRewrite "root" "html"
M src/Data/CSS/Style.hs => src/Data/CSS/Style.hs +23 -6
@@ 19,7 19,9 @@ 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(..))
+import Data.CSS.Syntax.StyleSheet (StyleSheet(..), skipAtRule)
+import Data.CSS.Syntax.AtLayer as AtLayer
+
import Data.HashMap.Strict (HashMap, lookupDefault, fromList)
import Data.Text (isPrefixOf)
import Data.List (elemIndex)
@@ 38,24 40,39 @@ data QueryableStyleSheet' store parser = QueryableStyleSheet' {
-- | The "PropertyParser" to use for property syntax validation.
parser :: parser,
-- | Whether author, useragent, or user styles are currently being parsed.
- priority :: Int -- author vs user agent vs user styles
+ -- The tail of this list indicates which Cascade Layer is active.
+ priority :: [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, priority = 0}
+queryableStyleSheet = QueryableStyleSheet' {
+ store = new, parser = temp, layers = AtLayer.emptyTree,
+ priority = [0], layerNamespace = [] }
instance (RuleStore s, PropertyParser p) => StyleSheet (QueryableStyleSheet' s p) where
- setPriority v self = self {priority = v}
- addRule self@(QueryableStyleSheet' store' _ priority') rule = self {
+ setPriorities vs self = self { priority = vs }
+ addRule self@(QueryableStyleSheet' store' _ priority' _ _) rule = self {
store = addStyleRule store' priority' $ styleRule' rule
}
+ addAtRule self@QueryableStyleSheet' { layerNamespace = ns, layers = layers_, priority = v:_ }
+ "layer" toks =
+ case parseAtLayer ns toks layers_ $ \ns' path -> self {
+ priority = 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'
+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.
M src/Data/CSS/Style/Cascade.hs => src/Data/CSS/Style/Cascade.hs +1 -25
@@ 8,6 8,7 @@ module Data.CSS.Style.Cascade(
import Data.CSS.Style.Common
import Data.CSS.Syntax.Tokens
+import Stylist (PropertyParser(..), Props)
-- TODO do performance tests to decide beside between strict/lazy,
-- or is another Map implementation better?
@@ 15,28 16,6 @@ import Data.HashMap.Strict
import qualified Data.HashMap.Lazy as HML
import Data.Text (unpack, pack, isPrefixOf)
--- | Defines how to parse CSS properties into an output "style" format.
-class PropertyParser a where
- -- | Default styles.
- temp :: a
- -- | Creates a style inherited from a parent style.
- inherit :: a -> a
- inherit = id
-
- -- | Expand a shorthand property into longhand properties.
- shorthand :: a -> Text -> [Token] -> [(Text, [Token])]
- shorthand self key value | Just _ <- longhand self self key value = [(key, value)]
- | otherwise = []
- -- longhand parent self name value
- longhand :: a -> a -> Text -> [Token] -> Maybe a
-
- -- | Retrieve stored variables, optional.
- getVars :: a -> Props
- getVars _ = []
- -- | Save variable values, optional.
- setVars :: Props -> a -> a
- setVars _ = id
-
-- | Gather properties into a hashmap.
data TrivialPropertyParser = TrivialPropertyParser (HashMap String [Token]) deriving (Show, Eq)
instance PropertyParser TrivialPropertyParser where
@@ 44,9 23,6 @@ instance PropertyParser TrivialPropertyParser where
longhand _ (TrivialPropertyParser self) key value =
Just $ TrivialPropertyParser $ insert (unpack key) value self
--- | "key: value;" entries to be parsed into an output type.
-type Props = [(Text, [Token])]
-
--------
---- Query/Psuedo-elements
--------
M src/Data/CSS/Style/Common.hs => src/Data/CSS/Style/Common.hs +4 -19
@@ 11,39 11,24 @@ import Data.CSS.Syntax.StyleSheet
import Data.CSS.Syntax.Selector
import Data.CSS.Syntax.Tokens
import Data.Text.Internal (Text(..))
-
--- | An inversely-linked tree of elements, to apply CSS selectors to.
-data Element = ElementNode {
- -- | The element's parent in the tree.
- parent :: Maybe Element,
- -- | The element's previous sibling in the tree.
- previous :: Maybe Element,
- -- | The element's name.
- name :: Text,
- -- | The element's namespace.
- namespace :: Text,
- -- | The element's attributes, in sorted order.
- attributes :: [Attribute]
-}
--- | A key-value attribute.
-data Attribute = Attribute Text Text String deriving (Eq, Ord)
+import Stylist (Element(..), Attribute(..))
class RuleStore a where
new :: a
- addStyleRule :: a -> Int -> StyleRule' -> 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.
+ rank :: ([Int], (Int, Int, Int), Int) -- This reads ugly, but oh well.
}
styleRule' :: StyleRule -> StyleRule'
styleRule' rule = StyleRule' {
inner = rule,
compiledSelector = \_ -> True,
- rank = (0, (0, 0, 0), 0)
+ rank = ([0], (0, 0, 0), 0)
}
instance Eq StyleRule' where
M src/Data/CSS/Style/Importance.hs => src/Data/CSS/Style/Importance.hs +1 -1
@@ 26,7 26,7 @@ instance RuleStore inner => RuleStore (ImportanceSplitter inner) where
new = ImportanceSplitter new
addStyleRule (ImportanceSplitter self) priority rule =
ImportanceSplitter $ addStyleRule (
- addStyleRule self (negate priority) $ buildRule unimportant
+ addStyleRule self (map negate priority) $ buildRule unimportant
) priority $ buildRule important
where
(unimportant, important) = splitProperties props
M src/Data/CSS/Style/Selector/Interpret.hs => src/Data/CSS/Style/Selector/Interpret.hs +2 -18
@@ 7,6 7,7 @@ module Data.CSS.Style.Selector.Interpret(
) where
import Data.CSS.Style.Common
+import Stylist (compileAttrTest, matched, hasWord)
import Data.Text (unpack)
import Data.List
@@ 14,7 15,7 @@ import Data.Maybe
import Data.Bits (xor)
-- For pseudoclasses
-import Data.CSS.Syntax.Selector (parseSelectors, PropertyFunc(..))
+import Data.CSS.Syntax.Selector (parseSelectors)
import Data.CSS.Syntax.Tokens (Token(..), NumericValue(..))
-- | A compiled(?) CSS selector.
@@ 73,16 74,6 @@ lowerInner (Psuedoclass c []:s) =
lowerInner (Psuedoclass _ _:_) = ([Fail], [])
lowerInner [] = ([], [])
-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
-compileAttrTest (Callback (PropertyFunc cb)) = cb
-
sortAttrs :: [(Text, Maybe Text, b)] -> [(Text, Maybe Text, b)]
sortAttrs = sortBy compareAttrs where compareAttrs (x, x', _) (y, y', _) = (x, x') `compare` (y, y')
@@ 106,8 97,6 @@ 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')
@@ 124,11 113,6 @@ testAttrNS expectedNS expected test next attrs@(Attribute attr ns value : attrs'
| otherwise = False
testAttrNS _ _ _ _ [] = False
-hasWord :: String -> String -> Bool
-hasWord expected value = expected `elem` words value
-hasLang :: [Char] -> [Char] -> Bool
-hasLang expected value = expected == value || isPrefixOf (expected ++ "-") value
-
--- Pseudoclasses
recursiveSelect :: Bool -> [SelectorFunc] -> SelectorFunc -> SelectorFunc
recursiveSelect negate' sels success el | negate' `xor` any ($ el) sels = success el
M src/Data/CSS/Style/Selector/Specificity.hs => src/Data/CSS/Style/Selector/Specificity.hs +4 -1
@@ 40,7 40,10 @@ 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 (psuedoElement rule) $ selector rule, count)
+ rank = (
+ priority ++ [maxBound], -- Ensure unlayered rules take precedance.
+ computeSpecificity (psuedoElement rule) $ selector rule,
+ count)
}
) (count + 1)
lookupRules (OrderedRuleStore self _) el = sort $ lookupRules self el
M src/Data/CSS/StyleTree.hs => src/Data/CSS/StyleTree.hs +34 -42
@@ 1,50 1,42 @@
-- | Abstracts away tree traversals.
-- Mostly used by callers including (soon) XML Conduit Stylist,
-- but also used internally for generating counter text.
+--
+-- Backwards compatability module, this API has been moved out into "stylist-traits".
+-- Though it also contains integration between the styletree & styling APIs.
+{-# LANGUAGE OverloadedStrings #-}
module Data.CSS.StyleTree(StyleTree(..), treeOrder, treeOrder',
- Path, treeMap, treeFlatten, preorder, preorder', postorder) where
+ Path, treeMap, treeFlatten, preorder, preorder', postorder,
+ stylize, inlinePseudos) where
-data StyleTree p = StyleTree {
- style :: p,
- children :: [StyleTree p]
-}
+import Stylist.Tree -- Mainly for reexports
-type Path = [Integer]
-treeOrder :: (c -> c -> Path -> p -> (c, p')) ->
- c -> StyleTree p -> StyleTree p'
-treeOrder cb ctxt tree = StyleTree
- (snd $ cb ctxt ctxt [] $ style tree)
- (snd $ treeOrder' cb ctxt ctxt [0] $ children tree)
-treeOrder' :: (c -> c -> Path -> p -> (c, p')) ->
- c -> c -> Path -> [StyleTree p] -> (c, [StyleTree p'])
-treeOrder' cb prevContext context (num:path) (node:nodes) = (tailContext, StyleTree node' children' : nodes')
- where
- (selfContext, node') = cb prevContext context (num:path) $ style node
- (childContext, children') = treeOrder' cb selfContext selfContext (0:num:path) $ children node
- (tailContext, nodes') = treeOrder' cb selfContext childContext (num + 1:path) nodes
-treeOrder' _ _ context _ [] = (context, [])
-treeOrder' _ _ _ [] _ = error "Invalid path during tree traversal!"
+import Stylist
+import Data.CSS.Style
+import Data.CSS.Syntax.StyleSheet (parseProperties')
+import Data.CSS.Syntax.Tokens
+import Data.Text (Text, pack)
+import Data.HashMap.Strict as M (toList)
+import Data.Maybe (fromMaybe)
-treeMap :: (p -> p') -> StyleTree p -> StyleTree p'
-treeMap cb = treeOrder (\_ _ _ p -> ((), cb p)) ()
+stylize :: PropertyParser s => QueryableStyleSheet s -> StyleTree Element -> StyleTree [(Text, s)]
+stylize = preorder . stylize'
+stylize' :: PropertyParser s => QueryableStyleSheet s -> Maybe [(Text, s)] -> Maybe [(Text, s)] ->
+ Element -> [(Text, s)]
+stylize' stylesheet parent' _ el = ("", base) : [
+ (k, cascade' v [] base) | (k, v) <- M.toList $ queryRules stylesheet el
+ ] where
+ base = cascade stylesheet el overrides $ fromMaybe temp $ lookup "" =<< parent'
+ overrides = concat [fst $ parseProperties' $ tokenize $ pack val
+ | Attribute "style" _ val <- attributes el]
-treeFlatten :: StyleTree p -> [p]
-treeFlatten = treeFlatten' . children
-treeFlatten' :: [StyleTree p] -> [p]
-treeFlatten' (StyleTree p []:ps) = p : treeFlatten' ps
-treeFlatten' (StyleTree _ childs:sibs) = treeFlatten' childs ++ treeFlatten' sibs
-treeFlatten' [] = []
-
-preorder :: (Maybe b -> Maybe b -> a -> b) -> StyleTree a -> StyleTree b
-preorder cb self = head $ preorder' cb Nothing Nothing [self]
-preorder' :: (Maybe b -> Maybe b -> a -> b) ->
- Maybe b -> Maybe b -> [StyleTree a] -> [StyleTree b]
-preorder' cb parent previous (self:sibs) = let self' = cb parent previous $ style self
- in StyleTree self' (preorder' cb (Just self') Nothing $ children self) :
- preorder' cb parent (Just self') sibs
-preorder' _ _ _ [] = []
-
-postorder :: (a -> [b] -> [b]) -> StyleTree a -> [StyleTree b]
-postorder cb (StyleTree self childs) =
- [StyleTree self' children' | self' <- cb self $ Prelude.map style children']
- where children' = concat $ Prelude.map (postorder cb) childs
+inlinePseudos :: PropertyParser s => StyleTree [(Text, VarParser s)] -> StyleTree s
+inlinePseudos (StyleTree self childs) = StyleTree {
+ style = fromMaybe temp $ innerParser <$> lookup "" self,
+ children = pseudo "before" ++ map inlinePseudos childs ++ pseudo "after"
+ } where
+ pseudo n
+ | Just sty <- innerParser <$> lookup n self,
+ Just style' <- longhand sty sty "::" [Ident n] = [StyleTree style' []]
+ | Just sty <- innerParser <$> lookup n self = [StyleTree sty []]
+ | otherwise = []
A src/Data/CSS/Syntax/AtLayer.hs => src/Data/CSS/Syntax/AtLayer.hs +74 -0
@@ 0,0 1,74 @@
+module Data.CSS.Syntax.AtLayer(parseAtLayer, Tree(..),
+ registerLayer, layerPath, uniqueName, emptyTree) where
+
+import Data.HashMap.Lazy as M (HashMap, (!?), insert, size, empty)
+import Data.Text as T hiding (reverse, replicate, length)
+import Data.CSS.Syntax.Tokens
+
+import Stylist.Parse
+
+parseAtLayer :: StyleSheet s => [Text] -> [Token] -> Tree ->
+ ([Text] -> [Int] -> s) -> (Tree, Maybe s, [Token])
+parseAtLayer namespace (Whitespace:toks) tree cb = parseAtLayer namespace toks tree cb
+parseAtLayer namespace (Ident layer:toks) tree cb = inner toks [layer] tree
+ where
+ inner (Delim '.':Ident sublayer:toks') layers tree' = inner toks' (sublayer:layers) tree'
+ inner (Whitespace:toks') layers tree' = inner toks' layers tree'
+ inner (Comma:toks') layers tree' =
+ let (ret, tail') = parseLayerStmt namespace toks' $registerLayer (namespaced layers) tree'
+ in (ret, Nothing, tail')
+ inner (LeftCurlyBracket:toks') layers tree' =
+ let (ret, styles, tail') = parseLayerBlock (namespaced layers) toks' tree' cb
+ in (ret, Just styles, tail')
+ inner (Semicolon:toks') layers tree' = (registerLayer (namespaced layers) tree', Nothing, toks')
+ inner [] layers tree' = (registerLayer (namespaced layers) tree', Nothing, [])
+ inner toks' _ _ = (tree, Nothing, skipAtRule toks')
+ namespaced layers = namespace ++ reverse layers
+parseAtLayer ns (LeftCurlyBracket:toks) tree cb =
+ let (ret, styles, tail') = parseLayerBlock (uniqueName ns tree) toks tree cb
+ in (ret, Just styles, tail')
+parseAtLayer _ toks tree _ = (tree, Nothing, skipAtRule toks)
+
+parseLayerStmt :: [Text] -> [Token] -> Tree -> (Tree, [Token])
+parseLayerStmt namespace (Whitespace:toks) tree = parseLayerStmt namespace toks tree
+parseLayerStmt namespace (Ident layer:toks) tree = inner toks [layer] tree
+ where
+ inner (Delim '.':Ident sublayer:toks') layers tree' = inner toks' (sublayer:layers) tree'
+ inner (Comma:toks') layers tree' =
+ parseLayerStmt namespace toks' $ registerLayer (namespaced layers) tree'
+ inner (Whitespace:toks') layers tree' = inner toks' layers tree'
+ inner (Semicolon:toks') layers tree' = (registerLayer (namespaced layers) tree', toks')
+ inner [] layers tree' = (registerLayer (namespaced layers) tree', [])
+ inner toks' _ _ = (tree, skipAtRule toks')
+ namespaced layers = namespace ++ reverse layers
+parseLayerStmt _ toks tree = (tree, skipAtRule toks)
+
+parseLayerBlock :: StyleSheet s => [Text] -> [Token] -> Tree ->
+ ([Text] -> [Int] -> s) -> (Tree, s, [Token])
+parseLayerBlock layers toks tree cb = (tree', parse' styles block, toks')
+ where
+ (block, toks') = scanBlock toks
+ tree' = registerLayer layers tree
+ styles = cb layers $ layerPath layers tree'
+
+newtype Tree = Tree (HashMap Text (Int, Tree))
+registerLayer :: [Text] -> Tree -> Tree
+registerLayer (layer:sublayers) (Tree self)
+ | Just (ix, subtree) <- self !? layer = Tree $ insert layer (ix, registerLayer sublayers subtree) self
+ | otherwise = Tree $ insert layer (succ $ size self, registerLayer sublayers $ Tree M.empty) self
+registerLayer [] self = self
+
+layerPath :: [Text] -> Tree -> [Int]
+layerPath (layer:sublayers) (Tree self)
+ | Just (ix, subtree) <- self !? layer = ix:layerPath sublayers subtree
+ | otherwise = [] -- Should have registered first...
+layerPath [] _ = []
+
+uniqueName :: [Text] -> Tree -> [Text]
+uniqueName (namespace:namespaces) (Tree self)
+ | Just (_, subtree) <- self !? namespace = namespace:uniqueName namespaces subtree
+ | otherwise = replicate (length namespaces + 2) T.empty -- Should have registered first
+uniqueName [] (Tree self) = [T.pack $ show $ size self]
+
+emptyTree :: Tree
+emptyTree = Tree M.empty
M src/Data/CSS/Syntax/Selector.hs => src/Data/CSS/Syntax/Selector.hs +3 -112
@@ 1,119 1,10 @@
-- | Parses CSS selectors
-- See `parseSelectors`
+--
+-- Backwards-compatibility module, this API has been moved out into "stylist-traits".
module Data.CSS.Syntax.Selector(
Selector(..), SimpleSelector(..), PropertyTest(..), PropertyFunc(..),
parseSelectors
) where
-import Data.CSS.Syntax.Tokens
-import Data.CSS.Syntax.StylishUtil
-
-import Data.Text.Internal (Text(..))
-
--- | A CSS "selector" indicating which elements should be effected by CSS.
-data Selector = Element [SimpleSelector] -- ^ Selects a single element.
- | Child Selector [SimpleSelector] -- ^ Represents "a > b" operator.
- | Descendant Selector [SimpleSelector] -- ^ Represents "a b" operator.
- | Adjacent Selector [SimpleSelector] -- ^ Represents "a + b" operator.
- | Sibling Selector [SimpleSelector] -- ^ Represents "a ~ b" operator.
- 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 (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.
-data PropertyTest = Exists -- ^ Matches whether an attribute actually exists, e.g. "[title]"
- | Equals Text -- ^ Matches whether the attribute is exactly equal to the value, e.g. "="
- | Suffix Text -- ^ Matches whether attribute ends with the given value, e.g. "$="
- | Prefix Text -- ^ Matches whether attribute starts with the given value, e.g. "^="
- | Substring Text -- ^ Matches whether the attribute contains the given value, e.g. "*="
- | Include Text -- ^ Is one of the whitespace-seperated values the one specified? e.g. "~="
- | Dash Text -- ^ Matches whitespace seperated values, or their "-"-seperated prefixes. e.g. "|="
- | Callback PropertyFunc -- ^ Calls the given function to test this property.
- deriving (Show, Eq)
--- | Caller-specified functions to extend property selection.
--- Has incorrect Show/Eq implementations so this rare exception doesn't break things.
-data PropertyFunc = PropertyFunc (String -> Bool)
-instance Show PropertyFunc where
- show _ = "xx"
-instance Eq PropertyFunc where
- _ == _ = False
-
--- | Parses a CSS selector.
-parseSelectors :: Parser [Selector]
-parseSelectors tokens = concatP (:) parseCompound parseSelectorsTail $ skipSpace tokens
-parseSelectorsTail :: Parser [Selector]
-parseSelectorsTail (Comma:tokens) = parseSelectors tokens
-parseSelectorsTail tokens = ([], tokens)
-parseCompound :: Parser Selector
-parseCompound tokens = parseCombinators (Element selector) tokens'
- where (selector, tokens') = parseSelector tokens
-
-parseSelector' :: SimpleSelector -> Parser [SimpleSelector]
-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 Nothing prop test : selector
-parseSelector (Colon:Ident p:ts) = parseSelector' (Psuedoclass p []) ts
-parseSelector (Colon:Function fn:tokens) =
- concatP appendPseudo scanBlock parseSelector tokens
- where appendPseudo args selector = Psuedoclass fn args : selector
-parseSelector tokens = ([], tokens)
-
-parseCombinators' :: Selector -> Parser Selector
-parseCombinators' selector tokens = parseCombinators selector' tokens'
- where (selector', tokens') = parseCombinator selector tokens
-parseCombinators :: Selector -> Parser Selector
-parseCombinators selector (Whitespace:tokens) = parseCombinators' selector tokens
-parseCombinators selector tokens@(Delim _:_) = parseCombinators' selector tokens
-parseCombinators selector tokens = (selector, tokens)
-
-parseCombinator' :: (Selector -> [SimpleSelector] -> Selector)
- -> Selector -> Parser Selector
-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 :: Parser PropertyTest
-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 :: (Text -> PropertyTest) -> Parser PropertyTest
-parsePropertyVal wrapper (Ident val:RightSquareBracket:tokens) = (wrapper val, tokens)
-parsePropertyVal wrapper (String val:RightSquareBracket:tokens) = (wrapper val, tokens)
-parsePropertyVal _ tokens = (Exists, skipBlock tokens)
+import Stylist.Parse.Selector
M src/Data/CSS/Syntax/StyleSheet.hs => src/Data/CSS/Syntax/StyleSheet.hs +3 -138
@@ 1,6 1,8 @@
{-# LANGUAGE OverloadedStrings #-}
-- | Parses a CSS stylesheet
-- See `StyleSheet` & `parseForURL`.
+--
+-- Backwards-compatability module, this API has been moved out into "stylist-traits".
module Data.CSS.Syntax.StyleSheet (
parse, parse', parseForURL, TrivialStyleSheet(..),
StyleSheet(..), skipAtRule, scanAtRule, scanBlock, skipSpace,
@@ 11,141 13,4 @@ module Data.CSS.Syntax.StyleSheet (
scanValue
) where
-import Data.CSS.Syntax.Tokens
-import Data.CSS.Syntax.Selector
-import Data.CSS.Syntax.StylishUtil
-
-import Data.Text.Internal (Text(..))
-import Data.Text (pack, unpack)
-import Network.URI (parseRelativeReference, relativeTo, uriToString, URI(..))
-
---------
----- Output type class
---------
--- | Describes how to store, and to some extent parse, CSS stylesheets.
--- These methods are used to construct the results from `parse`, etc.
-class StyleSheet s where
- -- | Sets the stylesheet priority (useragent vs user vs author), optional.
- setPriority :: Int -> s -> s
- setPriority _ = id
- -- | Stores a parsed selector+properties rule.
- addRule :: s -> StyleRule -> s
- -- | Stores and parses an identified at-rule.
- addAtRule :: s -> Text -> [Token] -> (s, [Token])
- addAtRule self _ tokens = (self, skipAtRule tokens)
-
--- | Stores the parsed selector*s*+proeprties rule.
-addRules :: StyleSheet ss => ss -> ([Selector], ([(Text, [Token])], Text)) -> ss
-addRules self (selector:selectors, val@(props, psuedoel)) = addRules self' (selectors, val)
- where self' = addRule self $ StyleRule selector props psuedoel
-addRules self ([], _) = self
-
--- | The properties to set for elements matching the given selector.
-data StyleRule = StyleRule Selector [(Text, [Token])] Text deriving (Show, Eq)
-
--- | Gathers StyleRules into a list, mainly for testing.
-data TrivialStyleSheet = TrivialStyleSheet [StyleRule] deriving (Show, Eq)
-instance StyleSheet TrivialStyleSheet where
- addRule (TrivialStyleSheet self) rule = TrivialStyleSheet $ rule:self
-
---------
----- Basic parsing
---------
--- | Parse a CSS stylesheet
-parse :: StyleSheet s => s -> Text -> s
-parse stylesheet source = parse' stylesheet $ tokenize source
-
--- | Parse a CSS stylesheet, resolving all URLs to absolute form.
-parseForURL :: StyleSheet s => s -> URI -> Text -> s
-parseForURL stylesheet base source = parse' stylesheet $ rewriteURLs $ tokenize source
- where
- rewriteURLs (Url text:toks)
- | Just url <- parseRelativeReference $ unpack text =
- Url (pack $ uriToString id (relativeTo url base) "") : rewriteURLs toks
- | otherwise = Function "url" : RightParen : rewriteURLs toks
- rewriteURLs (tok:toks) = tok : rewriteURLs toks
- rewriteURLs [] = []
-
--- | Parse a tokenized (via `css-syntax`) CSS stylesheet
-parse' :: StyleSheet t => t -> [Token] -> t
--- 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 $ skipSpace tokens
-parse' stylesheet tokens = parse' (addRules stylesheet rule) tokens'
- where (rule, tokens') = concatP (,) parseSelectors parseProperties tokens
-
---------
----- Property parsing
---------
--- | Parse "{key: value; ...}" property values, with a psuedoelement.
-parseProperties :: Parser ([(Text, [Token])], Text)
-parseProperties (LeftCurlyBracket:tokens) = noPsuedoel $ parseProperties' tokens
-parseProperties (Whitespace:tokens) = parseProperties tokens
-parseProperties (Colon:Colon:Ident n:tokens) = ((val, n), tokens')
- where ((val, _), tokens') = parseProperties tokens
--- This error recovery is a bit overly conservative, but it's simple.
-parseProperties (_:tokens) = noPsuedoel ([], skipAtRule tokens)
-parseProperties [] = noPsuedoel ([], [])
-
-noPsuedoel :: (x, y) -> ((x, Text), y)
-noPsuedoel (val, tokens) = ((val, ""), tokens)
-
--- | Parse "key: value;"... property values, as per the HTML "style" property.
-parseProperties' :: Parser [(Text, [Token])]
-parseProperties' (Whitespace:tokens) = parseProperties' tokens
-parseProperties' (Ident name:tokens)
- | Colon:tokens' <- skipSpace tokens =
- concatP appendProp scanValue parseProperties' tokens'
- where appendProp value props = (name, value):props
-parseProperties' (RightCurlyBracket:tokens) = ([], tokens)
-parseProperties' [] = ([], [])
-parseProperties' tokens = parseProperties' (skipValue tokens)
-
---------
----- Skipping/Scanning utilities
---------
--- | Returns tokens before & after an at-rule value, terminated after a curly-bracketed block or a semicolon.
-scanAtRule :: Parser [Token]
-scanAtRule (Semicolon:tokens) = ([Semicolon], tokens)
-scanAtRule tokens@(LeftCurlyBracket:_) = scanInner tokens $ \rest -> ([], rest)
-
-scanAtRule tokens@(LeftParen:_) = scanInner tokens scanValue
-scanAtRule tokens@(Function _:_) = scanInner tokens scanValue
-scanAtRule tokens@(LeftSquareBracket:_) = scanInner tokens scanValue
--- To ensure parens are balanced, should already be handled.
-scanAtRule (RightCurlyBracket:tokens) = ([], RightCurlyBracket:tokens)
-scanAtRule (RightParen:tokens) = ([], RightParen:tokens)
-scanAtRule (RightSquareBracket:tokens) = ([], RightSquareBracket:tokens)
-
-scanAtRule tokens = capture scanAtRule tokens
-
--- | Returns tokens after an at-rule, as per `scanAtRule`.
-skipAtRule :: [Token] -> [Token]
-skipAtRule tokens = snd $ scanAtRule tokens
-
--- | Returns tokens before & after a semicolon.
-scanValue :: Parser [Token]
-scanValue (Semicolon:tokens) = ([], tokens)
-scanValue (Whitespace:tokens) = scanValue tokens
-
-scanValue tokens@(LeftCurlyBracket:_) = scanInner tokens scanValue
-scanValue tokens@(LeftParen:_) = scanInner tokens scanValue
-scanValue tokens@(Function _:_) = scanInner tokens scanValue
-scanValue tokens@(LeftSquareBracket:_) = 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
-
--- | Returns tokens after a semicolon.
-skipValue :: [Token] -> [Token]
-skipValue tokens = snd $ scanValue tokens
+import Stylist.Parse
A stylist-traits/CHANGELOG.md => stylist-traits/CHANGELOG.md +5 -0
@@ 0,0 1,5 @@
+# Revision history for stylist-traits
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
A stylist-traits/LICENSE => stylist-traits/LICENSE +674 -0
@@ 0,0 1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
A stylist-traits/Setup.hs => stylist-traits/Setup.hs +2 -0
@@ 0,0 1,2 @@
+import Distribution.Simple
+main = defaultMain
A stylist-traits/src/Stylist.hs => stylist-traits/src/Stylist.hs +100 -0
@@ 0,0 1,100 @@
+module Stylist(cssPriorityAgent, cssPriorityUser, cssPriorityAuthor,
+ PropertyParser(..), TrivialPropertyParser(..),
+ StyleSheet(..), TrivialStyleSheet(..), Props,
+ Element(..), Attribute(..),
+ elementPath, compileAttrTest, matched, attrTest, hasWord, hasLang) where
+
+import Data.Text (Text, unpack)
+import Data.CSS.Syntax.Tokens (Token)
+import Data.List
+
+import Stylist.Parse (StyleSheet(..), TrivialStyleSheet(..))
+import Stylist.Parse.Selector
+
+-- | Set the priority for a CSS stylesheet being parsed.
+cssPriorityAgent, cssPriorityUser, cssPriorityAuthor :: StyleSheet s => s -> s
+cssPriorityAgent = setPriority 1
+cssPriorityUser = setPriority 2
+cssPriorityAuthor = setPriority 3
+
+-- | Defines how to parse CSS properties into an output "style" format.
+class PropertyParser a where
+ -- | Default styles.
+ temp :: a
+ -- | Creates a style inherited from a parent style.
+ inherit :: a -> a
+ inherit = id
+
+ -- | Expand a shorthand property into longhand properties.
+ shorthand :: a -> Text -> [Token] -> [(Text, [Token])]
+ shorthand self key value | Just _ <- longhand self self key value = [(key, value)]
+ | otherwise = []
+ -- longhand parent self name value
+ longhand :: a -> a -> Text -> [Token] -> Maybe a
+
+ -- | Retrieve stored variables, optional.
+ getVars :: a -> Props
+ getVars _ = []
+ -- | Save variable values, optional.
+ setVars :: Props -> a -> a
+ setVars _ = id
+
+-- | "key: value;" entries to be parsed into an output type.
+type Props = [(Text, [Token])]
+
+-- | Gathers properties as a key'd list.
+-- Works well with `lookup`.
+data TrivialPropertyParser = TrivialPropertyParser [(String, [Token])] deriving (Show, Eq)
+instance PropertyParser TrivialPropertyParser where
+ temp = TrivialPropertyParser []
+ longhand _ (TrivialPropertyParser self) key value =
+ Just $ TrivialPropertyParser ((unpack key, value):self)
+
+-- | An inversely-linked tree of elements, to apply CSS selectors to.
+data Element = ElementNode {
+ -- | The element's parent in the tree.
+ parent :: Maybe Element,
+ -- | The element's previous sibling in the tree.
+ previous :: Maybe Element,
+ -- | The element's name.
+ name :: Text,
+ -- | The element's namespace.
+ namespace :: Text,
+ -- | The element's attributes, in sorted order.
+ attributes :: [Attribute]
+}
+-- | A key-value attribute.
+data Attribute = Attribute Text Text String deriving (Eq, Ord)
+
+elementPath :: Element -> [Int]
+elementPath = elementPath' []
+elementPath' path ElementNode { parent = Just parent', previous = prev } =
+ elementPath' (succ (countSib prev) : path) parent'
+elementPath' path ElementNode { parent = Nothing, previous = prev } =
+ (succ (countSib prev) : path)
+countSib (Just (ElementNode { previous = prev })) = succ $ countSib prev
+countSib Nothing = 0
+
+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
+compileAttrTest (Callback (PropertyFunc cb)) = cb
+
+matched :: t -> Bool
+matched _ = True
+hasWord :: String -> String -> Bool
+hasWord expected value = expected `elem` words value
+hasLang :: [Char] -> [Char] -> Bool
+hasLang expected value = expected == value || isPrefixOf (expected ++ "-") value
+
+attrTest :: Maybe Text -> Text -> PropertyTest -> Element -> Bool
+attrTest namespace name test ElementNode { attributes = attrs } = any predicate attrs
+ where
+ predicate attr@(Attribute ns' _ _) | Just ns <- namespace = ns == ns' && predicate' attr
+ | otherwise = predicate' attr
+ predicate' (Attribute _ name' value') = name == name' && compileAttrTest test value'
A stylist-traits/src/Stylist/Parse.hs => stylist-traits/src/Stylist/Parse.hs +159 -0
@@ 0,0 1,159 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- | Parses a CSS stylesheet
+-- See `StyleSheet` & `parseForURL`.
+module Stylist.Parse (
+ parse, parse', parseForURL, TrivialStyleSheet(..),
+ StyleSheet(..), skipAtRule, scanAtRule, scanBlock, skipSpace,
+ StyleRule(..),
+ -- For parsing at-rules, HTML "style" attribute, etc.
+ parseProperties, parseProperties',
+ -- for testing
+ scanValue
+ ) where
+
+import Data.CSS.Syntax.Tokens
+import Stylist.Parse.Selector
+import Stylist.Parse.Util
+
+import Data.Text.Internal (Text(..))
+import Data.Text (pack, unpack)
+import Network.URI (parseRelativeReference, relativeTo, uriToString, URI(..))
+
+--------
+---- Output type class
+--------
+-- | Describes how to store, and to some extent parse, CSS stylesheets.
+-- These methods are used to construct the results from `parse`, etc.
+class StyleSheet s where
+ -- | Sets the stylesheet priority (useragent vs user vs author), optional.
+ -- Favor `setPriorities` for richer API.
+ setPriority :: Int -> s -> s
+ setPriority v self = setPriorities [v] self
+ -- | Sets the multi-layered stylesheet priority (for the sake of @layer rules), optional.
+ setPriorities :: [Int] -> s -> s
+ setPriorities _ = id
+ -- | Stores a parsed selector+properties rule.
+ addRule :: s -> StyleRule -> s
+ -- | Stores and parses an identified at-rule.
+ addAtRule :: s -> Text -> [Token] -> (s, [Token])
+ addAtRule self _ tokens = (self, skipAtRule tokens)
+
+-- | Stores the parsed selector*s*+proeprties rule.
+addRules :: StyleSheet ss => ss -> ([Selector], ([(Text, [Token])], Text)) -> ss
+addRules self (selector:selectors, val@(props, psuedoel)) = addRules self' (selectors, val)
+ where self' = addRule self $ StyleRule selector props psuedoel
+addRules self ([], _) = self
+
+-- | The properties to set for elements matching the given selector.
+data StyleRule = StyleRule Selector [(Text, [Token])] Text deriving (Show, Eq)
+
+-- | Gathers StyleRules into a list, mainly for testing.
+data TrivialStyleSheet = TrivialStyleSheet [StyleRule] deriving (Show, Eq)
+instance StyleSheet TrivialStyleSheet where
+ addRule (TrivialStyleSheet self) rule = TrivialStyleSheet $ rule:self
+
+-- | In case an indirect caller doesn't actually want to use Haskell Stylist.
+instance StyleSheet () where
+ addRule () _ = ()
+
+--------
+---- Basic parsing
+--------
+-- | Parse a CSS stylesheet
+parse :: StyleSheet s => s -> Text -> s
+parse stylesheet source = parse' stylesheet $ tokenize source
+
+-- | Parse a CSS stylesheet, resolving all URLs to absolute form.
+parseForURL :: StyleSheet s => s -> URI -> Text -> s
+parseForURL stylesheet base source = parse' stylesheet $ rewriteURLs $ tokenize source
+ where
+ rewriteURLs (Url text:toks)
+ | Just url <- parseRelativeReference $ unpack text =
+ Url (pack $ uriToString id (relativeTo url base) "") : rewriteURLs toks
+ | otherwise = Function "url" : RightParen : rewriteURLs toks
+ rewriteURLs (tok:toks) = tok : rewriteURLs toks
+ rewriteURLs [] = []
+
+-- | Parse a tokenized (via `css-syntax`) CSS stylesheet
+parse' :: StyleSheet t => t -> [Token] -> t
+-- 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 $ skipSpace tokens
+parse' stylesheet tokens = parse' (addRules stylesheet rule) tokens'
+ where (rule, tokens') = concatP (,) parseSelectors parseProperties tokens
+
+--------
+---- Property parsing
+--------
+-- | Parse "{key: value; ...}" property values, with a psuedoelement.
+parseProperties :: Parser ([(Text, [Token])], Text)
+parseProperties (LeftCurlyBracket:tokens) = noPsuedoel $ parseProperties' tokens
+parseProperties (Whitespace:tokens) = parseProperties tokens
+parseProperties (Colon:Colon:Ident n:tokens) = ((val, n), tokens')
+ where ((val, _), tokens') = parseProperties tokens
+-- This error recovery is a bit overly conservative, but it's simple.
+parseProperties (_:tokens) = noPsuedoel ([], skipAtRule tokens)
+parseProperties [] = noPsuedoel ([], [])
+
+noPsuedoel :: (x, y) -> ((x, Text), y)
+noPsuedoel (val, tokens) = ((val, ""), tokens)
+
+-- | Parse "key: value;"... property values, as per the HTML "style" property.
+parseProperties' :: Parser [(Text, [Token])]
+parseProperties' (Whitespace:tokens) = parseProperties' tokens
+parseProperties' (Ident name:tokens)
+ | Colon:tokens' <- skipSpace tokens =
+ concatP appendProp scanValue parseProperties' tokens'
+ where appendProp value props = (name, value):props
+parseProperties' (RightCurlyBracket:tokens) = ([], tokens)
+parseProperties' [] = ([], [])
+parseProperties' tokens = parseProperties' (skipValue tokens)
+
+--------
+---- Skipping/Scanning utilities
+--------
+-- | Returns tokens before & after an at-rule value, terminated after a curly-bracketed block or a semicolon.
+scanAtRule :: Parser [Token]
+scanAtRule (Semicolon:tokens) = ([Semicolon], tokens)
+scanAtRule tokens@(LeftCurlyBracket:_) = scanInner tokens $ \rest -> ([], rest)
+
+scanAtRule tokens@(LeftParen:_) = scanInner tokens scanValue
+scanAtRule tokens@(Function _:_) = scanInner tokens scanValue
+scanAtRule tokens@(LeftSquareBracket:_) = scanInner tokens scanValue
+-- To ensure parens are balanced, should already be handled.
+scanAtRule (RightCurlyBracket:tokens) = ([], RightCurlyBracket:tokens)
+scanAtRule (RightParen:tokens) = ([], RightParen:tokens)
+scanAtRule (RightSquareBracket:tokens) = ([], RightSquareBracket:tokens)
+
+scanAtRule tokens = capture scanAtRule tokens
+
+-- | Returns tokens after an at-rule, as per `scanAtRule`.
+skipAtRule :: [Token] -> [Token]
+skipAtRule tokens = snd $ scanAtRule tokens
+
+-- | Returns tokens before & after a semicolon.
+scanValue :: Parser [Token]
+scanValue (Semicolon:tokens) = ([], tokens)
+scanValue (Whitespace:tokens) = scanValue tokens
+
+scanValue tokens@(LeftCurlyBracket:_) = scanInner tokens scanValue
+scanValue tokens@(LeftParen:_) = scanInner tokens scanValue
+scanValue tokens@(Function _:_) = scanInner tokens scanValue
+scanValue tokens@(LeftSquareBracket:_) = 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
+
+-- | Returns tokens after a semicolon.
+skipValue :: [Token] -> [Token]
+skipValue tokens = snd $ scanValue tokens
A stylist-traits/src/Stylist/Parse/Selector.hs => stylist-traits/src/Stylist/Parse/Selector.hs +119 -0
@@ 0,0 1,119 @@
+-- | Parses CSS selectors
+-- See `parseSelectors`
+module Stylist.Parse.Selector(
+ Selector(..), SimpleSelector(..), PropertyTest(..), PropertyFunc(..),
+ parseSelectors
+ ) where
+
+import Data.CSS.Syntax.Tokens
+import Stylist.Parse.Util
+
+import Data.Text.Internal (Text(..))
+
+-- | A CSS "selector" indicating which elements should be effected by CSS.
+data Selector = Element [SimpleSelector] -- ^ Selects a single element.
+ | Child Selector [SimpleSelector] -- ^ Represents "a > b" operator.
+ | Descendant Selector [SimpleSelector] -- ^ Represents "a b" operator.
+ | Adjacent Selector [SimpleSelector] -- ^ Represents "a + b" operator.
+ | Sibling Selector [SimpleSelector] -- ^ Represents "a ~ b" operator.
+ 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 (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.
+data PropertyTest = Exists -- ^ Matches whether an attribute actually exists, e.g. "[title]"
+ | Equals Text -- ^ Matches whether the attribute is exactly equal to the value, e.g. "="
+ | Suffix Text -- ^ Matches whether attribute ends with the given value, e.g. "$="
+ | Prefix Text -- ^ Matches whether attribute starts with the given value, e.g. "^="
+ | Substring Text -- ^ Matches whether the attribute contains the given value, e.g. "*="
+ | Include Text -- ^ Is one of the whitespace-seperated values the one specified? e.g. "~="
+ | Dash Text -- ^ Matches whitespace seperated values, or their "-"-seperated prefixes. e.g. "|="
+ | Callback PropertyFunc -- ^ Calls the given function to test this property.
+ deriving (Show, Eq)
+-- | Caller-specified functions to extend property selection.
+-- Has incorrect Show/Eq implementations so this rare exception doesn't break things.
+data PropertyFunc = PropertyFunc (String -> Bool)
+instance Show PropertyFunc where
+ show _ = "xx"
+instance Eq PropertyFunc where
+ _ == _ = False
+
+-- | Parses a CSS selector.
+parseSelectors :: Parser [Selector]
+parseSelectors tokens = concatP (:) parseCompound parseSelectorsTail $ skipSpace tokens
+parseSelectorsTail :: Parser [Selector]
+parseSelectorsTail (Comma:tokens) = parseSelectors tokens
+parseSelectorsTail tokens = ([], tokens)
+parseCompound :: Parser Selector
+parseCompound tokens = parseCombinators (Element selector) tokens'
+ where (selector, tokens') = parseSelector tokens
+
+parseSelector' :: SimpleSelector -> Parser [SimpleSelector]
+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 Nothing prop test : selector
+parseSelector (Colon:Ident p:ts) = parseSelector' (Psuedoclass p []) ts
+parseSelector (Colon:Function fn:tokens) =
+ concatP appendPseudo scanBlock parseSelector tokens
+ where appendPseudo args selector = Psuedoclass fn args : selector
+parseSelector tokens = ([], tokens)
+
+parseCombinators' :: Selector -> Parser Selector
+parseCombinators' selector tokens = parseCombinators selector' tokens'
+ where (selector', tokens') = parseCombinator selector tokens
+parseCombinators :: Selector -> Parser Selector
+parseCombinators selector (Whitespace:tokens) = parseCombinators' selector tokens
+parseCombinators selector tokens@(Delim _:_) = parseCombinators' selector tokens
+parseCombinators selector tokens = (selector, tokens)
+
+parseCombinator' :: (Selector -> [SimpleSelector] -> Selector)
+ -> Selector -> Parser Selector
+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 :: Parser PropertyTest
+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 :: (Text -> PropertyTest) -> Parser PropertyTest
+parsePropertyVal wrapper (Ident val:RightSquareBracket:tokens) = (wrapper val, tokens)
+parsePropertyVal wrapper (String val:RightSquareBracket:tokens) = (wrapper val, tokens)
+parsePropertyVal _ tokens = (Exists, skipBlock tokens)
R src/Data/CSS/Syntax/StylishUtil.hs => stylist-traits/src/Stylist/Parse/Util.hs +1 -1
@@ 1,5 1,5 @@
-- | Utility parser combinators for parsing CSS stylesheets.
-module Data.CSS.Syntax.StylishUtil(
+module Stylist.Parse.Util(
concatP, capture, skipSpace,
scanBlock, skipBlock, scanInner,
Parser
A stylist-traits/src/Stylist/Tree.hs => stylist-traits/src/Stylist/Tree.hs +60 -0
@@ 0,0 1,60 @@
+-- | Abstracts away tree traversals.
+-- Mostly used by callers including (soon) XML Conduit Stylist,
+-- but also used internally for generating counter text.
+module Stylist.Tree(StyleTree(..), treeOrder, treeOrder',
+ Path, treeMap, treeFlatten, preorder, preorder', postorder) where
+
+data StyleTree p = StyleTree {
+ style :: p,
+ children :: [StyleTree p]
+}
+
+type Path = [Integer]
+treeOrder :: (c -> c -> Path -> p -> (c, p')) ->
+ c -> StyleTree p -> StyleTree p'
+treeOrder cb ctxt tree = StyleTree
+ (snd $ cb ctxt ctxt [] $ style tree)
+ (snd $ treeOrder' cb ctxt ctxt [0] $ children tree)
+treeOrder' :: (c -> c -> Path -> p -> (c, p')) ->
+ c -> c -> Path -> [StyleTree p] -> (c, [StyleTree p'])
+treeOrder' cb prevContext context (num:path) (node:nodes) = (tailContext, StyleTree node' children' : nodes')
+ where
+ (selfContext, node') = cb prevContext context (num:path) $ style node
+ (childContext, children') = treeOrder' cb selfContext selfContext (0:num:path) $ children node
+ (tailContext, nodes') = treeOrder' cb selfContext childContext (num + 1:path) nodes
+treeOrder' _ _ context _ [] = (context, [])
+treeOrder' _ _ _ [] _ = error "Invalid path during tree traversal!"
+
+treeMap :: (p -> p') -> StyleTree p -> StyleTree p'
+treeMap cb = treeOrder (\_ _ _ p -> ((), cb p)) ()
+
+treeFlatten :: StyleTree p -> [p]
+treeFlatten = treeFlatten' . children
+treeFlatten' :: [StyleTree p] -> [p]
+treeFlatten' (StyleTree p []:ps) = p : treeFlatten' ps
+treeFlatten' (StyleTree _ childs:sibs) = treeFlatten' childs ++ treeFlatten' sibs
+treeFlatten' [] = []
+
+treeFlattenAll :: StyleTree p -> [p]
+treeFlattenAll = treeFlatten' . children
+treeFlattenAll' :: [StyleTree p] -> [p]
+treeFlattenAll' (StyleTree p []:ps) = p : treeFlatten' ps
+treeFlattenAll' (StyleTree p childs:sibs) = p : treeFlatten' childs ++ treeFlatten' sibs
+treeFlattenAll' [] = []
+
+treeFind :: StyleTree p -> (p -> Bool) -> [p]
+treeFind p test = filter test $ treeFlattenAll p
+
+preorder :: (Maybe b -> Maybe b -> a -> b) -> StyleTree a -> StyleTree b
+preorder cb self = head $ preorder' cb Nothing Nothing [self]
+preorder' :: (Maybe b -> Maybe b -> a -> b) ->
+ Maybe b -> Maybe b -> [StyleTree a] -> [StyleTree b]
+preorder' cb parent previous (self:sibs) = let self' = cb parent previous $ style self
+ in StyleTree self' (preorder' cb (Just self') Nothing $ children self) :
+ preorder' cb parent (Just self') sibs
+preorder' _ _ _ [] = []
+
+postorder :: (a -> [b] -> [b]) -> StyleTree a -> [StyleTree b]
+postorder cb (StyleTree self childs) =
+ [StyleTree self' children' | self' <- cb self $ Prelude.map style children']
+ where children' = concat $ Prelude.map (postorder cb) childs
A stylist-traits/stylist-traits.cabal => stylist-traits/stylist-traits.cabal +70 -0
@@ 0,0 1,70 @@
+-- Initial stylist-traits.cabal generated by cabal init. For further
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+-- The name of the package.
+name: stylist-traits
+
+-- The package version. See the Haskell package versioning policy (PVP)
+-- for standards guiding when and how versions should be incremented.
+-- https://wiki.haskell.org/Package_versioning_policy
+-- PVP summary: +-+------- breaking API changes
+-- | | +----- non-breaking API additions
+-- | | | +--- code changes with no API change
+version: 0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis: Traits, datatypes, & parsers for Haskell Stylist
+
+-- A longer description of the package.
+description: Decoupling layer for Haskell Stylist, so other modules don't have to pull in the full CSS engine in order to integrate it.
+
+-- URL for the project homepage or repository.
+homepage: https://rhapsode.adrian.geek.nz/
+
+-- The license under which the package is released.
+license: GPL-3
+
+-- The file containing the license text.
+license-file: LICENSE
+
+-- The package author(s).
+author: Adrian Cochrane
+
+-- An email address to which users can send suggestions, bug reports, and
+-- patches.
+maintainer: alcinnz@lavabit.com
+
+-- A copyright notice.
+-- copyright:
+
+category: Web
+
+build-type: Simple
+
+-- Extra files to be distributed with the package, such as examples or a
+-- README.
+extra-source-files: CHANGELOG.md
+
+-- Constraint on the version of Cabal needed to build this package.
+cabal-version: >=1.10
+
+
+library
+ -- Modules exported by the library.
+ exposed-modules: Stylist, Stylist.Tree, Stylist.Parse, Stylist.Parse.Selector
+
+ -- Modules included in this library but not exported.
+ other-modules: Stylist.Parse.Util
+
+ -- LANGUAGE extensions used by modules in this package.
+ -- other-extensions:
+
+ -- Other library packages from which modules are imported.
+ build-depends: base >=4.12 && <4.13, css-syntax >=0.1 && <0.2, text, network-uri >= 2.6 && <2.7
+
+ -- Directories containing source files.
+ hs-source-dirs: src
+
+ -- Base language which the package is written in.
+ default-language: Haskell2010
+
M stylist.cabal => stylist.cabal +13 -7
@@ 10,7 10,7 @@ name: stylist
-- PVP summary: +-+------- breaking API changes
-- | | +----- non-breaking API additions
-- | | | +--- code changes with no API change
-version: 2.4.0.2
+version: 2.5.0.0
-- A short (one-line) description of the package.
synopsis: Apply CSS styles to a document tree.
@@ 59,10 59,10 @@ library
Data.CSS.Preprocessor.Assets, Data.CSS.Preprocessor.Text, Data.CSS.Preprocessor.PsuedoClasses
-- Modules included in this library but not exported.
- other-modules: Data.CSS.Syntax.StylishUtil,
- Data.CSS.Style.Importance, Data.CSS.Style.Common, Data.CSS.Style.Cascade,
+ other-modules: Data.CSS.Style.Importance, Data.CSS.Style.Common, Data.CSS.Style.Cascade,
Data.CSS.Style.Selector.Index, Data.CSS.Style.Selector.Interpret,
- Data.CSS.Style.Selector.Specificity, Data.CSS.Style.Selector.LowerWhere
+ Data.CSS.Style.Selector.Specificity, Data.CSS.Style.Selector.LowerWhere,
+ Data.CSS.Syntax.AtLayer
-- LANGUAGE extensions used by modules in this package.
-- other-extensions:
@@ 71,7 71,7 @@ library
build-depends: base >=4.9 && <5, css-syntax >=0.1 && <0.2, text,
unordered-containers >= 0.2 && <0.3, hashable,
network-uri >= 2.6 && <2.7, async >= 2.1 && <2.3,
- regex-tdfa >= 1.3
+ regex-tdfa >= 1.3, stylist-traits >= 0.1 && < 0.2
-- Directories containing source files.
hs-source-dirs: src
@@ 82,6 82,13 @@ library
ghc-options: -Wall
test-suite test-stylist
+ other-modules:
+ Data.CSS.Preprocessor.Conditions, Data.CSS.Preprocessor.Conditions.Expr, Data.CSS.Preprocessor.Text,
+ Data.CSS.Style.Cascade, Data.CSS.Style.Common, Data.CSS.Style.Importance,
+ Data.CSS.Style.Selector.Index, Data.CSS.Style.Selector.Interpret,
+ Data.CSS.Style.Selector.LowerWhere, Data.CSS.Style.Selector.Specificity,
+ Data.CSS.StyleTree, Data.CSS.Syntax.AtLayer
+
hs-source-dirs: src test
default-language: Haskell2010
type: exitcode-stdio-1.0
@@ 91,5 98,4 @@ test-suite test-stylist
unordered-containers >= 0.2 && <0.3, hashable,
network-uri >= 2.6 && <2.7, async >= 2.1 && <2.3,
regex-tdfa >= 1.3, hspec, QuickCheck,
- scientific >= 0.3 && <1.0, regex-tdfa >= 1.3
- ghc-options: -Wall
+ scientific >= 0.3 && <1.0, regex-tdfa >= 1.3, stylist-traits >= 0.1 && < 0.2
M test/Test.hs => test/Test.hs +23 -8
@@ 3,6 3,7 @@ module Main where
import Test.Hspec
import Data.HashMap.Strict
+import qualified Data.HashMap.Lazy as L
import Data.Maybe (fromJust)
import Network.URI
import Data.Scientific (toRealFloat)
@@ 11,6 12,8 @@ import Data.CSS.Syntax.Tokens
import Data.CSS.Syntax.StyleSheet (parse, StyleSheet(..), TrivialStyleSheet(..), scanAtRule, scanValue)
import Data.CSS.Syntax.Selector
+import Data.CSS.Syntax.AtLayer
+
import Data.CSS.Style.Common
import Data.CSS.Style.Selector.Index
import Data.CSS.Style.Selector.Interpret
@@ 98,7 101,7 @@ spec = do
]
describe "Style Index" $ do
it "Retrieves appropriate styles" $ do
- let index = addStyleRule styleIndex 0 $ styleRule' sampleRule
+ let index = addStyleRule styleIndex [0] $ styleRule' sampleRule
let element = ElementNode {
name = "a",
namespace = "",
@@ 121,17 124,17 @@ spec = do
rulesForElement index element2 `shouldBe` []
let rule1 = StyleRule (Element [Class "external"]) [("color", [Ident "green"])] ""
- let index1 = addStyleRule styleIndex 0 $ styleRule' rule1
+ let index1 = addStyleRule styleIndex [0] $ styleRule' rule1
rulesForElement index1 element `shouldBe` [rule1]
rulesForElement index1 element2 `shouldBe` []
let rule2 = StyleRule (Element [Id "mysite"]) [("color", [Ident "green"])] ""
- let index2 = addStyleRule styleIndex 0 $ styleRule' rule2
+ let index2 = addStyleRule styleIndex [0] $ styleRule' rule2
rulesForElement index2 element `shouldBe` [rule2]
rulesForElement index2 element2 `shouldBe` []
let rule3 = StyleRule (Element [Property Nothing "href" $ Prefix "https://"]) [("color", [Ident "green"])] ""
- let index3 = addStyleRule styleIndex 0 $ styleRule' rule3
+ let index3 = addStyleRule styleIndex [0] $ styleRule' rule3
rulesForElement index3 element `shouldBe` [rule3]
rulesForElement index3 element2 `shouldBe` []
describe "Selector Compiler" $ do
@@ 274,8 277,8 @@ spec = do
previous = Nothing,
attributes = [Attribute "class" "" "link"]
}
- let rules = parse (queryable {priority = 1}) "a {color: green}"
- let rules2 = parse (rules {priority = 2}) "a {color: red}" :: QueryableStyleSheet (VarParser TrivialPropertyParser)
+ let rules = parse (queryable {priority = [1]}) "a {color: green}"
+ let rules2 = parse (rules {priority = [2]}) "a {color: red}" :: QueryableStyleSheet (VarParser TrivialPropertyParser)
let VarParser _ (TrivialPropertyParser style) = cascade rules2 el [] temp::(VarParser TrivialPropertyParser)
style ! "color" `shouldBe` [Ident "green"]
@@ 286,8 289,8 @@ spec = do
previous = Nothing,
attributes = [Attribute "class" "" "link"]
}
- let rules' = parse (queryable {priority = 1}) "a {color: red}"
- let rules2' = parse (rules' {priority = 2}) "a {color: green !important}" :: QueryableStyleSheet (VarParser TrivialPropertyParser)
+ let rules' = parse (queryable {priority = [1]}) "a {color: red}"
+ let rules2' = parse (rules' {priority = [2]}) "a {color: green !important}" :: QueryableStyleSheet (VarParser TrivialPropertyParser)
let VarParser _ (TrivialPropertyParser style') = cascade rules2' el' [] temp::(VarParser TrivialPropertyParser)
style' ! "color" `shouldBe` [Ident "green"]
it "respects overrides" $ do
@@ 504,6 507,18 @@ spec = do
let textStyle4 = fromJust $ longhand temp textStyle1 "counter-increment" [Ident "-rhaps-ol"]
style (Txt.resolve $ StyleTree textStyle4 []) `shouldBe` TrivialPropertyParser (fromList [("content", [String "1"])])
+ describe "@layer" $ do
+ it "Deduplicates names" $ do
+ let init = Tree L.empty
+ let tree2 = registerLayer ["LeagueOfGentlemenAdventurers", "The Stranger"] init
+ let tree3 = registerLayer ["JusticeUnion", "TomTomorrow"] tree2
+ let tree4 = registerLayer ["HomeTeam", "DocRocket"] tree3
+ let tree5 = registerLayer ["JusticeUnion", "TheOgre"] tree4
+
+ layerPath ["JusticeUnion", "TheOgre"] tree5 `shouldBe` [2, 2]
+ layerPath ["HomeTeam"] tree5 `shouldBe` [3]
+ layerPath ["LeagueOfGentlemenAdventurers"] tree5 `shouldBe` [1]
+ uniqueName ["HomeTeam"] tree5 `shouldBe` ["HomeTeam", "1"]
styleIndex :: StyleIndex
styleIndex = new
M xml-conduit-stylist/src/Data/HTML2CSS.hs => xml-conduit-stylist/src/Data/HTML2CSS.hs +33 -64
@@ 1,58 1,45 @@
{-# LANGUAGE OverloadedStrings #-}
-- | Bindings from `xml-conduit` to `haskell-stylist`.
module Data.HTML2CSS(
- html2css, cssPriorityAgent, cssPriorityUser, cssPriorityAuthor, -- parsing
- preorder, el2styletree, els2stylist, el2stylist, stylize, stylize', stylizeEl, -- application
- inlinePseudos, stylizeNoPseudos, stylizeElNoPseudos
+ html2css, -- parsing
+ el2styletree, els2stylist, el2stylist -- application
) where
import qualified Data.List as L
import qualified Data.Map as M
-import qualified Data.HashMap.Strict as HM
import qualified Data.Text as Txt
import Data.Maybe
import qualified Text.XML as XML
-import Data.CSS.Syntax.StyleSheet
-import Data.CSS.Style
-import Data.CSS.StyleTree
+import Stylist.Parse
+import Stylist
+import Stylist.Tree
import Data.CSS.Syntax.Tokens
-import Data.CSS.Preprocessor.Conditions
-import qualified Data.CSS.Preprocessor.Conditions.Expr as Query
import Network.URI
----- Constants
--- | Set the priority for a CSS stylesheet being parsed.
-cssPriorityAgent, cssPriorityUser, cssPriorityAuthor :: StyleSheet s => s -> s
-cssPriorityAgent = setPriority 1
-cssPriorityUser = setPriority 2
-cssPriorityAuthor = setPriority 3
-
---- Parsing
-- | Converts a parsed XML or HTML file to a `ConditionalStyles` `StyleSheet`.
-html2css :: PropertyParser p => XML.Document -> URI -> ConditionalStyles p
-html2css xml url = testIsStyled $ ConditionalStyles {
- hostURL = url,
- mediaDocument = "document",
- isUnstyled = False,
- rules = Priority 3 : html2css' (XML.documentRoot xml) (conditionalStyles url "document"),
- propertyParser = temp
-}
-
-html2css' :: PropertyParser p => XML.Element -> ConditionalStyles p -> [ConditionalRule p]
-html2css' (XML.Element (XML.Name "style" _ _) attrs children) base =
- [Internal (parseMediaQuery attrs) (parseForURL base (hostURL base) $ strContent children)]
-html2css' (XML.Element (XML.Name "link" _ _) attrs _) base
- | Just link <- "href" `M.lookup` attrs,
+html2css :: StyleSheet s => XML.Document -> URI -> s -> s
+html2css xml url self = html2css' (XML.documentRoot xml) url self
+
+html2css' :: StyleSheet s => XML.Element -> URI -> s -> s
+html2css' (XML.Element (XML.Name "style" _ _) attrs children) url self
+ | M.lookup "type" attrs `notElem` [Nothing, Just "text/css"] = self -- Unsupported stylesheet.
+ | Just media <- "media" `M.lookup` attrs =
+ fst $ addAtRule self "media" (tokenize media ++
+ LeftCurlyBracket : tokContent url children ++ [RightCurlyBracket])
+ | otherwise = parseForURL self url $ strContent children
+html2css' (XML.Element (XML.Name "link" _ _) attrs _) baseURL self
+ | M.lookup "type" attrs `elem` [Nothing, Just "text/css"],
Just "stylesheet" <- "rel" `M.lookup` attrs,
- Just uri <- parseURIReference $ Txt.unpack link =
- [External (parseMediaQuery attrs) (relativeTo uri $ hostURL base)]
-html2css' (XML.Element _ _ children) base = concat [html2css' el base | XML.NodeElement el <- children]
-
-parseMediaQuery :: M.Map XML.Name Txt.Text -> Query.Expr
-parseMediaQuery attrs
- | Just text <- "media" `M.lookup` attrs = Query.parse' (tokenize text) []
- | otherwise = []
+ Just link <- "href" `M.lookup` attrs,
+ Just url <- parseURIReference $ Txt.unpack link =
+ fst $ addAtRule self "import" (
+ Url (Txt.pack $ uriToString' $ relativeTo url baseURL) :
+ fromMaybe [] (tokenize <$> M.lookup "media" attrs) ++
+ [Semicolon])
+html2css' (XML.Element _ _ children) url self =
+ L.foldl' (\s el -> html2css' el url s) self [el | XML.NodeElement el <- children]
strContent :: [XML.Node] -> Txt.Text
@@ 64,6 51,14 @@ strContent (XML.NodeElement (XML.Element _ _ children):rest) =
strContent (_:rest) = strContent rest
strContent [] = ""
+tokContent :: URI -> [XML.Node] -> [Token]
+tokContent baseURL = map absolutizeUrl . tokenize . strContent
+ where
+ absolutizeUrl (Url link) | Just url <- parseURIReference $ Txt.unpack link =
+ Url $ Txt.pack $ uriToString' $ relativeTo url baseURL
+
+uriToString' uri = uriToString id uri ""
+
---- Styling
el2styletree el = StyleTree (Left el) $ mapMaybe node2styletree $ XML.elementNodes el
@@ 90,29 85,3 @@ els2stylist' parent previous (Right attrs) = ElementNode {
} where style = concat [[prop, ": ", serialize v, "; "] | (prop, v) <- attrs]
el2stylist = els2stylist . el2styletree
-
-stylize :: PropertyParser s => QueryableStyleSheet s -> StyleTree Element -> StyleTree [(Txt.Text, s)]
-stylize = preorder . stylize'
-stylize' :: PropertyParser s => QueryableStyleSheet s -> Maybe [(Txt.Text, s)] -> Maybe [(Txt.Text, s)] ->
- Element -> [(Txt.Text, s)]
-stylize' stylesheet parent _ el = ("", base) : [
- (k, cascade' v [] base) | (k, v) <- HM.toList $ queryRules stylesheet el
- ] where
- base = cascade stylesheet el overrides $ fromMaybe temp $ lookup "" =<< parent
- overrides = concat [fst $ parseProperties' $ tokenize $ Txt.pack val
- | Attribute "style" _ val <- attributes el]
-stylizeEl stylesheet = stylize stylesheet . el2stylist
-
-inlinePseudos :: PropertyParser s => StyleTree [(Txt.Text, VarParser s)] -> StyleTree s
-inlinePseudos (StyleTree self childs) = StyleTree {
- style = fromMaybe temp $ innerParser <$> lookup "" self,
- children = pseudo "before" ++ map inlinePseudos childs ++ pseudo "after"
- } where
- pseudo n
- | Just style <- innerParser <$> lookup n self,
- Just style' <- longhand style style "::" [Ident n] = [StyleTree style' []]
- | Just style <- innerParser <$> lookup n self = [StyleTree style []]
- | otherwise = []
-
-stylizeNoPseudos css = inlinePseudos . stylize css
-stylizeElNoPseudos css = inlinePseudos . stylizeEl css
M xml-conduit-stylist/xml-conduit-stylist.cabal => xml-conduit-stylist/xml-conduit-stylist.cabal +2 -2
@@ 10,7 10,7 @@ name: xml-conduit-stylist
-- PVP summary: +-+------- breaking API changes
-- | | +----- non-breaking API additions
-- | | | +--- code changes with no API change
-version: 2.3.0.0
+version: 3.0.0.0
-- A short (one-line) description of the package.
synopsis: Bridge between xml-conduit/html-conduit and stylist
@@ 61,7 61,7 @@ library
-- Other library packages from which modules are imported.
build-depends: base >=4.9 && <5,
- stylist >=2.4 && <3, css-syntax, unordered-containers,
+ stylist-traits >=0.1 && <2, css-syntax,
xml-conduit >=1.8 && < 1.9, text, containers,
network-uri