{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} ----------------------------------------------------------------------------- -- | -- Module : Hasmin.Parser.Value -- Copyright : (c) 2017 Cristian Adrián Ontivero -- License : BSD3 -- Stability : experimental -- Portability : unknown -- -- Parsers for CSS values. -- ----------------------------------------------------------------------------- module Hasmin.Parser.Value ( valuesFor , valuesFallback , value , valuesInParens , stringOrUrl , percentage , url , stringtype , stringvalue -- used in StringSpec , shadowList -- used in ShadowSpec , timingFunction , repeatStyle , position , color , number , fontStyle , textualvalue , borderRadius ) where import Control.Applicative ((<|>), many, optional) import Control.Arrow (first) import Control.Monad (mzero) import Data.Functor (($>)) import Data.Monoid ((<>)) import Data.Maybe (fromMaybe, isNothing) import Data.Text (Text) import Data.Char (isAscii) import Text.Parser.Permutation ((<|?>), (<$$>), (<$?>), (<||>), permute) import qualified Data.Set as Set import Data.Attoparsec.Text (asciiCI, char, option, Parser, skipSpace, string) import qualified Data.Attoparsec.Text as A import qualified Data.Char as C import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import qualified Data.List as L import qualified Data.Text as T import Hasmin.Parser.BasicShape import Hasmin.Parser.BorderRadius import Hasmin.Parser.Color import Hasmin.Parser.Dimension import Hasmin.Parser.Gradient import Hasmin.Parser.Numeric import Hasmin.Parser.PercentageLength import Hasmin.Parser.Position import Hasmin.Parser.String import Hasmin.Parser.TimingFunction import Hasmin.Parser.TransformFunction import Hasmin.Parser.Utils import Hasmin.Types.BgSize import Hasmin.Types.Dimension import Hasmin.Types.FilterFunction import Hasmin.Types.Numeric import Hasmin.Types.Position import Hasmin.Types.RepeatStyle import Hasmin.Types.Shadow import Hasmin.Types.String import Hasmin.Types.TransformFunction import Hasmin.Types.Value import Hasmin.Utils -- | Given a propery name, it returns a specific parser of values for that -- property. Fails if no specific parser is found. valuesFor :: Text -> Parser Values valuesFor propName = maybe mzero (<* skipComments) parser where parser = Map.lookup (T.toLower propName) propertyValueParsersMap -- A map relating dimension units and the percentage symbol, -- to functions that construct that value. Meant to unify all the numerical -- parsing in a single parse for generality without losing much efficiency. -- See numericalvalue. numericalConstructorsMap :: Map Text (Number -> Value) numericalConstructorsMap = Map.fromList $ fmap (first T.toCaseFold) l where frequencyFunc u v = FrequencyV (Frequency v u) resolutionFunc u v = ResolutionV (Resolution v u) l = [("hz", frequencyFunc Hz) ,("khz", frequencyFunc Khz) ,("dpi", resolutionFunc Dpi) ,("dpcm", resolutionFunc Dpcm) ,("dppx", resolutionFunc Dppx) ,("%", \x -> PercentageV (Percentage $ toRational x)) ] ++ (fmap . fmap) (TimeV .) timeConstructorsList ++ (fmap . fmap) (AngleV .) angleConstructorsList ++ (fmap . fmap) (LengthV .) distanceConstructorsList -- Unified numerical parser. -- Parses , dimensions (i.e. , , ...), and numericalvalue :: Parser Value numericalvalue = do n <- number rest <- opt (string "%" <|> A.takeWhile1 C.isAlpha) if T.null rest -- if true, then it was just a value then pure $ NumberV n else case Map.lookup (T.toCaseFold rest) numericalConstructorsMap of Just f -> pure $ f n Nothing -> mzero -- TODO see if we should return an "Other" value hexvalue :: Parser Value hexvalue = ColorV <$> hex -- | Parser for >, -- used in the @font-style@ and @font@ properties. fontStyle :: Parser Value fontStyle = Other <$> matchKeywords ["normal", "italic", "oblique"] {- fontWeight :: Parser Value fontWeight = do k <- ident if Set.member k keywords then pure $ mkOther k else mzero where keywords = Set.fromList ["normal", "bold", "lighter", "bolder"] validNumbers = Set.fromList [100, 200, 300, 400, 500, 600, 700, 800, 900] -} fontSize :: Parser Value fontSize = fontSizeKeyword <|> (LengthV <$> distance) <|> (PercentageV <$> percentage) where fontSizeKeyword = Other <$> matchKeywords ["large", "xx-small", "x-small", "small", "medium", "x-large", "xx-large", "smaller" , "larger"] {- [ [ <‘font-style’> || || <‘font-weight’> || - <‘font-stretch’> ]? <‘font-size’> [ / <‘line-height’> ]? <‘font-family’> ] | - caption | icon | menu | message-box | small-caption | status-bar where = [normal | small-caps] -} positionvalue :: Parser Value positionvalue = PositionV <$> position {- transformOrigin :: Parser Values transformOrigin = twoVal <|> oneVal where oneVal = (singleValue numericalvalue) <|> offsetKeyword offsetKeyword = do v1 <- ident if v1 == "top" || v1 == "right" || v1 == "bottom" || v1 == "left" || v1 == "center" then pure $ mkValues [mkOther v1] else mzero twoVal = do (v1, v2) <- ((,) <$> yAxis <*> (skipComments *> (xAxis <|> numericalvalue))) <|> ((,) <$> xAxis <*> (skipComments *> (yAxis <|> numericalvalue))) <|> ((,) <$> numericalvalue <*> (skipComments *> (yAxis <|> xAxis))) option (mkValues [v1,v2]) ((\x -> mkValues [v1,v2, LengthV x]) <$> distance) yAxis = do v1 <- ident if v1 == "top" || v1 == "bottom" || v1 == "center" then pure $ mkOther v1 else mzero xAxis = do v1 <- ident if v1 == "right" || v1 == "left" || v1 == "center" then pure $ mkOther v1 else mzero -} bgSize :: Parser BgSize bgSize = twovaluesyntax <|> containOrCover where containOrCover = parserFromPairs [("cover", pure Cover) ,("contain", pure 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]) -- = || [ / ]? || || || || || <'background-color'> 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 -- parameters e and f are being swapped to deal with the permutation -- changing the original order of the parsed values. 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 -- = || [ / ]? || || || {1,2} 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 <- optional box pure (x,y) -- used for the background property, which takes among other things: -- [ / ]? positionAndBgSize :: Parser (Position, Maybe BgSize) positionAndBgSize = mzip position (optional (slash *> bgSize)) 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 ["url", "element", "linear-gradient", "radial-gradient"] transition :: Parser Values transition = parseCommaSeparated singleTransition -- [ none | ] ||