@@ -250,6 +250,30 @@ def specificity(self):
250
250
return a1 + a2 , b1 + b2 , c1 + c2
251
251
252
252
253
+ class Relation (object ):
254
+ """
255
+ Represents selector:has(subselector)
256
+ """
257
+ def __init__ (self , selector , subselector ):
258
+ self .selector = selector
259
+ self .subselector = subselector
260
+
261
+ def __repr__ (self ):
262
+ return '%s[%r:has(%r)]' % (
263
+ self .__class__ .__name__ , self .selector , self .subselector )
264
+
265
+ def canonical (self ):
266
+ subsel = self .subselector .canonical ()
267
+ if len (subsel ) > 1 :
268
+ subsel = subsel .lstrip ('*' )
269
+ return '%s:has(%s)' % (self .selector .canonical (), subsel )
270
+
271
+ def specificity (self ):
272
+ a1 , b1 , c1 = self .selector .specificity ()
273
+ a2 , b2 , c2 = self .subselector .specificity ()
274
+ return a1 + a2 , b1 + b2 , c1 + c2
275
+
276
+
253
277
class Attrib (object ):
254
278
"""
255
279
Represents selector[namespace|attrib operator value]
@@ -456,7 +480,7 @@ def parse_selector(stream):
456
480
return result , pseudo_element
457
481
458
482
459
- def parse_simple_selector (stream , inside_negation = False ):
483
+ def parse_simple_selector (stream , nestable = True ):
460
484
stream .skip_whitespace ()
461
485
selector_start = len (stream .used )
462
486
peek = stream .peek ()
@@ -479,7 +503,7 @@ def parse_simple_selector(stream, inside_negation=False):
479
503
while 1 :
480
504
peek = stream .peek ()
481
505
if peek .type in ('S' , 'EOF' ) or peek .is_delim (',' , '+' , '>' , '~' ) or (
482
- inside_negation and peek == ('DELIM' , ')' )):
506
+ not nestable and peek == ('DELIM' , ')' )):
483
507
break
484
508
if pseudo_element :
485
509
raise SelectorSyntaxError (
@@ -507,7 +531,8 @@ def parse_simple_selector(stream, inside_negation=False):
507
531
pseudo_element , parse_arguments (stream ))
508
532
continue
509
533
ident = stream .next_ident ()
510
- if ident .lower () in ('first-line' , 'first-letter' ,
534
+ lowercase_indent = ident .lower ()
535
+ if lowercase_indent in ('first-line' , 'first-letter' ,
511
536
'before' , 'after' ):
512
537
# Special case: CSS 2.1 pseudo-elements can have a single ':'
513
538
# Any new pseudo-element must have two.
@@ -523,13 +548,16 @@ def parse_simple_selector(stream, inside_negation=False):
523
548
'Got immediate child pseudo-element ":scope" '
524
549
'not at the start of a selector' )
525
550
continue
551
+
526
552
stream .next ()
527
553
stream .skip_whitespace ()
528
- if ident .lower () == 'not' :
529
- if inside_negation :
530
- raise SelectorSyntaxError ('Got nested :not()' )
554
+
555
+ if lowercase_indent == 'not' :
556
+ if not nestable :
557
+ raise SelectorSyntaxError (
558
+ 'Got :not() within :has() or another :not()' )
531
559
argument , argument_pseudo_element = parse_simple_selector (
532
- stream , inside_negation = True )
560
+ stream , nestable = False )
533
561
next = stream .next ()
534
562
if argument_pseudo_element :
535
563
raise SelectorSyntaxError (
@@ -538,8 +566,25 @@ def parse_simple_selector(stream, inside_negation=False):
538
566
if next != ('DELIM' , ')' ):
539
567
raise SelectorSyntaxError ("Expected ')', got %s" % (next ,))
540
568
result = Negation (result , argument )
541
- else :
542
- result = Function (result , ident , parse_arguments (stream ))
569
+ continue
570
+
571
+ if lowercase_indent == 'has' :
572
+ if not nestable :
573
+ raise SelectorSyntaxError (
574
+ 'Got :has() within :not() or another :has()' )
575
+ argument , argument_pseudo_element = parse_simple_selector (
576
+ stream , nestable = False )
577
+ next = stream .next ()
578
+ if argument_pseudo_element :
579
+ raise SelectorSyntaxError (
580
+ 'Got pseudo-element ::%s inside :has() at %s'
581
+ % (argument_pseudo_element , next .pos ))
582
+ if next != ('DELIM' , ')' ):
583
+ raise SelectorSyntaxError ("Expected ')', got %s" % (next ,))
584
+ result = Relation (result , argument )
585
+ continue
586
+
587
+ result = Function (result , ident , parse_arguments (stream ))
543
588
else :
544
589
raise SelectorSyntaxError (
545
590
"Expected selector, got %s" % (peek ,))
0 commit comments