Skip to content

Commit 2c15198

Browse files
author
annbgn
committed
support parsing complex selector in :not()
1 parent 52bbdd1 commit 2c15198

File tree

3 files changed

+30
-12
lines changed

3 files changed

+30
-12
lines changed

cssselect/parser.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,22 @@ class Negation(object):
238238
Represents selector:not(subselector)
239239
"""
240240

241-
def __init__(self, selector, subselector):
241+
def __init__(self, selector, subselector, combinator=None, subselector2=None):
242242
self.selector = selector
243243
self.subselector = subselector
244+
self.combinator = combinator
245+
self.subselector2 = subselector2
244246

245247
def __repr__(self):
246-
return "%s[%r:not(%r)]" % (self.__class__.__name__, self.selector, self.subselector)
248+
if self.combinator is None and self.subselector2 is None:
249+
return "%s[%r:not(%r)]" % (self.__class__.__name__, self.selector, self.subselector)
250+
return "%s[%r:not(%r %s %r)]" % (
251+
self.__class__.__name__,
252+
self.selector,
253+
self.subselector,
254+
self.combinator.value,
255+
self.subselector2.parsed_tree,
256+
)
247257

248258
def canonical(self):
249259
subsel = self.subselector.canonical()
@@ -614,9 +624,11 @@ def parse_simple_selector(stream, inside_negation=False):
614624
"Got pseudo-element ::%s inside :not() at %s"
615625
% (argument_pseudo_element, next.pos)
616626
)
627+
combinator = arguments = None
617628
if next != ("DELIM", ")"):
618-
raise SelectorSyntaxError("Expected ')', got %s" % (next,))
619-
result = Negation(result, argument)
629+
stream.skip_whitespace()
630+
combinator, arguments = parse_relative_selector(stream)
631+
result = Negation(result, argument, combinator, arguments)
620632
elif ident.lower() == "has":
621633
combinator, arguments = parse_relative_selector(stream)
622634
result = Relation(result, combinator, arguments)

cssselect/xpath.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -270,10 +270,17 @@ def xpath_combinedselector(self, combined):
270270
def xpath_negation(self, negation):
271271
xpath = self.xpath(negation.selector)
272272
sub_xpath = self.xpath(negation.subselector)
273-
sub_xpath.add_name_test()
274-
if sub_xpath.condition:
273+
if negation.combinator is not None and negation.subselector2 is not None:
274+
sub_xpath.add_condition(
275+
"name() != %s" % GenericTranslator.xpath_literal(sub_xpath.element)
276+
)
277+
sub2_xpath = self.xpath(negation.subselector2.parsed_tree)
278+
return sub2_xpath.add_condition("..[%s]" % sub_xpath.condition)
279+
elif sub_xpath.condition:
280+
sub_xpath.add_name_test()
275281
return xpath.add_condition("not(%s)" % sub_xpath.condition)
276282
else:
283+
sub_xpath.add_name_test()
277284
return xpath.add_condition("0")
278285

279286
def xpath_relation(self, relation):

tests/test_cssselect.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def parse_many(first, *others):
145145
assert parse_many("a:lang(fr)") == ["Function[Element[a]:lang(['fr'])]"]
146146
assert parse_many('div:contains("foo")') == ["Function[Element[div]:contains(['foo'])]"]
147147
assert parse_many("div#foobar") == ["Hash[Element[div]#foobar]"]
148+
assert parse_many(":not(a > b)") == ["Negation[Element[*]:not(Element[a] > Element[b])]"]
148149
assert parse_many("div:not(div.foo)") == [
149150
"Negation[Element[div]:not(Class[Element[div].foo])]"
150151
]
@@ -391,10 +392,8 @@ def get_error(css):
391392
assert get_error("> div p") == ("Expected selector, got <DELIM '>' at 0>")
392393

393394
# Unsupported :has() with several arguments
394-
assert get_error(':has(a, b)') == (
395-
"Expected an argument, got <DELIM ',' at 6>")
396-
assert get_error(':has()') == (
397-
"Expected selector, got <EOF at 0>")
395+
assert get_error(":has(a, b)") == ("Expected an argument, got <DELIM ',' at 6>")
396+
assert get_error(":has()") == ("Expected selector, got <EOF at 0>")
398397

399398
def test_translation(self):
400399
def xpath(css):
@@ -470,12 +469,12 @@ def xpath(css):
470469
assert xpath("e:EmPTY") == ("e[not(*) and not(string-length())]")
471470
assert xpath("e:root") == ("e[not(parent::*)]")
472471
assert xpath("e:hover") == ("e[0]") # never matches
472+
assert xpath("*:not(f > e)") == "e[..[name() != 'f']]" # select e whose parent is not f
473473
assert xpath("e:has(> f)") == "e[./f]"
474474
assert xpath("e:has(f)") == "e[descendant::f]"
475475
assert xpath("e:has(~ f)") == "e[following-sibling::f]"
476476
assert (
477-
xpath("e:has(+ f)")
478-
== "e[following-sibling::*[(name() = 'f') and (position() = 1)]]"
477+
xpath("e:has(+ f)") == "e[following-sibling::*[(name() = 'f') and (position() = 1)]]"
479478
)
480479
assert xpath('e:contains("foo")') == ("e[contains(., 'foo')]")
481480
assert xpath("e:ConTains(foo)") == ("e[contains(., 'foo')]")

0 commit comments

Comments
 (0)