diff options
| author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-07-12 18:26:38 -0700 |
|---|---|---|
| committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-07-12 18:27:42 -0700 |
| commit | 7c50bb9c26e7c9988cfcdbf8ea2cd3030324bd80 (patch) | |
| tree | faf93ae2a25544e8ec176c87d600babd05beaf27 | |
| parent | d26f7ed6756967360b37780b517857b067d62b88 (diff) | |
| download | pyscss-7c50bb9c26e7c9988cfcdbf8ea2cd3030324bd80.tar.gz | |
At last, real parsing for url(). Partly. #192
| -rw-r--r-- | scss/expression.py | 60 | ||||
| -rw-r--r-- | scss/src/grammar/grammar.g | 15 | ||||
| -rw-r--r-- | scss/types.py | 7 |
3 files changed, 64 insertions, 18 deletions
diff --git a/scss/expression.py b/scss/expression.py index bb2fd01..034a007 100644 --- a/scss/expression.py +++ b/scss/expression.py @@ -15,7 +15,7 @@ import scss.config as config from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re from scss.errors import SassError, SassEvaluationError, SassParseError from scss.rule import Namespace -from scss.types import Boolean, Color, List, Map, Null, Number, String, Undefined, Value +from scss.types import Boolean, Color, List, Map, Null, Number, String, Undefined, Url, Value from scss.util import dequote, normalize_var ################################################################################ @@ -576,6 +576,9 @@ class Parser(object): class SassExpressionScanner(Scanner): patterns = None _patterns = [ + ('"\'"', "'"), + ('"\\""', '"'), + ('"url"', 'url'), ('":"', ':'), ('","', ','), ('[ \r\t\n]+', '[ \r\t\n]+'), @@ -614,6 +617,7 @@ class SassExpressionScanner(Scanner): ('KWID', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\s*:)'), ('ID', '[-a-zA-Z_][-a-zA-Z0-9_]*'), ('BANG_IMPORTANT', '!important'), + ('URL', '(?:[\\\\].|[^\'"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f])*'), ] def __init__(self, input=None): @@ -837,6 +841,25 @@ class SassExpression(Parser): v = expr_lst RPAR = self._scan('RPAR') return Parentheses(v) + elif _token_ == '"url"': + self._scan('"url"') + LPAR = self._scan('LPAR') + _token_ = self._peek(self.atom_rsts_) + if _token_ == 'URL': + URL = self._scan('URL') + quotes = None + elif _token_ == '"\\""': + self._scan('"\\""') + URL = self._scan('URL') + self._scan('"\\""') + quotes = '"' + else: # == '"\'"' + self._scan('"\'"') + URL = self._scan('URL') + self._scan('"\'"') + quotes = "'" + RPAR = self._scan('RPAR') + return Literal(Url(URL, quotes=quotes)) elif _token_ == 'FNCT': FNCT = self._scan('FNCT') LPAR = self._scan('LPAR') @@ -852,7 +875,7 @@ class SassExpression(Parser): elif _token_ == 'NUM': NUM = self._scan('NUM') UNITS = None - if self._peek(self.atom_rsts_) == 'UNITS': + if self._peek(self.atom_rsts__) == 'UNITS': UNITS = self._scan('UNITS') return Literal(Number(float(NUM), unit=UNITS)) elif _token_ == 'STR': @@ -894,32 +917,33 @@ class SassExpression(Parser): KWVAR = self._scan('KWVAR') return Variable(KWVAR) - u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + u_expr_chks = set(['"url"', 'LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) + m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) argspec_items_rsts = set(['RPAR', 'END', '","']) expr_map_rsts = set(['RPAR', '","']) - argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) kwatom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'KWCOLOR', '":"', 'KWNUM']) - argspec_item_chks = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + argspec_item_chks = set(['"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) a_expr_chks = set(['ADD', 'SUB']) - expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) - or_expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) - and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) - comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) + expr_slst_rsts = set(['"url"', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) + atom_rsts__ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + or_expr_rsts = set(['"url"', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) + and_expr_rsts = set(['AND', 'LPAR', 'RPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) + comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) argspec_chks = set(['DOTDOTDOT', 'SLURPYVAR']) - atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + atom_rsts_ = set(['URL', '"\\""', '"\'"']) expr_map_rsts_ = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'RPAR', 'KWCOLOR', '":"', 'KWNUM', '","']) - u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) + u_expr_rsts = set(['"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ']) - argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'RPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) m_expr_chks = set(['MUL', 'DIV']) kwatom_rsts_ = set(['UNITS', '":"']) - argspec_items_chks = set(['KWVAR', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) - argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID']) - atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'RPAR', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT']) + argspec_items_chks = set(['KWVAR', '"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID']) + atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'RPAR', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '"url"', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT']) argspec_chks_ = set(['END', 'RPAR']) - argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID']) + argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID']) ### Grammar ends. diff --git a/scss/src/grammar/grammar.g b/scss/src/grammar/grammar.g index 135d594..58489aa 100644 --- a/scss/src/grammar/grammar.g +++ b/scss/src/grammar/grammar.g @@ -40,6 +40,10 @@ parser SassExpression: token KWID: "[-a-zA-Z_][-a-zA-Z0-9_]*(?=\s*:)" token ID: "[-a-zA-Z_][-a-zA-Z0-9_]*" token BANG_IMPORTANT: "!important" + # http://dev.w3.org/csswg/css-syntax-3/#consume-a-url-token0 + # URLs may not contain quotes, parentheses, or unprintables + # TODO reify escapes, for this and for strings + token URL: "(?:[\\\\].|[^'\"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f])*" # Goals: rule goal: expr_lst END {{ return expr_lst }} @@ -152,6 +156,17 @@ parser SassExpression: | expr_map {{ v = expr_map }} | expr_lst {{ v = expr_lst }} ) RPAR {{ return Parentheses(v) }} + # Special functions. Note that these technically overlap with the + # regular function rule, which makes this not quite LL -- but they're + # different tokens so yapps can't tell, and it resolves the conflict by + # picking the first one. + | "url" LPAR + ( + URL {{ quotes = None }} + | "\"" URL "\"" {{ quotes = '"' }} + | "'" URL "'" {{ quotes = "'" }} + ) + RPAR {{ return Literal(Url(URL, quotes=quotes)) }} | FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }} | BANG_IMPORTANT {{ return Literal(String(BANG_IMPORTANT, quotes=None)) }} | ID {{ return Literal(parse_bareword(ID)) }} diff --git a/scss/types.py b/scss/types.py index ef28748..27528e2 100644 --- a/scss/types.py +++ b/scss/types.py @@ -1062,6 +1062,13 @@ class String(Value): return self.__str__() +class Url(String): + def render(self, compress=False): + # TODO url-escape whatever needs escaping + # TODO does that mean we should un-url-escape when parsing? probably + return "url({0})".format(super(String, self).render(compress)) + + class Map(Value): sass_type_name = six.u('map') |
