module Hasmin.Parser.Value (
valuesFor
, valuesFallback
, value
, valuesInParens
, stringOrUrl
, percentage
, url
, stringtype
, textualvalue
, stringvalue
, shadowList
, timingFunction
, repeatStyle
, position
, color
, number
, fontStyle
) where
import Control.Applicative ((<|>), many, liftA3)
import Control.Arrow (first, (&&&))
import Control.Monad (mzero)
import Data.Functor (($>))
import Data.Attoparsec.Text (asciiCI, char, count, option, Parser, satisfy,
skipSpace, string)
import Data.Map.Strict (Map)
import Data.Monoid ((<>))
import Data.Maybe (fromMaybe, isNothing)
import Data.Text (Text)
import Data.Word (Word8)
import Data.Char (isAscii)
import Text.Parser.Permutation ((<|?>), (<$$>), (<$?>), (<||>), permute)
import Numeric (readSigned, readFloat)
import qualified Data.Set as Set
import qualified Data.Attoparsec.Text as A
import qualified Data.Char as C
import qualified Data.Map.Strict as Map
import qualified Data.List as L
import qualified Data.Text as T
import Hasmin.Parser.Utils
import Hasmin.Types.BgSize
import Hasmin.Class
import Hasmin.Types.Color
import Hasmin.Types.Dimension
import Hasmin.Types.FilterFunction
import Hasmin.Types.Gradient
import Hasmin.Types.Numeric
import Hasmin.Types.PercentageLength
import Hasmin.Types.Position
import Hasmin.Types.RepeatStyle
import Hasmin.Types.Shadow
import Hasmin.Types.String
import Hasmin.Types.TimingFunction
import Hasmin.Types.TransformFunction
import Hasmin.Types.Value
valuesFor :: Text -> Parser Values
valuesFor propName =
case Map.lookup (T.toLower propName) propertyValueParsersMap of
Just x -> x <* skipComments
Nothing -> mzero
number :: Parser Number
number = Number <$> rational
rgb :: Parser Color
rgb = functionParser (rgbInt <|> rgbPer)
where rgbInt = mkRGBInt <$> word8 <* comma <*> word8 <* comma <*> word8
rgbPer = mkRGBPer <$> percentage <* comma
<*> percentage <* comma <*> percentage
rgba :: Parser Color
rgba = functionParser (rgbaInt <|> rgbaPer)
where rgbaInt = mkRGBAInt <$> word8 <* comma <*> word8 <* comma
<*> word8 <* comma <*> alphavalue
rgbaPer = mkRGBAPer <$> percentage <* comma <*> percentage <* comma
<*> percentage <* comma <*> alphavalue
hsl :: Parser Color
hsl = functionParser p
where p = mkHSL <$> int <* comma <*> percentage <* comma <*> percentage
hsla :: Parser Color
hsla = functionParser p
where p = mkHSLA <$> int <* comma <*> percentage <* comma
<*> percentage <* comma <*> alphavalue
alphavalue :: Parser Alphavalue
alphavalue = mkAlphavalue <$> rational
hexvalue :: Parser Value
hexvalue = ColorV <$> hex
hex :: Parser Color
hex = do
_ <- char '#'
a <- hexadecimal
b <- hexadecimal
c <- hexadecimal
x <- optional hexadecimal
case x of
Nothing -> pure $ mkHex3 a b c
Just d -> do y <- optional hexadecimal
case y of
Nothing -> pure $ mkHex4 a b c d
Just e -> do f <- hexadecimal
z <- optional hexadecimal
case z of
Nothing -> pure $ mkHex6 [a,b] [c,d] [e,f]
Just g -> do h <- hexadecimal
pure $ mkHex8 [a,b] [c,d] [e,f] [g,h]
where optional w = option Nothing (Just <$> w)
hexadecimal = satisfy C.isHexDigit
numericalConstructorsMap :: Map Text (Number -> Value)
numericalConstructorsMap = Map.fromList $ fmap (first T.toCaseFold) l
where durationFunc u v = DurationV (Duration v u)
frequencyFunc u v = FrequencyV (Frequency v u)
resolutionFunc u v = ResolutionV (Resolution v u)
l = [("s", durationFunc S)
,("ms", durationFunc Ms)
,("hz", frequencyFunc Hz)
,("khz", frequencyFunc Khz)
,("dpi", resolutionFunc Dpi)
,("dpcm", resolutionFunc Dpcm)
,("dppx", resolutionFunc Dppx)
,("%", \x -> PercentageV (Percentage $ toRational x))
] ++ (fmap . fmap) (LengthV .) distanceConstructorsList
++ (fmap . fmap) (AngleV .) angleConstructorsList
numericalvalue :: Parser Value
numericalvalue = do
n <- number
rest <- opt (string "%" <|> A.takeWhile1 C.isAlpha)
if T.null rest
then pure $ NumberV n
else case Map.lookup (T.toCaseFold rest) numericalConstructorsMap of
Just f -> pure $ f n
Nothing -> mzero
dimensionParser :: Map Text (Number -> a) -> a -> Parser a
dimensionParser m unitlessValue = do
n <- number
u <- opt (A.takeWhile1 C.isAlpha)
if T.null u
then if n == 0
then pure unitlessValue
else mzero
else case Map.lookup (T.toCaseFold u) m of
Just f -> pure $ f n
Nothing -> mzero
distance :: Parser Length
distance = dimensionParser distanceConstructorsMap NullLength
where distanceConstructorsMap = Map.fromList distanceConstructorsList
angle :: Parser Angle
angle = dimensionParser angleConstructorsMap NullAngle
where angleConstructorsMap = Map.fromList angleConstructorsList
duration :: Parser Duration
duration = do
n <- number
u <- opt (A.takeWhile1 C.isAlpha)
if T.null u
then mzero
else case Map.lookup (T.toCaseFold u) durationConstructorsMap of
Just f -> pure $ f n
Nothing -> mzero
where durationConstructorsMap = Map.fromList $
fmap (toText &&& flip Duration) [S, Ms]
angleConstructorsList :: [(Text, Number -> Angle)]
angleConstructorsList = fmap (toText &&& flip Angle)
[Deg, Grad, Rad, Turn]
distanceConstructorsList :: [(Text, Number -> Length)]
distanceConstructorsList = fmap (toText &&& flip Length)
[EM, EX, CH, VH, VW, VMIN, VMAX, REM, Q, CM, MM, IN, PC, PT, PX]
percentage :: Parser Percentage
percentage = Percentage <$> rational <* char '%'
int' :: Parser String
int' = do
sign <- option '+' (char '-' <|> char '+')
d <- digits
case sign of
'+' -> pure d
'-' -> pure (sign:d)
_ -> error "int': parsed a number starting with other than [+|-]"
int :: Parser Int
int = read <$> int'
word8 :: Parser Word8
word8 = read <$> digits
rational :: Parser Rational
rational = do
sign <- option [] (wrapMinus <$> (char '-' <|> char '+'))
dgts <- ((++) <$> digits <*> option "" fractionalPart)
<|> ("0"++) <$> fractionalPart
e <- option [] expo
pure . fst . head $ readSigned readFloat (sign ++ dgts ++ e)
where fractionalPart = (:) <$> char '.' <*> digits
expo = (:) <$> satisfy (\c -> c == 'e' || c == 'E') <*> int'
wrapMinus x = [x | x == '-']
fontStyle :: Parser Value
fontStyle = do
k <- ident
if Set.member k keywords
then pure $ mkOther k
else mzero
where keywords = Set.fromList ["normal", "italic", "oblique"]
fontSize :: Parser Value
fontSize = fontSizeKeyword
<|> (LengthV <$> distance)
<|> (PercentageV <$> percentage)
where fontSizeKeyword = do
v1 <- ident
if Set.member v1 keywords
then pure $ mkOther v1
else mzero
keywords = Set.fromList ["medium", "xx-small", "x-small", "small"
,"large", "x-large", "xx-large", "smaller"
,"larger"]
positionvalue :: Parser Value
positionvalue = PositionV <$> position
position :: Parser Position
position = pos4 <|> pos2 <|> pos1
pos1 :: Parser Position
pos1 = (asciiCI "left" $> f (Just PosLeft))
<|> (asciiCI "center" $> f (Just PosCenter))
<|> (asciiCI "right" $> f (Just PosRight))
<|> (asciiCI "top" $> f (Just PosTop))
<|> (asciiCI "bottom" $> f (Just PosBottom))
<|> ((\a -> Position Nothing a Nothing Nothing) <$> (Just <$> percentageLength))
where f x = Position x Nothing Nothing Nothing
pos2 :: Parser Position
pos2 = firstx <|> firsty
where firstx = do
a <- (asciiCI "left" $> Position (Just PosLeft) Nothing)
<|> (asciiCI "center" $> Position (Just PosCenter) Nothing)
<|> (asciiCI "right" $> Position (Just PosRight) Nothing)
<|> ((Position Nothing . Just) <$> percentageLength)
skipComments *> ((asciiCI "top" $> a (Just PosTop) Nothing)
<|> (asciiCI "center" $> a (Just PosCenter) Nothing)
<|> (asciiCI "bottom" $> a (Just PosBottom) Nothing)
<|> ((a Nothing . Just) <$> percentageLength))
firsty = do
a <- (asciiCI "top" $> Position (Just PosTop) Nothing)
<|> (asciiCI "center" $> Position (Just PosCenter) Nothing)
<|> (asciiCI "bottom" $> Position (Just PosBottom) Nothing)
<|> ((Position Nothing . Just) <$> percentageLength)
skipComments *> ((asciiCI "left" $> a (Just PosLeft) Nothing)
<|> (asciiCI "center" $> a (Just PosCenter) Nothing)
<|> (asciiCI "right" $> a (Just PosRight) Nothing)
<|> ((a Nothing . Just) <$> percentageLength))
pos4 :: Parser Position
pos4 = firstx <|> firsty
where posTop = asciiCI "top" $> Position (Just PosTop)
posRight = asciiCI "right" $> Position (Just PosRight)
posBottom = asciiCI "bottom" $> Position (Just PosBottom)
posLeft = asciiCI "left" $> Position (Just PosLeft)
firstx = do
x <- (asciiCI "center" $> Position (Just PosCenter) Nothing)
<|> ((posLeft <|> posRight) <*> (skipComments *> option Nothing (Just <$> percentageLength)))
_ <- skipComments
(asciiCI "center" $> x (Just PosCenter) Nothing)
<|> (((asciiCI "top" $> x (Just PosTop)) <|> (asciiCI "bottom" $> x (Just PosBottom)))
<*> (skipComments *> option Nothing (Just <$> percentageLength)))
firsty = do
x <- (asciiCI "center" $> Position (Just PosCenter) Nothing)
<|> ((posTop <|> posBottom) <*> (skipComments *> option Nothing (Just <$> percentageLength)))
_ <- skipComments
(asciiCI "center" $> x (Just PosCenter) Nothing)
<|> (((asciiCI "left" $> x (Just PosLeft)) <|> (asciiCI "right" $> x (Just PosRight)))
<*> (skipComments *> option Nothing (Just <$> percentageLength)))
bgSize :: Parser BgSize
bgSize = twovaluesyntax <|> contain <|> cover
where cover = asciiCI "cover" $> Cover
contain = asciiCI "contain" $> Contain
twovaluesyntax = do
x <- bgsizeValue <* skipComments
(BgSize2 x <$> bgsizeValue) <|> pure (BgSize1 x)
bgsizeValue = (Left <$> percentageLength) <|> (Right <$> auto)
bgAttachment :: Parser TextV
bgAttachment = matchKeywords ["scroll", "fixed", "local"]
box :: Parser TextV
box = matchKeywords ["border-box", "padding-box", "content-box"]
background :: Parser Values
background = do
xs <- many (bgLayer <* char ',' <* skipComments)
x <- finalBgLayer
pure $ if null xs
then Values x []
else Values (head xs) (fmap (Comma,) $ tail xs ++ [x])
finalBgLayer :: Parser Value
finalBgLayer = do
layer <- permute (mkFinalBgLayer <$?> (Nothing, Just <$> image <* skipComments)
<|?> (Nothing, Just <$> positionAndBgSize <* skipComments)
<|?> (Nothing, Just <$> repeatStyle <* skipComments)
<|?> (Nothing, Just <$> bgAttachment <* skipComments)
<|?> (Nothing, Just <$> box <* skipComments)
<|?> (Nothing, Just <$> box <* skipComments)
<|?> (Nothing, Just <$> color <* skipComments))
if finalBgLayerIsEmpty layer
then mzero
else pure layer
where finalBgLayerIsEmpty (FinalBgLayer Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing) = True
finalBgLayerIsEmpty _ = False
mkFinalBgLayer a Nothing c d e f g = FinalBgLayer a Nothing Nothing c d f e g
mkFinalBgLayer a (Just (p,s)) c d e f g = FinalBgLayer a (Just p) s c d f e g
bgLayer :: Parser Value
bgLayer = do
layer <- permute (mkBgLayer <$?> (Nothing, Just <$> image <* skipComments)
<|?> (Nothing, Just <$> positionAndBgSize <* skipComments)
<|?> (Nothing, Just <$> repeatStyle <* skipComments)
<|?> (Nothing, Just <$> bgAttachment <* skipComments)
<|?> (Nothing, Just <$> box2 <* skipComments))
if bgLayerIsEmpty layer
then mzero
else pure layer
where bgLayerIsEmpty (BgLayer Nothing Nothing Nothing Nothing Nothing Nothing Nothing) = True
bgLayerIsEmpty _ = False
mkBgLayer a Nothing c d Nothing = BgLayer a Nothing Nothing c d Nothing Nothing
mkBgLayer a (Just (p,s)) c d Nothing = BgLayer a (Just p) s c d Nothing Nothing
mkBgLayer a Nothing c d (Just (i,j)) = BgLayer a Nothing Nothing c d (Just i) j
mkBgLayer a (Just (p,s)) c d (Just (i,j)) = BgLayer a (Just p) s c d (Just i) j
box2 :: Parser (TextV, Maybe TextV)
box2 = do
x <- box <* skipComments
y <- option Nothing (Just <$> box)
pure (x,y)
positionAndBgSize :: Parser (Position, Maybe BgSize)
positionAndBgSize = do
x <- position <* skipComments
y <- option Nothing (Just <$> (char '/' *> skipComments *> bgSize))
pure (x,y)
matchKeywords :: [Text] -> Parser TextV
matchKeywords listOfKeywords = do
i <- ident
if T.toCaseFold i `elem` Set.fromList listOfKeywords
then pure $ TextV i
else mzero
image :: Parser Value
image = do
i <- ident
let lowercased = T.toLower i
if lowercased == "none"
then pure $ mkOther "none"
else do _ <- char '('
if Set.member lowercased possibilities
then fromMaybe mzero (Map.lookup lowercased functionsMap)
else mzero
where possibilities = Set.fromList $ map T.toCaseFold
["url", "element", "linear-gradient", "radial-gradient"]
transition :: Parser Values
transition = parseCommaSeparated singleTransition
singleTransition :: Parser Value
singleTransition = do
st <- permute (mkSingleTransition <$?> (Nothing, Just <$> singleTransitionProperty <* skipComments)
<|?> (Nothing, Just <$> duration <* skipComments)
<|?> (Nothing, Just <$> timingFunction <* skipComments)
<|?> (Nothing, Just <$> duration <* skipComments))
if singleTransitionIsEmpty st
then mzero
else pure st
where singleTransitionIsEmpty (SingleTransition Nothing Nothing Nothing Nothing) = True
singleTransitionIsEmpty _ = False
mkSingleTransition a b c d = SingleTransition a d c b
singleTransitionProperty = do
i <- ident
let lowercased = T.toLower i
if Set.member lowercased excludedKeywords
then mzero
else pure $ TextV i
excludedKeywords = Set.fromList ["initial", "inherit", "unset", "default", "none"]
timingFunction :: Parser TimingFunction
timingFunction = do
i <- ident
let lowercased = T.toLower i
case Map.lookup lowercased timingFunctionKeywords of
Just x -> x
Nothing -> char '(' *> (if lowercased == "steps"
then steps
else if lowercased == "cubic-bezier"
then cubicbezier
else mzero)
where timingFunctionKeywords = Map.fromList [("ease", pure Ease)
,("ease-in", pure EaseIn)
,("ease-in-out", pure EaseInOut)
,("ease-out", pure EaseOut)
,("linear", pure Linear)
,("step-end", pure StepEnd)
,("step-start", pure StepStart)]
backgroundSize :: Parser Values
backgroundSize = parseCommaSeparated (BgSizeV <$> bgSize)
auto :: Parser Auto
auto = asciiCI "auto" $> Auto
propertyValueParsersMap :: Map Text (Parser Values)
propertyValueParsersMap = Map.fromList
[
("display", singleValue textualvalue)
,("font", singleValue font)
,("font-size", singleValue fontSize)
,("font-style", singleValue fontStyle)
,("font-weight", singleValue numberOrText)
,("font-family", fontFamilyValues)
,("background", background)
,("background-repeat", parseCommaSeparated (RepeatStyleV <$> repeatStyle))
,("background-size", backgroundSize)
,("box-shadow", shadowList)
,("-o-box-shadow", shadowList)
,("-moz-box-shadow", shadowList)
,("-webkit-box-shadow", shadowList)
,("text-shadow", textShadow)
,("animation", animation)
,("-o-animation", animation)
,("-ms-animation", animation)
,("-moz-animation", animation)
,("-webkit-animation", animation)
,("transition", transition)
,("-o-transition", transition)
,("-ms-transition", transition)
,("-moz-transition", transition)
,("-webkit-transition", transition)
,("height", singleValue numberOrText)
,("margin-bottom", singleValue numberOrText)
,("margin-left", singleValue numberOrText)
,("background-position", singleValue positionvalue)
,("perspective-origin", singleValue positionvalue)
,("-o-perspective-origin", singleValue positionvalue)
,("-moz-perspective-origin", singleValue positionvalue)
,("-webkit-perspective-origin", singleValue positionvalue)
,("mask-position", positionList)
,("-webkit-mask-position", positionList)
]
where numberOrText = numericalvalue <|> textualvalue
singleValue :: Parser Value -> Parser Values
singleValue = (flip Values [] <$>)
invalidvalue :: Parser Value
invalidvalue = mkOther <$> A.takeWhile1 cond
where cond c = c /= '\\' && c /= ';' && c /= '}' && c /= '!'
font :: Parser Value
font = systemFonts <|> do
(fsty, fvar, fwgt, fstr) <- parse4
(fsz, lh) <- fontSizeAndLineHeight
ff <- ((:) <$> fontfamily <* skipComments) <*> many (char ',' *> lexeme fontfamily)
pure $ FontV fsty fvar fwgt fstr fsz lh ff
where systemFonts = Other <$> parseIdents ["caption", "icon", "menu", "message-box", "small-caption", "status-bar"]
fontSizeAndLineHeight = do
fsz <- fontSize <* skipComments
lh <- option Nothing (Just <$> (char '/' *> lexeme lineHeight))
pure (fsz, lh)
lineHeight = let validNum = do n <- numericalvalue
case n of
NumberV _ -> pure n
PercentageV _ -> pure n
LengthV _ -> pure n
_ -> mzero
in (Other <$> parseIdents ["normal"]) <|> validNum
fontWeightNumber :: Parser Value
fontWeightNumber = do
n <- number
if Set.notMember n (Set.fromList [100, 200, 300, 400, 500, 600, 700, 800, 900])
then mzero
else do c <- A.peekChar
case c of
Nothing -> mzero
Just x -> if isAscii x || x == '%'
then mzero
else pure $ NumberV n
parseFirstFour j = (storeProperty' j <$> fontWeightNumber <* skipComments) <|> do
i <- ident
case Map.lookup (T.toLower i) m of
Just x -> case x of
FontWeight -> pure $ storeProperty' j (mkOther i)
_ -> pure $ storeProperty j x (TextV i)
Nothing -> mzero
where m = Map.fromList $ zip ["ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"] (repeat FontStretch)
++ zip ["small-caps"] (repeat FontVariant)
++ zip ["italic", "oblique"] (repeat FontStyle)
++ zip ["bold", "bolder", "lighter"] (repeat FontWeight)
++ [("normal", Ambiguous)]
parse4 :: Parser (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV)
parse4 = do
let initialized = (Nothing, Nothing, Nothing, Nothing, 0)
w <- option initialized (parseFirstFour initialized <* skipComments)
x <- option w (parseFirstFour w <* skipComments)
y <- option x (parseFirstFour x <* skipComments)
(a,b,c,d,e) <- option y (parseFirstFour y <* skipComments)
pure $ fillTuple (a,b,c,d) e
fillTuple (a,b,c,d) 0 = (a,b,c,d)
fillTuple (a,b,c,d) x
| isNothing a = fillTuple (Just nrml,b,c,d) (x1)
| isNothing b = fillTuple (a, Just nrml,c,d) (x1)
| isNothing c = fillTuple (a,b, Just $ Other nrml,d) (x1)
| isNothing d = fillTuple (a,b,c, Just nrml) (x1)
| otherwise = (a,b,c,d)
where nrml = TextV "normal"
data FontProperty = FontStyle | FontVariant | FontWeight | FontStretch | Ambiguous
deriving (Eq, Show)
storeProperty :: (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV, Int) -> FontProperty -> TextV
-> (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV, Int)
storeProperty (a,b,c,d,i) y x = replace y
where replace FontStyle = (Just x,b,c,d,i)
replace FontVariant = (a, Just x,c,d,i)
replace FontStretch = (a,b,c, Just x,i)
replace Ambiguous = (a,b,c,d,i + 1)
replace _ = (a,b,c,d,i)
storeProperty' :: (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV, Int) -> Value
-> (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV, Int)
storeProperty' (a,b,_,d,i) x = (a,b, Just x,d,i)
fontFamilyValues :: Parser Values
fontFamilyValues = singleValue csswideKeyword <|> do
v <- fontfamily
vs <- many ((,) <$> separator <*> fontfamily)
pure $ Values v vs
fontfamily :: Parser Value
fontfamily = (StringV <$> stringtype) <|> (mkOther <$> unquotedFontFamily)
local :: Parser Value
local = functionParser $
Local <$> ((Left <$> unquotedFontFamily) <|> (Right <$> stringtype))
unquotedFontFamily :: Parser Text
unquotedFontFamily = do
v <- ident
vs <- many (skipComments *> ident)
pure $ v <> mconcat (map (T.singleton ' ' <>) vs)
textualvalue :: Parser Value
textualvalue = do
i <- ident
if i == "\\9"
then mzero
else do c <- A.peekChar
case c of
Just '(' -> functionParsers i
Just ':' -> mzero
_ -> textualParsers i
textualParsers :: Text -> Parser Value
textualParsers i = let t = T.toCaseFold i
in fromMaybe (pure $ mkOther i) (Map.lookup t textualParsersMap)
where textualParsersMap = Map.union csswideKeywordsMap namedColorsParsersMap
csswideKeyword :: Parser Value
csswideKeyword = do
i <- ident <* skipComments
let lowercased = T.toLower i
case Map.lookup lowercased csswideKeywordsMap of
Nothing -> mzero
Just x -> do c <- A.peekChar
case c of
Nothing -> x
Just y -> if y `elem` ['!', ';', '}']
then x
else mzero
csswideKeywordsMap :: Map Text (Parser Value)
csswideKeywordsMap = Map.fromList $ map (first T.toCaseFold)
[("initial", pure Initial)
,("inherit", pure Inherit)
,("unset", pure Unset)]
stringvalue :: Parser Value
stringvalue = StringV <$> stringtype
functionParsers :: Text -> Parser Value
functionParsers i = char '(' *>
case Map.lookup (T.toCaseFold i) functionsMap of
Just x -> x <|> genericFunc i
Nothing -> genericFunc i
<|> (mkOther <$> (f i "(" <$> someText <*> string ")"))
where f x y z w = x <> y <> z <> w
someText = A.takeWhile (/= ')')
genericFunc :: Text -> Parser Value
genericFunc i = (GenericFunc i <$> valuesInParens) <* char ')'
valuesInParens :: Parser Values
valuesInParens = Values <$> v <*> many ((,) <$> separator <*> v) <* skipComments
where v = textualvalue
<|> numericalvalue
<|> hexvalue
<|> (StringV <$> stringtype)
stringOrUrl :: Parser (Either StringType Url)
stringOrUrl = (Left <$> stringtype) <|> (Right <$> someUrl)
where someUrl :: Parser Url
someUrl = asciiCI "url" *> char '(' *> url
repeatStyle :: Parser RepeatStyle
repeatStyle = do
i <- ident
let lowercased = T.toLower i
case L.lookup lowercased singleKeywords of
Nothing -> case Map.lookup lowercased keywordPairs of
Nothing -> mzero
Just y -> (RepeatStyle2 y <$> secondKeyword)
<|> pure (RepeatStyle1 y)
Just x -> pure x
where secondKeyword = do
z <- skipComments *> ident
maybe mzero pure $ Map.lookup (T.toLower z) keywordPairs
singleKeywords = [("repeat-x", RepeatX), ("repeat-y", RepeatY)]
keywordPairs = Map.fromList [("repeat", RsRepeat)
,("no-repeat", RsNoRepeat)
,("space", RsSpace)
,("round", RsRound)]
functionsMap :: Map Text (Parser Value)
functionsMap = Map.fromList $ fmap (first T.toCaseFold)
[("rgb", ColorV <$> rgb)
,("rgba", ColorV <$> rgba)
,("hsl", ColorV <$> hsl)
,("hsla", ColorV <$> hsla)
,("url", UrlV <$> url)
,("format", format)
,("local", local)
,("linear-gradient", GradientV "linear-gradient" <$> lineargradient)
,("-o-linear-gradient", GradientV "-o-linear-gradient" <$> lineargradient)
,("-ms-linear-gradient", GradientV "-ms-linear-gradient" <$> lineargradient)
,("-moz-linear-gradient", GradientV "-moz-linear-gradient" <$> lineargradient)
,("-webkit-linear-gradient", GradientV "-webkit-linear-gradient" <$> lineargradient)
,("radial-gradient", GradientV "radial-gradient" <$> radialgradient)
,("-o-radial-gradient", GradientV "-o-radial-gradient" <$> radialgradient)
,("-moz-radial-gradient", GradientV "-moz-radial-gradient" <$> radialgradient)
,("-webkit-radial-gradient", GradientV "-webkit-radial-gradient" <$> radialgradient)
,("rect", rect)
,("matrix", TransformV <$> matrix)
,("matrix3d", TransformV <$> matrix3d)
,("rotate", (TransformV . Rotate) <$> functionParser angle)
,("rotate3d", TransformV <$> rotate3d)
,("rotateX", (TransformV . Rotate) <$> functionParser angle)
,("rotateY", (TransformV . Rotate) <$> functionParser angle)
,("rotateZ", (TransformV . Rotate) <$> functionParser angle)
,("scale", TransformV <$> scale)
,("scale3d", TransformV <$> scale3d)
,("scaleX", (TransformV . ScaleY) <$> functionParser number)
,("scaleY", (TransformV . ScaleY) <$> functionParser number)
,("scaleZ", (TransformV . ScaleZ) <$> functionParser number)
,("skew", TransformV <$> skew)
,("skewX", (TransformV . SkewX) <$> functionParser angle)
,("skewY", (TransformV . SkewY) <$> functionParser angle)
,("translate", TransformV <$> translate)
,("translate3d", TransformV <$> translate3d)
,("translateX", (TransformV . TranslateX) <$> functionParser percentageLength)
,("translateY", (TransformV . TranslateY) <$> functionParser percentageLength)
,("translateZ", (TransformV . TranslateZ) <$> functionParser distance)
,("perspective", (TransformV . Perspective) <$> functionParser distance)
,("cubic-bezier", TimingFuncV <$> cubicbezier)
,("steps", TimingFuncV <$> steps)
,("blur", (FilterV . Blur) <$> functionParser distance)
,("contrast", (FilterV . Contrast) <$> functionParser numberPercentage)
,("grayscale", (FilterV . Grayscale) <$> functionParser numberPercentage)
,("invert", (FilterV . Invert) <$> functionParser numberPercentage)
,("opacity", (FilterV . Opacity) <$> functionParser numberPercentage)
,("saturate", (FilterV . Saturate) <$> functionParser numberPercentage)
,("sepia", (FilterV . Sepia) <$> functionParser numberPercentage)
,("brightness", (FilterV . Brightness) <$> functionParser numberPercentage)
,("drop-shadow", FilterV <$> dropShadow)
,("hue-rotate", (FilterV . HueRotate) <$> functionParser angle)
,("element", genericFunc "element")
]
dropShadow :: Parser FilterFunction
dropShadow = functionParser $ do
l1 <- distance
l2 <- lexeme distance
l3 <- option Nothing ((Just <$> distance) <* skipComments)
c <- option Nothing (Just <$> color)
pure $ DropShadow l1 l2 l3 c
textShadow :: Parser Values
textShadow = parseCommaSeparated shadowText
shadowText :: Parser Value
shadowText = permute (mkShadowText <$$> (lns <* skipComments)
<|?> (Nothing , Just <$> color <* skipComments))
where mkShadowText (x,y,b) = ShadowText x y b
lns = do
l1 <- distance
l2 <- lexeme distance
l3 <- option Nothing ((Just <$> distance) <* skipComments)
pure (l1,l2,l3)
shadowList :: Parser Values
shadowList = parseCommaSeparated (ShadowV <$> shadow)
positionList :: Parser Values
positionList = parseCommaSeparated positionvalue
parseCommaSeparated :: Parser Value -> Parser Values
parseCommaSeparated p = do
v <- p
vs <- lexeme $ many ((,) <$> commaSeparator <*> p)
c <- A.peekChar
case c of
Just x -> if x `elem` ['!', ';', '}']
then pure $ Values v vs
else mzero
Nothing -> pure $ Values v vs
shadow :: Parser Shadow
shadow = permute (mkShadow <$?> (False, asciiCI "inset" $> True <* skipComments)
<||> fourLengths
<|?> (Nothing , Just <$> color <* skipComments))
where mkShadow i (l1,l2,l3,l4) = Shadow i l1 l2 l3 l4
fourLengths = do
l1 <- distance
l2 <- lexeme distance
l3 <- option Nothing ((Just <$> distance) <* skipComments)
l4 <- option Nothing ((Just <$> distance) <* skipComments)
pure (l1,l2,l3,l4)
radialgradient :: Parser Gradient
radialgradient = functionParser $ do
(def, c) <- option (True, RadialGradient Nothing Nothing) ((False,) <$> endingShapeAndSize <* skipComments)
p <- option Nothing (asciiCI "at" *> skipComments *> (Just <$> position))
_ <- if def && isNothing p
then pure '*'
else comma
cs <- colorStopList
pure $ c p cs
where circle = asciiCI "circle" $> Just Circle <* skipComments
ellipse = asciiCI "ellipse" $> Just Ellipse <* skipComments
endingShapeAndSize = r1 <|> r2 <|> r3
where r1 = permute (RadialGradient <$?> (Nothing, ellipse) <||> (Just <$> (PL <$> percentageLength <*> lexeme percentageLength)))
r2 = permute (RadialGradient <$?> (Nothing, circle) <||> ((Just . SL) <$> distance <* skipComments))
r3 = permute (RadialGradient <$?> (Nothing, circle <|> ellipse) <||> extentKeyword)
<|> permute (RadialGradient <$$> (circle <|> ellipse) <|?> (Nothing, extentKeyword))
extentKeyword = do
i <- ident
_ <- skipComments
case Map.lookup i extentKeywords of
Just x -> pure (Just x)
Nothing -> mzero
extentKeywords :: Map Text Size
extentKeywords = Map.fromList [("closest-corner", ClosestCorner)
,("closest-side", ClosestSide)
,("farthest-corner", FarthestCorner)
,("farthest-side", FarthestSide)]
lineargradient :: Parser Gradient
lineargradient = functionParser (lg <|> oldLg)
where lg = do
x <- option Nothing angleOrSide
c <- colorStopList
pure $ LinearGradient x c
oldLg = do
x <- option Nothing ((ga <|> ((Just . Right) <$> sideOrCorner)) <* comma)
c <- colorStopList
pure $ OldLinearGradient x c
angleOrSide = (ga <|> gs) <* comma
ga = (Just . Left) <$> angle
gs = asciiCI "to" *> skipComments *> ((Just . Right) <$> sideOrCorner)
sideOrCorner :: Parser (Side, Maybe Side)
sideOrCorner = orderOne <|> orderTwo
where orderOne = (,) <$> leftright <* skipComments
<*> option Nothing (Just <$> topbottom)
orderTwo = (,) <$> topbottom <* skipComments
<*> option Nothing (Just <$> leftright)
leftright :: Parser Side
leftright = (asciiCI "left" $> LeftSide)
<|> (asciiCI "right" $> RightSide)
topbottom :: Parser Side
topbottom = (asciiCI "top" $> TopSide)
<|> (asciiCI "bottom" $> BottomSide)
colorStopList :: Parser [ColorStop]
colorStopList = do
c1 <- colorStop
_ <- char ',' <* skipComments
c2 <- colorStop
cs <- many (char ',' *> skipComments *> colorStop)
pure $ c1:c2:cs
colorStop :: Parser ColorStop
colorStop = ColorStop <$> color <* skipComments
<*> option Nothing (Just <$> percentageLength <* skipComments)
color :: Parser Color
color = hex <|> othercolor
where othercolor = do
t <- textualvalue
case t of
ColorV c -> pure c
_ -> mzero
percentageLength :: Parser PercentageLength
percentageLength = do
n <- numericalvalue
case n of
PercentageV p -> pure $ Left p
NumberV 0 -> pure $ Right NullLength
LengthV d -> pure $ Right d
_ -> mzero
numberPercentage :: Parser (Either Number Percentage)
numberPercentage = do
n <- numericalvalue
case n of
NumberV x -> pure $ Left x
PercentageV p -> pure $ Right p
_ -> mzero
rect :: Parser Value
rect = functionParser $ do
length1 <- distance <* comma
length2 <- distance <* comma
length3 <- distance <* comma
length4 <- distance
pure $ Rect length1 length2 length3 length4
translate :: Parser TransformFunction
translate = functionParser $ do
pl <- percentageLength <* skipComments
mpl <- option Nothing (char ',' *> skipComments *> (Just <$> percentageLength))
pure $ Translate pl mpl
scale :: Parser TransformFunction
scale = functionParser $ do
n <- number <* skipComments
mn <- option Nothing (char ',' *> skipComments *> (Just <$> number))
pure $ Scale n mn
scale3d :: Parser TransformFunction
scale3d = functionParser $ liftA3 Scale3d n n number
where n = number <* comma
skew :: Parser TransformFunction
skew = functionParser $ do
a <- angle <* skipComments
ma <- option Nothing (char ',' *> skipComments *> (Just <$> angle))
pure $ Skew a ma
translate3d :: Parser TransformFunction
translate3d = functionParser $ do
x <- percentageLength <* comma
y <- percentageLength <* comma
z <- distance
pure $ Translate3d x y z
matrix :: Parser TransformFunction
matrix = functionParser $ do
n <- number
ns <- count 5 (comma *> number)
pure $ mkMat (n:ns)
matrix3d :: Parser TransformFunction
matrix3d = functionParser $ do
n <- number
ns <- count 15 (comma *> number)
pure $ mkMat3d (n:ns)
rotate3d :: Parser TransformFunction
rotate3d = functionParser $ do
x <- number <* comma
y <- number <* comma
z <- number <* comma
a <- angle
pure $ Rotate3d x y z a
cubicbezier :: Parser TimingFunction
cubicbezier = functionParser $ do
p0 <- number <* comma
p1 <- number <* comma
p2 <- number <* comma
p3 <- number
pure $ CubicBezier p0 p1 p2 p3
steps :: Parser TimingFunction
steps = functionParser $ do
i <- int
s <- option Nothing (comma *> (Just <$> startOrEnd))
pure $ Steps i s
where startOrEnd = (asciiCI "end" $> End)
<|> (asciiCI "start" $> Start)
url :: Parser Url
url = Url <$> (skipSpace *> someUri <* skipSpace <* char ')')
where someUri = (Right <$> stringtype) <|> (Left <$> nonQuotedUri)
nonQuotedUri = A.takeWhile1 (/= ')')
format :: Parser Value
format = Format <$> functionParser p
where p = (:) <$> stringtype <*> many (comma *> stringtype)
namedColorsParsersMap :: Map Text (Parser Value)
namedColorsParsersMap = Map.fromList $ foldr f [] keywordColors
where f x xs = let a = fst x
in (a, pure $ ColorV (Named a)) : xs
valuesFallback :: Parser Values
valuesFallback = Values <$> value <*> many ((,) <$> separator <*> value) <* skipComments
value :: Parser Value
value = textualvalue
<|> numericalvalue
<|> hexvalue
<|> (StringV <$> stringtype)
<|> invalidvalue
separator :: Parser Separator
separator = lexeme $ (char ',' $> Comma)
<|> (char '/' $> Slash)
<|> pure Space
commaSeparator :: Parser Separator
commaSeparator = lexeme (char ',' $> Comma)
stringtype :: Parser StringType
stringtype = doubleQuotesString <|> singleQuotesString
doubleQuotesString :: Parser StringType
doubleQuotesString = char '\"' *> (DoubleQuotes <$> untilDoubleQuotes)
where untilDoubleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\"') <*> checkCharacter
checkCharacter = (string "\"" $> mempty)
<|> (T.cons <$> char '\\' <*> untilDoubleQuotes)
singleQuotesString :: Parser StringType
singleQuotesString = char '\'' *> (SingleQuotes <$> untilSingleQuotes)
where untilSingleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\'') <*> checkCharacter
checkCharacter = (string "\'" $> mempty)
<|> (T.cons <$> char '\\' <*> untilSingleQuotes)
animation :: Parser Values
animation = parseCommaSeparated singleAnimation
singleAnimation :: Parser Value
singleAnimation = do
sa <- permute (mkSingleAnimation <$?> (Nothing, Just <$> keyframesName <* skipComments)
<|?> (Nothing, Just <$> iterationCount <* skipComments)
<|?> (Nothing, Just <$> duration <* skipComments)
<|?> (Nothing, Just <$> duration <* skipComments)
<|?> (Nothing, Just <$> timingFunction <* skipComments)
<|?> (Nothing, Just <$> animationDirection <* skipComments)
<|?> (Nothing, Just <$> animationFillMode <* skipComments)
<|?> (Nothing, Just <$> animationPlayState <* skipComments))
if saIsEmpty sa
then mzero
else pure sa
where
mkSingleAnimation kf ic t1 t2 tf c d e = SingleAnimation t2 tf t1 ic c d e kf
saIsEmpty (SingleAnimation Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing) = True
saIsEmpty _ = False
iterationCount = (mkOther <$> asciiCI "infinite") <|> (NumberV <$> number)
animationDirection = parseIdents ["normal", "reverse", "alternate", "alternate-reverse"]
animationFillMode = parseIdents ["none", "forwards", "backwards", "both"]
animationPlayState = parseIdents ["running", "paused"]
keyframesName = stringvalue <|> (mkOther <$> ident)
parseIdents :: [Text] -> Parser TextV
parseIdents ls = do
i <- ident
if Set.member (T.toLower i) s
then pure $ TextV i
else mzero
where s = Set.fromList ls