summaryrefslogtreecommitdiff
path: root/scss/types.py
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2014-08-29 15:36:06 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2014-08-29 17:07:28 -0700
commit227ea22bb12c976aa6a59bf74084e181dee436f4 (patch)
treeef39587cebdb912cf3f6bd093f992c74cf456926 /scss/types.py
parent45d3a91713e7a26050121f300d3ea2b93c7908d1 (diff)
downloadpyscss-227ea22bb12c976aa6a59bf74084e181dee436f4.tar.gz
Several vast improvements to string parsing.
- Interpolation is now understood by the parser! It works for barewords, quoted strings, and both forms of URL. - Escaped characters are now understood and translated by the parser as well! - Rendering strings to CSS attempts to escape them correctly, regardless of how they were put together. - The block locator (at least the Python version) is now a little more aware of CSS escaping. Unfortunately there are some problems with the C module in this commit, which I will be promptly fixing. Conflicts: scss/blockast.py
Diffstat (limited to 'scss/types.py')
-rw-r--r--scss/types.py75
1 files changed, 67 insertions, 8 deletions
diff --git a/scss/types.py b/scss/types.py
index 5803b24..90faca1 100644
--- a/scss/types.py
+++ b/scss/types.py
@@ -6,6 +6,8 @@ from __future__ import unicode_literals
from collections import Iterable
import colorsys
import operator
+import re
+import string
from warnings import warn
import six
@@ -1006,6 +1008,8 @@ class String(Value):
sass_type_name = 'string'
+ bad_identifier_rx = re.compile('[^-_a-zA-Z\x80-\U0010FFFF]')
+
def __init__(self, value, quotes='"'):
if isinstance(value, String):
# TODO unclear if this should be here, but many functions rely on
@@ -1041,12 +1045,6 @@ class String(Value):
def __hash__(self):
return hash(self.value)
- def __str__(self):
- if self.quotes:
- return self.quotes + escape(self.value) + self.quotes
- else:
- return self.value
-
def __repr__(self):
if self.quotes:
quotes = '(' + self.quotes + ')'
@@ -1083,15 +1081,76 @@ class String(Value):
return String(self.value * int(other.value), quotes=self.quotes)
+ def _escape_character(self, match):
+ """Given a single character, return it appropriately CSS-escaped."""
+ # TODO is there any case where we'd want to use unicode escaping?
+ # TODO unsure if this works with newlines
+ return '\\' + match.group(0)
+
+ def _is_name_start(self, ch):
+ if ch == '_':
+ return True
+ if ord(ch) >= 128:
+ return True
+ if ch in string.ascii_letters:
+ return True
+ return False
+
def render(self, compress=False):
- return self.__str__()
+ # TODO should preserve original literals here too -- even the quotes.
+ # or at least that's what sass does.
+ # Escape and add quotes as appropriate.
+ if self.quotes is None:
+ return self._render_bareword()
+ else:
+ return self._render_quoted()
+
+ def _render_bareword(self):
+ # This is a bareword, so almost anything outside \w needs escaping
+ ret = self.value
+ ret = self.bad_identifier_rx.sub(self._escape_character, ret)
+
+ # Also apply some minor quibbling rules about how barewords can
+ # start: with a "name start", an escape, a hyphen followed by one
+ # of those, or two hyphens.
+ if ret[0] == '-':
+ if ret[1] in '-\\' or self._is_name_start(ret[1]):
+ pass
+ else:
+ # Escape the second character
+ # TODO what if it's a digit, oops
+ ret = ret[0] + '\\' + ret[1:]
+ elif ret[0] == '\\' or self._is_name_start(ret[0]):
+ pass
+ else:
+ # Escape the first character
+ # TODO what if it's a digit, oops
+ ret = '\\' + ret
+
+ return ret
+
+ def _render_quoted(self):
+ # Strictly speaking, the only things we need to quote are the quotes
+ # themselves, backslashes, and newlines.
+ # Note: We ignore the original quotes and always use double quotes, to
+ # match Ruby Sass's behavior. This isn't particularly well-specified,
+ # though.
+ quote = '"'
+ ret = self.value
+ ret = ret.replace('\\', '\\\\')
+ ret = ret.replace(quote, '\\' + quote)
+ # Note that a literal newline is ignored when escaped, so we have to
+ # use the codepoint instead. But we'll leave the newline as well, to
+ # aid readability.
+ ret = ret.replace('\n', '\\a\\\n')
+ return quote + ret + quote
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))
+ return "url({0})".format(super(Url, self).render(compress))
class Map(Value):