Skip to content

Commit c416f4b

Browse files
author
annbgn
committed
add suppport for [of S]? part in nth-child's arguments
1 parent ffb931c commit c416f4b

File tree

3 files changed

+57
-7
lines changed

3 files changed

+57
-7
lines changed

cssselect/parser.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,27 @@ class Function(object):
182182
"""
183183
Represents selector:name(expr)
184184
"""
185-
def __init__(self, selector, name, arguments):
185+
186+
def __init__(self, selector, name, arguments, of_type=None):
186187
self.selector = selector
187188
self.name = ascii_lower(name)
188189
self.arguments = arguments
189190

191+
# for css4 :nth-child(An+B of Subselector)
192+
try:
193+
self.of_type = of_type[0]
194+
except (IndexError, TypeError):
195+
self.of_type = None
196+
190197
def __repr__(self):
198+
if self.of_type:
199+
return "%s[%r:%s(%r of %s)]" % (
200+
self.__class__.__name__,
201+
self.selector,
202+
self.name,
203+
[token.value for token in self.arguments],
204+
self.of_type.__repr__(),
205+
)
191206
return '%s[%r:%s(%r)]' % (
192207
self.__class__.__name__, self.selector, self.name,
193208
[token.value for token in self.arguments])
@@ -539,7 +554,8 @@ def parse_simple_selector(stream, inside_negation=False):
539554
raise SelectorSyntaxError("Expected ')', got %s" % (next,))
540555
result = Negation(result, argument)
541556
else:
542-
result = Function(result, ident, parse_arguments(stream))
557+
arguments, of_type = parse_arguments(stream)
558+
result = Function(result, ident, arguments, of_type)
543559
else:
544560
raise SelectorSyntaxError(
545561
"Expected selector, got %s" % (peek,))
@@ -554,16 +570,33 @@ def parse_arguments(stream):
554570
while 1:
555571
stream.skip_whitespace()
556572
next = stream.next()
557-
if next.type in ('IDENT', 'STRING', 'NUMBER') or next in [
558-
('DELIM', '+'), ('DELIM', '-')]:
573+
if next == ("IDENT", "of"):
574+
stream.skip_whitespace()
575+
of_type = parse_of_type(stream)
576+
return arguments, of_type
577+
elif next.type in ("IDENT", "STRING", "NUMBER") or next in [
578+
("DELIM", "+"),
579+
("DELIM", "-"),
580+
]:
559581
arguments.append(next)
560582
elif next == ('DELIM', ')'):
561-
return arguments
583+
return arguments, None
562584
else:
563585
raise SelectorSyntaxError(
564586
"Expected an argument, got %s" % (next,))
565587

566588

589+
def parse_of_type(stream):
590+
subselector = ""
591+
while 1:
592+
next = stream.next()
593+
if next == ("DELIM", ")"):
594+
break
595+
subselector += next.value
596+
result = parse(subselector)
597+
return result
598+
599+
567600
def parse_attrib(selector, stream):
568601
stream.skip_whitespace()
569602
attrib = stream.next_ident_or_star()
@@ -620,6 +653,7 @@ def parse_series(tokens):
620653
for token in tokens:
621654
if token.type == 'STRING':
622655
raise ValueError('String tokens not allowed in series.')
656+
623657
s = ''.join(token.value for token in tokens).strip()
624658
if s == 'odd':
625659
return 2, 1
@@ -630,7 +664,7 @@ def parse_series(tokens):
630664
if 'n' not in s:
631665
# Just b
632666
return 0, int(s)
633-
a, b = s.split('n', 1)
667+
a, b = s.split("n", 1)
634668
if not a:
635669
a = 1
636670
elif a == '-' or a == '+':
@@ -641,6 +675,7 @@ def parse_series(tokens):
641675
b = 0
642676
else:
643677
b = int(b)
678+
644679
return a, b
645680

646681

cssselect/xpath.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,9 @@ def xpath_nth_child_function(self, xpath, function, last=False,
439439
# `add_name_test` boolean is inverted and somewhat counter-intuitive:
440440
#
441441
# nth_of_type() calls nth_child(add_name_test=False)
442-
if add_name_test:
442+
if function.of_type:
443+
nodetest = self.xpath(function.of_type.parsed_tree)
444+
elif add_name_test:
443445
nodetest = '*'
444446
else:
445447
nodetest = '%s' % xpath.element

tests/test_cssselect.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,19 @@ def xpath(css):
406406
"@hreflang = 'en' or starts-with(@hreflang, 'en-'))]")
407407

408408
# --- nth-* and nth-last-* -------------------------------------
409+
assert (
410+
xpath("e:nth-child(2n+1 of S)")
411+
== "e[count(preceding-sibling::S) mod 2 = 0]"
412+
)
413+
assert (
414+
xpath("e:nth-of-type(2n+1 of S)")
415+
== "e[count(preceding-sibling::S) mod 2 = 0]"
416+
)
417+
assert (
418+
xpath('e:nth-child(2n+1 of li.important)')
419+
== "e[count(preceding-sibling::li[@class and contains(concat(' ', normalize-space(@class), ' '), ' important ')]) mod 2 = 0]"
420+
)
421+
409422
assert xpath('e:nth-child(1)') == (
410423
"e[count(preceding-sibling::*) = 0]")
411424

0 commit comments

Comments
 (0)