196
196
FLG_IN_RANGE = 0x80
197
197
FLG_OUT_OF_RANGE = 0x100
198
198
FLG_PLACEHOLDER_SHOWN = 0x200
199
+ FLG_FORGIVE = 0x400
199
200
200
201
# Maximum cached patterns to store
201
202
_MAXCACHE = 500
@@ -715,11 +716,14 @@ def parse_pseudo_open(self, sel, name, has_selector, iselector, index):
715
716
flags = FLG_PSEUDO | FLG_OPEN
716
717
if name == ':not' :
717
718
flags |= FLG_NOT
718
- if name == ':has' :
719
- flags |= FLG_RELATIVE
719
+ elif name == ':has' :
720
+ flags |= FLG_RELATIVE | FLG_FORGIVE
721
+ elif name in (':where' , ':is' ):
722
+ flags |= FLG_FORGIVE
720
723
721
724
sel .selectors .append (self .parse_selectors (iselector , index , flags ))
722
725
has_selector = True
726
+
723
727
return has_selector
724
728
725
729
def parse_has_combinator (self , sel , m , has_selector , selectors , rel_type , index ):
@@ -731,12 +735,9 @@ def parse_has_combinator(self, sel, m, has_selector, selectors, rel_type, index)
731
735
if combinator == COMMA_COMBINATOR :
732
736
if not has_selector :
733
737
# If we've not captured any selector parts, the comma is either at the beginning of the pattern
734
- # or following another comma, both of which are unexpected. Commas must split selectors.
735
- raise SelectorSyntaxError (
736
- "The combinator '{}' at postion {}, must have a selector before it" .format (combinator , index ),
737
- self .pattern ,
738
- index
739
- )
738
+ # or following another comma, both of which are unexpected. But shouldn't fail the pseudo-class.
739
+ sel .no_match = True
740
+
740
741
sel .rel_type = rel_type
741
742
selectors [- 1 ].relations .append (sel )
742
743
rel_type = ":" + WS_COMBINATOR
@@ -757,41 +758,50 @@ def parse_has_combinator(self, sel, m, has_selector, selectors, rel_type, index)
757
758
self .pattern ,
758
759
index
759
760
)
761
+
760
762
# Set the leading combinator for the next selector.
761
763
rel_type = ':' + combinator
762
- sel = _Selector ()
763
764
765
+ sel = _Selector ()
764
766
has_selector = False
765
767
return has_selector , sel , rel_type
766
768
767
- def parse_combinator (self , sel , m , has_selector , selectors , relations , is_pseudo , index ):
769
+ def parse_combinator (self , sel , m , has_selector , selectors , relations , is_pseudo , is_forgive , index ):
768
770
"""Parse combinator tokens."""
769
771
770
772
combinator = m .group ('relation' ).strip ()
771
773
if not combinator :
772
774
combinator = WS_COMBINATOR
773
775
if not has_selector :
774
- raise SelectorSyntaxError (
775
- "The combinator '{}' at postion {}, must have a selector before it" .format (combinator , index ),
776
- self .pattern ,
777
- index
778
- )
776
+ if not is_forgive or combinator != COMMA_COMBINATOR :
777
+ raise SelectorSyntaxError (
778
+ "The combinator '{}' at postion {}, must have a selector before it" .format (combinator , index ),
779
+ self .pattern ,
780
+ index
781
+ )
779
782
780
- if combinator == COMMA_COMBINATOR :
781
- if not sel .tag and not is_pseudo :
782
- # Implied `*`
783
- sel .tag = ct .SelectorTag ('*' , None )
784
- sel .relations .extend (relations )
785
- selectors .append (sel )
786
- del relations [:]
783
+ # If we are in a forgiving pseudo class, just make the selector a "no match"
784
+ if combinator == COMMA_COMBINATOR :
785
+ sel .no_match = True
786
+ del relations [:]
787
+ selectors .append (sel )
787
788
else :
788
- sel .relations .extend (relations )
789
- sel .rel_type = combinator
790
- del relations [:]
791
- relations .append (sel )
792
- sel = _Selector ()
789
+ if combinator == COMMA_COMBINATOR :
790
+ if not sel .tag and not is_pseudo :
791
+ # Implied `*`
792
+ sel .tag = ct .SelectorTag ('*' , None )
793
+ sel .relations .extend (relations )
794
+ selectors .append (sel )
795
+ del relations [:]
796
+ else :
797
+ sel .relations .extend (relations )
798
+ sel .rel_type = combinator
799
+ del relations [:]
800
+ relations .append (sel )
793
801
802
+ sel = _Selector ()
794
803
has_selector = False
804
+
795
805
return has_selector , sel
796
806
797
807
def parse_class_id (self , sel , m , has_selector ):
@@ -862,12 +872,15 @@ def parse_pseudo_dir(self, sel, m, has_selector):
862
872
def parse_selectors (self , iselector , index = 0 , flags = 0 ):
863
873
"""Parse selectors."""
864
874
875
+ # Initialize important variables
865
876
sel = _Selector ()
866
877
selectors = []
867
878
has_selector = False
868
879
closed = False
869
880
relations = []
870
881
rel_type = ":" + WS_COMBINATOR
882
+
883
+ # Setup various flags
871
884
is_open = bool (flags & FLG_OPEN )
872
885
is_pseudo = bool (flags & FLG_PSEUDO )
873
886
is_relative = bool (flags & FLG_RELATIVE )
@@ -878,7 +891,9 @@ def parse_selectors(self, iselector, index=0, flags=0):
878
891
is_in_range = bool (flags & FLG_IN_RANGE )
879
892
is_out_of_range = bool (flags & FLG_OUT_OF_RANGE )
880
893
is_placeholder_shown = bool (flags & FLG_PLACEHOLDER_SHOWN )
894
+ is_forgive = bool (flags & FLG_FORGIVE )
881
895
896
+ # Print out useful debug stuff
882
897
if self .debug : # pragma: no cover
883
898
if is_pseudo :
884
899
print (' is_pseudo: True' )
@@ -900,7 +915,10 @@ def parse_selectors(self, iselector, index=0, flags=0):
900
915
print (' is_out_of_range: True' )
901
916
if is_placeholder_shown :
902
917
print (' is_placeholder_shown: True' )
918
+ if is_forgive :
919
+ print (' is_forgive: True' )
903
920
921
+ # The algorithm for relative selectors require an initial selector in the selector list
904
922
if is_relative :
905
923
selectors .append (_Selector ())
906
924
@@ -929,11 +947,13 @@ def parse_selectors(self, iselector, index=0, flags=0):
929
947
is_html = True
930
948
elif key == 'pseudo_close' :
931
949
if not has_selector :
932
- raise SelectorSyntaxError (
933
- "Expected a selector at postion {}" .format (m .start (0 )),
934
- self .pattern ,
935
- m .start (0 )
936
- )
950
+ if not is_forgive :
951
+ raise SelectorSyntaxError (
952
+ "Expected a selector at postion {}" .format (m .start (0 )),
953
+ self .pattern ,
954
+ m .start (0 )
955
+ )
956
+ sel .no_match = True
937
957
if is_open :
938
958
closed = True
939
959
break
@@ -950,7 +970,7 @@ def parse_selectors(self, iselector, index=0, flags=0):
950
970
)
951
971
else :
952
972
has_selector , sel = self .parse_combinator (
953
- sel , m , has_selector , selectors , relations , is_pseudo , index
973
+ sel , m , has_selector , selectors , relations , is_pseudo , is_forgive , index
954
974
)
955
975
elif key == 'attribute' :
956
976
has_selector = self .parse_attribute_selector (sel , m , has_selector )
@@ -969,13 +989,15 @@ def parse_selectors(self, iselector, index=0, flags=0):
969
989
except StopIteration :
970
990
pass
971
991
992
+ # Handle selectors that are not closed
972
993
if is_open and not closed :
973
994
raise SelectorSyntaxError (
974
995
"Unclosed pseudo-class at position {}" .format (index ),
975
996
self .pattern ,
976
997
index
977
998
)
978
999
1000
+ # Cleanup completed selector piece
979
1001
if has_selector :
980
1002
if not sel .tag and not is_pseudo :
981
1003
# Implied `*`
@@ -987,8 +1009,28 @@ def parse_selectors(self, iselector, index=0, flags=0):
987
1009
sel .relations .extend (relations )
988
1010
del relations [:]
989
1011
selectors .append (sel )
990
- else :
1012
+
1013
+ # Forgive empty slots in pseudo-classes that have lists (and are forgiving)
1014
+ elif is_forgive :
1015
+ if is_relative :
1016
+ # Handle relative selectors pseudo-classes with empty slots like `:has()`
1017
+ if selectors and selectors [- 1 ].rel_type is None and rel_type == ': ' :
1018
+ sel .rel_type = rel_type
1019
+ sel .no_match = True
1020
+ selectors [- 1 ].relations .append (sel )
1021
+ has_selector = True
1022
+ else :
1023
+ # Handle normal pseudo-classes with empty slots
1024
+ if not selectors or not relations :
1025
+ # Others like `:is()` etc.
1026
+ sel .no_match = True
1027
+ del relations [:]
1028
+ selectors .append (sel )
1029
+ has_selector = True
1030
+
1031
+ if not has_selector :
991
1032
# We will always need to finish a selector when `:has()` is used as it leads with combining.
1033
+ # May apply to others as well.
992
1034
raise SelectorSyntaxError (
993
1035
'Expected a selector at position {}' .format (index ),
994
1036
self .pattern ,
@@ -1009,6 +1051,7 @@ def parse_selectors(self, iselector, index=0, flags=0):
1009
1051
if is_placeholder_shown :
1010
1052
selectors [- 1 ].flags = ct .SEL_PLACEHOLDER_SHOWN
1011
1053
1054
+ # Return selector list
1012
1055
return ct .SelectorList ([s .freeze () for s in selectors ], is_not , is_html )
1013
1056
1014
1057
def selector_iter (self , pattern ):
0 commit comments