Skip to content

Commit

Permalink
Make pseudo-elements lower-case in the ASCII range.
Browse files Browse the repository at this point in the history
See http://www.w3.org/TR/selectors/#casesens

Pseudo-classes were already case-insensitive, but the
lower-casing was moved to the parser.
  • Loading branch information
SimonSapin committed Jun 14, 2012
1 parent 13023ed commit c192fcb
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 16 deletions.
14 changes: 11 additions & 3 deletions cssselect/parser.py
Expand Up @@ -25,6 +25,11 @@
_unichr = chr


def ascii_lower(string):
"""Lower-case, but only in the ASCII range."""
return string.encode('utf8').lower().decode('utf8')


class SelectorError(Exception):
"""Common parent for :class:`SelectorSyntaxError` and
:class:`ExpressionError`.
Expand Down Expand Up @@ -52,6 +57,8 @@ class Selector(object):
"""
def __init__(self, tree, pseudo_element=None):
self.parsed_tree = tree
if pseudo_element is not None:
pseudo_element = ascii_lower(pseudo_element)
#: The identifier for the pseudo-element as a string, or ``None``.
#:
#: +-------------------------+----------------+----------------+
Expand Down Expand Up @@ -114,7 +121,7 @@ class Function(object):
"""
def __init__(self, selector, name, arguments):
self.selector = selector
self.name = name
self.name = ascii_lower(name)
self.arguments = arguments

def __repr__(self):
Expand All @@ -137,7 +144,7 @@ class Pseudo(object):
"""
def __init__(self, selector, ident):
self.selector = selector
self.ident = ident
self.ident = ascii_lower(ident)

def __repr__(self):
return '%s[%r:%s]' % (
Expand Down Expand Up @@ -393,7 +400,8 @@ def parse_simple_selector(stream, inside_negation=False):
pseudo_element = stream.next_ident()
continue
ident = stream.next_ident()
if ident in ('first-line', 'first-letter', 'before', 'after'):
if ident.lower() in ('first-line', 'first-letter',
'before', 'after'):
# Special case: CSS 2.1 pseudo-elements can have a single ':'
# Any new pseudo-element must have two.
pseudo_element = _unicode(ident)
Expand Down
24 changes: 13 additions & 11 deletions cssselect/tests.py
Expand Up @@ -167,17 +167,17 @@ def parse_one(css):
assert parse_one(':empty') == ('Pseudo[Element[*]:empty]', None)

# Special cases for CSS 2.1 pseudo-elements
assert parse_one(':before') == ('Element[*]', 'before')
assert parse_one(':after') == ('Element[*]', 'after')
assert parse_one(':first-line') == ('Element[*]', 'first-line')
assert parse_one(':first-letter') == ('Element[*]', 'first-letter')
assert parse_one(':BEfore') == ('Element[*]', 'before')
assert parse_one(':aftER') == ('Element[*]', 'after')
assert parse_one(':First-Line') == ('Element[*]', 'first-line')
assert parse_one(':First-Letter') == ('Element[*]', 'first-letter')

assert parse_one('::before') == ('Element[*]', 'before')
assert parse_one('::after') == ('Element[*]', 'after')
assert parse_one('::first-line') == ('Element[*]', 'first-line')
assert parse_one('::first-letter') == ('Element[*]', 'first-letter')
assert parse_one('::befoRE') == ('Element[*]', 'before')
assert parse_one('::AFter') == ('Element[*]', 'after')
assert parse_one('::firsT-linE') == ('Element[*]', 'first-line')
assert parse_one('::firsT-letteR') == ('Element[*]', 'first-letter')

assert parse_one('::selection') == ('Element[*]', 'selection')
assert parse_one('::Selection') == ('Element[*]', 'selection')
assert parse_one('foo:after') == ('Element[foo]', 'after')
assert parse_one('foo::selection') == ('Element[foo]', 'selection')
assert parse_one('lorem#ipsum ~ a#b.c[href]:empty::selection') == (
Expand Down Expand Up @@ -346,13 +346,15 @@ def xpath(css):
"e[last() = 1]")
assert xpath('e:empty') == (
"e[not(*) and not(normalize-space())]")
assert xpath('e:EmPTY') == (
"e[not(*) and not(normalize-space())]")
assert xpath('e:root') == (
"e[not(parent::*)]")
assert xpath('e:hover') == (
"e[0]") # never matches
assert xpath('e:contains("foo")') == (
"e[contains(string(.), 'foo')]")
assert xpath('e:contains(foo)') == (
assert xpath('e:ConTains(foo)') == (
"e[contains(string(.), 'foo')]")
assert xpath('e.warning') == (
"e[@class and contains("
Expand All @@ -361,7 +363,7 @@ def xpath(css):
"e[@id = 'myid']")
assert xpath('e:not(:nth-child(odd))') == (
"e[not((position() -1) mod 2 = 0 and position() >= 1)]")
assert xpath('e:not(*)') == (
assert xpath('e:nOT(*)') == (
"e[0]") # never matches
assert xpath('e f') == (
"e/descendant-or-self::*/f")
Expand Down
4 changes: 2 additions & 2 deletions cssselect/xpath.py
Expand Up @@ -234,7 +234,7 @@ def xpath_negation(self, negation):

def xpath_function(self, function):
"""Translate a functional pseudo-class."""
method = 'xpath_%s_function' % function.name.replace('-', '_').lower()
method = 'xpath_%s_function' % function.name.replace('-', '_')
method = getattr(self, method, None)
if not method:
raise ExpressionError(
Expand All @@ -243,7 +243,7 @@ def xpath_function(self, function):

def xpath_pseudo(self, pseudo):
"""Translate a pseudo-class."""
method = 'xpath_%s_pseudo' % pseudo.ident.replace('-', '_').lower()
method = 'xpath_%s_pseudo' % pseudo.ident.replace('-', '_')
method = getattr(self, method, None)
if not method:
# TODO: better error message for pseudo-elements?
Expand Down

0 comments on commit c192fcb

Please sign in to comment.