Skip to content

Commit 7bcc7e0

Browse files
author
annbgn
committed
expand tests on relative selectors
1 parent c4ef8c8 commit 7bcc7e0

File tree

3 files changed

+46
-14
lines changed

3 files changed

+46
-14
lines changed

cssselect/parser.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ def canonical(self):
270270

271271
def specificity(self):
272272
a1, b1, c1 = self.selector.specificity()
273-
a2, b2, c2 = self.subselector.specificity()
273+
a2 = b2 = c2 = 0
274+
if self.subselector:
275+
a2, b2, c2 = self.subselector[-1].specificity()
274276
return a1 + a2, b1 + b2, c1 + c2
275277

276278

cssselect/xpath.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import sys
1616
import re
17+
import copy
1718

1819
from cssselect.parser import parse, parse_series, SelectorError
1920

@@ -76,13 +77,13 @@ def add_star_prefix(self):
7677
"""
7778
self.path += '*/'
7879

79-
def join(self, combiner, other):
80+
def join(self, combiner, other, closing_combiner=None):
8081
path = _unicode(self) + combiner
8182
# Any "star prefix" is redundant when joining.
8283
if other.path != '*/':
8384
path += other.path
8485
self.path = path
85-
self.element = other.element
86+
self.element = other.element + closing_combiner if closing_combiner else other.element
8687
self.condition = other.condition
8788
return self
8889

@@ -274,9 +275,14 @@ def xpath_negation(self, negation):
274275

275276
def xpath_relation(self, relation):
276277
xpath = self.xpath(relation.selector)
277-
combinator, subselector = relation.subselector
278-
method = getattr(self, 'xpath_%s_combinator' % self.combinator_mapping[combinator.value])
279-
return method(xpath, self.xpath(subselector))
278+
combinator, *subselector = relation.subselector
279+
if not subselector:
280+
combinator.value = ' '
281+
right = self.xpath(combinator)
282+
else:
283+
right = self.xpath(subselector[0])
284+
method = getattr(self, 'xpath_relation_%s_combinator' % self.combinator_mapping[combinator.value])
285+
return method(xpath, right)
280286

281287
def xpath_function(self, function):
282288
"""Translate a functional pseudo-class."""
@@ -375,6 +381,29 @@ def xpath_indirect_adjacent_combinator(self, left, right):
375381
"""right is a sibling after left, immediately or not"""
376382
return left.join('/following-sibling::', right)
377383

384+
def xpath_relation_descendant_combinator(self, left, right):
385+
"""right is a child, grand-child or further descendant of left; select left"""
386+
return left.join('/descendant-or-self::', right, closing_combiner='/ancestor-or-self::' + left.element)
387+
388+
def xpath_relation_child_combinator(self, left, right):
389+
"""right is an immediate child of left; select left"""
390+
return left.join('[./', right, closing_combiner=']')
391+
392+
def xpath_relation_direct_adjacent_combinator(self, left, right):
393+
"""right is a sibling immediately after left; select left"""
394+
left_copy = copy.copy(left)
395+
xpath = left.join('/following-sibling::', right)
396+
xpath.add_name_test()
397+
xpath.add_condition('position() = 1')
398+
399+
xpath = xpath.join('/preceding-sibling::', left_copy)
400+
xpath.add_name_test()
401+
return xpath.add_condition('position() = 1')
402+
403+
def xpath_relation_indirect_adjacent_combinator(self, left, right):
404+
"""right is a sibling after left, immediately or not; select left"""
405+
return left.join('/following-sibling::', right, closing_combiner='/preceding-sibling::'+left.element)
406+
378407

379408
# Function: dispatch by function/pseudo-class name
380409

tests/test_cssselect.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,10 @@ def specificity(css):
266266
assert specificity(':not(:empty)') == (0, 1, 0)
267267
assert specificity(':not(#foo)') == (1, 0, 0)
268268

269-
# assert specificity(':has(*)') == (0, 0, 0)
270-
# assert specificity(':has(foo)') == (0, 0, 1)
271-
# assert specificity(':has(.foo)') == (0, 1, 0)
272-
# assert specificity(':has([foo])') == (0, 1, 0)
273-
# assert specificity(':has(:empty)') == (0, 1, 0)
274-
# assert specificity(':has(#foo)') == (1, 0, 0)
269+
assert specificity(':has(*)') == (0, 0, 0)
270+
assert specificity(':has(foo)') == (0, 0, 1)
271+
assert specificity(':has(> foo)') == (0, 0, 1)
272+
275273

276274
assert specificity('foo:empty') == (0, 1, 1)
277275
assert specificity('foo:before') == (0, 0, 2)
@@ -504,8 +502,11 @@ def xpath(css):
504502
assert xpath('e:not(:nth-child(odd))') == (
505503
"e[not(count(preceding-sibling::*) mod 2 = 0)]")
506504
assert xpath('e:nOT(*)') == (
507-
"e[0]") # never matches
508-
assert xpath('e:has(> f)') == 'e/f'
505+
"e[0]") # never matches
506+
assert xpath('e:has(> f)') == 'e[./f]'
507+
assert xpath('e:has(f)') == 'e/descendant-or-self::f/ancestor-or-self::e'
508+
assert xpath('e:has(~ f)') == 'e/following-sibling::f/preceding-sibling::e'
509+
assert xpath('e:has(+ f)') == "e/following-sibling::*[(name() = 'f') and (position() = 1)]/preceding-sibling::*[(name() = 'e') and (position() = 1)]"
509510
assert xpath('e f') == (
510511
"e/descendant-or-self::*/f")
511512
assert xpath('e > f') == (

0 commit comments

Comments
 (0)