packages feed

css-parser-0.0.1: src/CssParser/Utils.hs

module CssParser.Utils where

import CssParser.List ( _initLast )
import Data.Char (chr, digitToInt, intToDigit, isAsciiLower, isAsciiUpper, isHexDigit, isDigit, ord)
import Data.Text (Text, cons, pack, singleton)
import qualified Data.Text.Lazy as LT
import Prelude

_isQuote :: Char -> Bool
_isQuote '"' = True
_isQuote '\'' = True
_isQuote _ = False

-- | Parses a css string literal to a string that ontains the content of that
-- string literal.
readCssString ::
  -- | The string that contains the string literal in the css selector.
  String ->
  -- | A string that contains the content of the string literal.
  String
readCssString (c : xs) | _isQuote c = f
  where
    f
      | Just (vs, c') <- _initLast xs = g c' vs
      | otherwise = "The string literal should contain at least two quotation marks."
      where
        g c' vs
          | c == c' = _readCssString c vs
          | otherwise = "The start and end quotation mark should be the same."
readCssString _ = error "The string should start with an \" or ' and end with the same quotation."

_readCssString :: Char -> String -> String
_readCssString c' = go
  where
    go [] = []
    go ('\\' : '\n' : xs) = go xs
    go ('\\' : ca@(c : xs))
      | c == c' = c : go xs
      | otherwise = let ~(y, ys) = _parseEscape ca in y : go ys
    go (x : xs)
      | x == c' = error "The string can not contain a " ++ show x ++ ", you should escape it."
      | otherwise = x : go xs

readIdentifier :: String -> String
readIdentifier = _readCssString '\\'

_notEncode :: Char -> Bool
_notEncode x = isAsciiLower x || isAsciiUpper x || x == '-' || x == '_' || isDigit x

-- | Convert a string to a css selector string literal. This is done by putting
-- quotes around the content, and escaping certain characters.
encodeString ::
  -- | The type of quotes that should be put around the content (should be @'@ or @"@).
  Char ->
  -- | The string that should be converted to a css selector string literal.
  String ->
  -- | The corresponding css selector string literal.
  String
encodeString c' = (c' :) . go
  where
    go [] = [c']
    go (c : cs)
      | _notEncode c = c : go cs
      | otherwise = '\\' : _showHex (ord c) (go cs)

encodeCharacter :: Char -> LT.Text
encodeCharacter c
  | _notEncode c = LT.singleton c
  | otherwise = LT.cons '\\' (LT.pack (_showHex (ord c) ""))

_encodeCharacter :: Char -> Text
_encodeCharacter c
  | _notEncode c = singleton c
  | otherwise = cons '\\' (pack (_showHex (ord c) ""))

-- | Encode a given identifier to its css selector equivalent by escaping
-- certain characters.
encodeIdentifier ::
  -- | The identifier to encode.
  Text ->
  -- | The encoded identifier.
  LT.Text
encodeIdentifier = LT.concatMap encodeCharacter . LT.fromStrict

_showHex :: Int -> ShowS
_showHex = go (6 :: Int)
  where
    go 0 _ s = s
    go k n rs = go (k - 1) q (intToDigit r : rs)
      where
        ~(q, r) = quotRem n 16

_parseEscape :: String -> (Char, String)
_parseEscape = go (6 :: Int) 0
  where
    go 0 n cs = yield n cs
    go _ n "" = yield n ""
    go i n ca@(c : cs)
      | isHexDigit c = go (i - 1) (16 * n + digitToInt c) cs
      | otherwise = yield n ca
    yield n s = (chr n, s)