How to use has_selector method in autotest

Best Python code snippet using autotest_python

css_parser.py

Source:css_parser.py Github

copy

Full Screen

1"""CSS selector parser."""2from __future__ import unicode_literals3import re4from . import util5from . import css_match as cm6from . import css_types as ct7from .util import SelectorSyntaxError8UNICODE_REPLACEMENT_CHAR = 0xFFFD9# Simple pseudo classes that take no parameters10PSEUDO_SIMPLE = {11 ":any-link",12 ":empty",13 ":first-child",14 ":first-of-type",15 ":in-range",16 ":out-of-range",17 ":last-child",18 ":last-of-type",19 ":link",20 ":only-child",21 ":only-of-type",22 ":root",23 ':checked',24 ':default',25 ':disabled',26 ':enabled',27 ':indeterminate',28 ':optional',29 ':placeholder-shown',30 ':read-only',31 ':read-write',32 ':required',33 ':scope',34 ':defined'35}36# Supported, simple pseudo classes that match nothing in the Soup Sieve environment37PSEUDO_SIMPLE_NO_MATCH = {38 ':active',39 ':current',40 ':focus',41 ':focus-visible',42 ':focus-within',43 ':future',44 ':host',45 ':hover',46 ':local-link',47 ':past',48 ':paused',49 ':playing',50 ':target',51 ':target-within',52 ':user-invalid',53 ':visited'54}55# Complex pseudo classes that take selector lists56PSEUDO_COMPLEX = {57 ':contains',58 ':has',59 ':is',60 ':matches',61 ':not',62 ':where'63}64PSEUDO_COMPLEX_NO_MATCH = {65 ':current',66 ':host',67 ':host-context'68}69# Complex pseudo classes that take very specific parameters and are handled special70PSEUDO_SPECIAL = {71 ':dir',72 ':lang',73 ':nth-child',74 ':nth-last-child',75 ':nth-last-of-type',76 ':nth-of-type'77}78PSEUDO_SUPPORTED = PSEUDO_SIMPLE | PSEUDO_SIMPLE_NO_MATCH | PSEUDO_COMPLEX | PSEUDO_COMPLEX_NO_MATCH | PSEUDO_SPECIAL79# Sub-patterns parts80# Whitespace81NEWLINE = r'(?:\r\n|(?!\r\n)[\n\f\r])'82WS = r'(?:[ \t]|{})'.format(NEWLINE)83# Comments84COMMENTS = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'85# Whitespace with comments included86WSC = r'(?:{ws}|{comments})'.format(ws=WS, comments=COMMENTS)87# CSS escapes88CSS_ESCAPES = r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$))'.format(ws=WS)89CSS_STRING_ESCAPES = r'(?:\\(?:[a-f0-9]{{1,6}}{ws}?|[^\r\n\f]|$|{nl}))'.format(ws=WS, nl=NEWLINE)90# CSS Identifier91IDENTIFIER = r'''92(?:(?:-?(?:[^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f]|{esc})+|--)93(?:[^\x00-\x2c\x2e\x2f\x3A-\x40\x5B-\x5E\x60\x7B-\x9f]|{esc})*)94'''.format(esc=CSS_ESCAPES)95# `nth` content96NTH = r'(?:[-+])?(?:[0-9]+n?|n)(?:(?<=n){ws}*(?:[-+]){ws}*(?:[0-9]+))?'.format(ws=WSC)97# Value: quoted string or identifier98VALUE = r'''99(?:"(?:\\(?:.|{nl})|[^\\"\r\n\f]+)*?"|'(?:\\(?:.|{nl})|[^\\'\r\n\f]+)*?'|{ident}+)100'''.format(nl=NEWLINE, ident=IDENTIFIER)101# Attribute value comparison. `!=` is handled special as it is non-standard.102ATTR = r'''103(?:{ws}*(?P<cmp>[!~^|*$]?=){ws}*(?P<value>{value})(?:{ws}+(?P<case>[is]))?)?{ws}*\]104'''.format(ws=WSC, value=VALUE)105# Selector patterns106# IDs (`#id`)107PAT_ID = r'\#{ident}'.format(ident=IDENTIFIER)108# Classes (`.class`)109PAT_CLASS = r'\.{ident}'.format(ident=IDENTIFIER)110# Prefix:Tag (`prefix|tag`)111PAT_TAG = r'(?:(?:{ident}|\*)?\|)?(?:{ident}|\*)'.format(ident=IDENTIFIER)112# Attributes (`[attr]`, `[attr=value]`, etc.)113PAT_ATTR = r'\[{ws}*(?P<ns_attr>(?:(?:{ident}|\*)?\|)?{ident}){attr}'.format(ws=WSC, ident=IDENTIFIER, attr=ATTR)114# Pseudo class (`:pseudo-class`, `:pseudo-class(`)115PAT_PSEUDO_CLASS = r'(?P<name>:{ident})(?P<open>\({ws}*)?'.format(ws=WSC, ident=IDENTIFIER)116# Pseudo class special patterns. Matches `:pseudo-class(` for special case pseudo classes.117PAT_PSEUDO_CLASS_SPECIAL = r'(?P<name>:{ident})(?P<open>\({ws}*)'.format(ws=WSC, ident=IDENTIFIER)118# Custom pseudo class (`:--custom-pseudo`)119PAT_PSEUDO_CLASS_CUSTOM = r'(?P<name>:(?=--){ident})'.format(ident=IDENTIFIER)120# Closing pseudo group (`)`)121PAT_PSEUDO_CLOSE = r'{ws}*\)'.format(ws=WSC)122# Pseudo element (`::pseudo-element`)123PAT_PSEUDO_ELEMENT = r':{}'.format(PAT_PSEUDO_CLASS)124# At rule (`@page`, etc.) (not supported)125PAT_AT_RULE = r'@P{ident}'.format(ident=IDENTIFIER)126# Pseudo class `nth-child` (`:nth-child(an+b [of S]?)`, `:first-child`, etc.)127PAT_PSEUDO_NTH_CHILD = r'''128(?P<pseudo_nth_child>{name}129(?P<nth_child>{nth}|even|odd))(?:{wsc}*\)|(?P<of>{comments}*{ws}{wsc}*of{comments}*{ws}{wsc}*))130'''.format(name=PAT_PSEUDO_CLASS_SPECIAL, wsc=WSC, comments=COMMENTS, ws=WS, nth=NTH)131# Pseudo class `nth-of-type` (`:nth-of-type(an+b)`, `:first-of-type`, etc.)132PAT_PSEUDO_NTH_TYPE = r'''133(?P<pseudo_nth_type>{name}134(?P<nth_type>{nth}|even|odd)){ws}*\)135'''.format(name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC, nth=NTH)136# Pseudo class language (`:lang("*-de", en)`)137PAT_PSEUDO_LANG = r'{name}(?P<values>{value}(?:{ws}*,{ws}*{value})*){ws}*\)'.format(138 name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC, value=VALUE139)140# Pseudo class direction (`:dir(ltr)`)141PAT_PSEUDO_DIR = r'{name}(?P<dir>ltr|rtl){ws}*\)'.format(name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC)142# Combining characters (`>`, `~`, ` `, `+`, `,`)143PAT_COMBINE = r'{wsc}*?(?P<relation>[,+>~]|{ws}(?![,+>~])){wsc}*'.format(ws=WS, wsc=WSC)144# Extra: Contains (`:contains(text)`)145PAT_PSEUDO_CONTAINS = r'{name}(?P<values>{value}(?:{ws}*,{ws}*{value})*){ws}*\)'.format(146 name=PAT_PSEUDO_CLASS_SPECIAL, ws=WSC, value=VALUE147)148# Regular expressions149# CSS escape pattern150RE_CSS_ESC = re.compile(r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$))'.format(ws=WSC), re.I)151RE_CSS_STR_ESC = re.compile(152 r'(?:(\\[a-f0-9]{{1,6}}{ws}?)|(\\[^\r\n\f])|(\\$)|(\\{nl}))'.format(ws=WS, nl=NEWLINE), re.I153)154# Pattern to break up `nth` specifiers155RE_NTH = re.compile(156 r'(?P<s1>[-+])?(?P<a>[0-9]+n?|n)(?:(?<=n){ws}*(?P<s2>[-+]){ws}*(?P<b>[0-9]+))?'.format(ws=WSC),157 re.I158)159# Pattern to iterate multiple values.160RE_VALUES = re.compile(r'(?:(?P<value>{value})|(?P<split>{ws}*,{ws}*))'.format(ws=WSC, value=VALUE), re.X)161# Whitespace checks162RE_WS = re.compile(WS)163RE_WS_BEGIN = re.compile('^{}*'.format(WSC))164RE_WS_END = re.compile('{}*$'.format(WSC))165RE_CUSTOM = re.compile(r'^{}$'.format(PAT_PSEUDO_CLASS_CUSTOM), re.X)166# Constants167# List split token168COMMA_COMBINATOR = ','169# Relation token for descendant170WS_COMBINATOR = " "171# Parse flags172FLG_PSEUDO = 0x01173FLG_NOT = 0x02174FLG_RELATIVE = 0x04175FLG_DEFAULT = 0x08176FLG_HTML = 0x10177FLG_INDETERMINATE = 0x20178FLG_OPEN = 0x40179FLG_IN_RANGE = 0x80180FLG_OUT_OF_RANGE = 0x100181# Maximum cached patterns to store182_MAXCACHE = 500183@util.lru_cache(maxsize=_MAXCACHE)184def _cached_css_compile(pattern, namespaces, custom, flags):185 """Cached CSS compile."""186 custom_selectors = process_custom(custom)187 return cm.SoupSieve(188 pattern,189 CSSParser(pattern, custom=custom_selectors, flags=flags).process_selectors(),190 namespaces,191 custom,192 flags193 )194def _purge_cache():195 """Purge the cache."""196 _cached_css_compile.cache_clear()197def process_custom(custom):198 """Process custom."""199 custom_selectors = {}200 if custom is not None:201 for key, value in custom.items():202 name = util.lower(key)203 if RE_CUSTOM.match(name) is None:204 raise SelectorSyntaxError("The name '{}' is not a valid custom pseudo-class name".format(name))205 if name in custom_selectors:206 raise KeyError("The custom selector '{}' has already been registered".format(name))207 custom_selectors[css_unescape(name)] = value208 return custom_selectors209def css_unescape(content, string=False):210 """211 Unescape CSS value.212 Strings allow for spanning the value on multiple strings by escaping a new line.213 """214 def replace(m):215 """Replace with the appropriate substitute."""216 if m.group(1):217 codepoint = int(m.group(1)[1:], 16)218 if codepoint == 0:219 codepoint = UNICODE_REPLACEMENT_CHAR220 value = util.uchr(codepoint)221 elif m.group(2):222 value = m.group(2)[1:]223 elif m.group(3):224 value = '\ufffd'225 else:226 value = ''227 return value228 return (RE_CSS_ESC if not string else RE_CSS_STR_ESC).sub(replace, content)229def escape(ident):230 """Escape identifier."""231 string = []232 length = len(ident)233 start_dash = length > 0 and ident[0] == '-'234 if length == 1 and start_dash:235 # Need to escape identifier that is a single `-` with no other characters236 string.append('\\{}'.format(ident))237 else:238 for index, c in enumerate(ident):239 codepoint = util.uord(c)240 if codepoint == 0x00:241 string.append('\ufffd')242 elif (0x01 <= codepoint <= 0x1F) or codepoint == 0x7F:243 string.append('\\{:x} '.format(codepoint))244 elif (index == 0 or (start_dash and index == 1)) and (0x30 <= codepoint <= 0x39):245 string.append('\\{:x} '.format(codepoint))246 elif (247 codepoint in (0x2D, 0x5F) or codepoint >= 0x80 or (0x30 <= codepoint <= 0x39) or248 (0x30 <= codepoint <= 0x39) or (0x41 <= codepoint <= 0x5A) or (0x61 <= codepoint <= 0x7A)249 ):250 string.append(c)251 else:252 string.append('\\{}'.format(c))253 return ''.join(string)254class SelectorPattern(object):255 """Selector pattern."""256 def __init__(self, name, pattern):257 """Initialize."""258 self.name = name259 self.re_pattern = re.compile(pattern, re.I | re.X | re.U)260 def get_name(self):261 """Get name."""262 return self.name263 def enabled(self, flags):264 """Enabled."""265 return True266 def match(self, selector, index):267 """Match the selector."""268 return self.re_pattern.match(selector, index)269class SpecialPseudoPattern(SelectorPattern):270 """Selector pattern."""271 def __init__(self, patterns):272 """Initialize."""273 self.patterns = {}274 for p in patterns:275 name = p[0]276 pattern = SelectorPattern(name, p[2])277 for pseudo in p[1]:278 self.patterns[pseudo] = pattern279 self.matched_name = None280 self.re_pseudo_name = re.compile(PAT_PSEUDO_CLASS_SPECIAL, re.I | re.X | re.U)281 def get_name(self):282 """Get name."""283 return self.matched_name.get_name()284 def enabled(self, flags):285 """Enabled."""286 return True287 def match(self, selector, index):288 """Match the selector."""289 pseudo = None290 m = self.re_pseudo_name.match(selector, index)291 if m:292 name = util.lower(css_unescape(m.group('name')))293 pattern = self.patterns.get(name)294 if pattern:295 pseudo = pattern.match(selector, index)296 if pseudo:297 self.matched_name = pattern298 return pseudo299class _Selector(object):300 """301 Intermediate selector class.302 This stores selector data for a compound selector as we are acquiring them.303 Once we are done collecting the data for a compound selector, we freeze304 the data in an object that can be pickled and hashed.305 """306 def __init__(self, **kwargs):307 """Initialize."""308 self.tag = kwargs.get('tag', None)309 self.ids = kwargs.get('ids', [])310 self.classes = kwargs.get('classes', [])311 self.attributes = kwargs.get('attributes', [])312 self.nth = kwargs.get('nth', [])313 self.selectors = kwargs.get('selectors', [])314 self.relations = kwargs.get('relations', [])315 self.rel_type = kwargs.get('rel_type', None)316 self.contains = kwargs.get('contains', [])317 self.lang = kwargs.get('lang', [])318 self.flags = kwargs.get('flags', 0)319 self.no_match = kwargs.get('no_match', False)320 def _freeze_relations(self, relations):321 """Freeze relation."""322 if relations:323 sel = relations[0]324 sel.relations.extend(relations[1:])325 return ct.SelectorList([sel.freeze()])326 else:327 return ct.SelectorList()328 def freeze(self):329 """Freeze self."""330 if self.no_match:331 return ct.SelectorNull()332 else:333 return ct.Selector(334 self.tag,335 tuple(self.ids),336 tuple(self.classes),337 tuple(self.attributes),338 tuple(self.nth),339 tuple(self.selectors),340 self._freeze_relations(self.relations),341 self.rel_type,342 tuple(self.contains),343 tuple(self.lang),344 self.flags345 )346 def __str__(self): # pragma: no cover347 """String representation."""348 return (349 '_Selector(tag={!r}, ids={!r}, classes={!r}, attributes={!r}, nth={!r}, selectors={!r}, '350 'relations={!r}, rel_type={!r}, contains={!r}, lang={!r}, flags={!r}, no_match={!r})'351 ).format(352 self.tag, self.ids, self.classes, self.attributes, self.nth, self.selectors,353 self.relations, self.rel_type, self.contains, self.lang, self.flags, self.no_match354 )355 __repr__ = __str__356class CSSParser(object):357 """Parse CSS selectors."""358 css_tokens = (359 SelectorPattern("pseudo_close", PAT_PSEUDO_CLOSE),360 SpecialPseudoPattern(361 (362 ("pseudo_contains", (':contains',), PAT_PSEUDO_CONTAINS),363 ("pseudo_nth_child", (':nth-child', ':nth-last-child'), PAT_PSEUDO_NTH_CHILD),364 ("pseudo_nth_type", (':nth-of-type', ':nth-last-of-type'), PAT_PSEUDO_NTH_TYPE),365 ("pseudo_lang", (':lang',), PAT_PSEUDO_LANG),366 ("pseudo_dir", (':dir',), PAT_PSEUDO_DIR)367 )368 ),369 SelectorPattern("pseudo_class_custom", PAT_PSEUDO_CLASS_CUSTOM),370 SelectorPattern("pseudo_class", PAT_PSEUDO_CLASS),371 SelectorPattern("pseudo_element", PAT_PSEUDO_ELEMENT),372 SelectorPattern("at_rule", PAT_AT_RULE),373 SelectorPattern("id", PAT_ID),374 SelectorPattern("class", PAT_CLASS),375 SelectorPattern("tag", PAT_TAG),376 SelectorPattern("attribute", PAT_ATTR),377 SelectorPattern("combine", PAT_COMBINE)378 )379 def __init__(self, selector, custom=None, flags=0):380 """Initialize."""381 self.pattern = selector.replace('\x00', '\ufffd')382 self.flags = flags383 self.debug = self.flags & util.DEBUG384 self.custom = {} if custom is None else custom385 def parse_attribute_selector(self, sel, m, has_selector):386 """Create attribute selector from the returned regex match."""387 inverse = False388 op = m.group('cmp')389 case = util.lower(m.group('case')) if m.group('case') else None390 parts = [css_unescape(a) for a in m.group('ns_attr').split('|')]391 ns = ''392 is_type = False393 pattern2 = None394 if len(parts) > 1:395 ns = parts[0]396 attr = parts[1]397 else:398 attr = parts[0]399 if case:400 flags = re.I if case == 'i' else 0401 elif util.lower(attr) == 'type':402 flags = re.I403 is_type = True404 else:405 flags = 0406 if op:407 if m.group('value').startswith(('"', "'")):408 value = css_unescape(m.group('value')[1:-1], True)409 else:410 value = css_unescape(m.group('value'))411 else:412 value = None413 if not op:414 # Attribute name415 pattern = None416 elif op.startswith('^'):417 # Value start with418 pattern = re.compile(r'^%s.*' % re.escape(value), flags)419 elif op.startswith('$'):420 # Value ends with421 pattern = re.compile(r'.*?%s$' % re.escape(value), flags)422 elif op.startswith('*'):423 # Value contains424 pattern = re.compile(r'.*?%s.*' % re.escape(value), flags)425 elif op.startswith('~'):426 # Value contains word within space separated list427 # `~=` should match nothing if it is empty or contains whitespace,428 # so if either of these cases is present, use `[^\s\S]` which cannot be matched.429 value = r'[^\s\S]' if not value or RE_WS.search(value) else re.escape(value)430 pattern = re.compile(r'.*?(?:(?<=^)|(?<=[ \t\r\n\f]))%s(?=(?:[ \t\r\n\f]|$)).*' % value, flags)431 elif op.startswith('|'):432 # Value starts with word in dash separated list433 pattern = re.compile(r'^%s(?:-.*)?$' % re.escape(value), flags)434 else:435 # Value matches436 pattern = re.compile(r'^%s$' % re.escape(value), flags)437 if op.startswith('!'):438 # Equivalent to `:not([attr=value])`439 inverse = True440 if is_type and pattern:441 pattern2 = re.compile(pattern.pattern)442 # Append the attribute selector443 sel_attr = ct.SelectorAttribute(attr, ns, pattern, pattern2)444 if inverse:445 # If we are using `!=`, we need to nest the pattern under a `:not()`.446 sub_sel = _Selector()447 sub_sel.attributes.append(sel_attr)448 not_list = ct.SelectorList([sub_sel.freeze()], True, False)449 sel.selectors.append(not_list)450 else:451 sel.attributes.append(sel_attr)452 has_selector = True453 return has_selector454 def parse_tag_pattern(self, sel, m, has_selector):455 """Parse tag pattern from regex match."""456 parts = [css_unescape(x) for x in m.group(0).split('|')]457 if len(parts) > 1:458 prefix = parts[0]459 tag = parts[1]460 else:461 tag = parts[0]462 prefix = None463 sel.tag = ct.SelectorTag(tag, prefix)464 has_selector = True465 return has_selector466 def parse_pseudo_class_custom(self, sel, m, has_selector):467 """468 Parse custom pseudo class alias.469 Compile custom selectors as we need them. When compiling a custom selector,470 set it to `None` in the dictionary so we can avoid an infinite loop.471 """472 pseudo = util.lower(css_unescape(m.group('name')))473 selector = self.custom.get(pseudo)474 if selector is None:475 raise SelectorSyntaxError(476 "Undefined custom selector '{}' found at postion {}".format(pseudo, m.end(0)),477 self.pattern,478 m.end(0)479 )480 if not isinstance(selector, ct.SelectorList):481 self.custom[pseudo] = None482 selector = CSSParser(483 selector, custom=self.custom, flags=self.flags484 ).process_selectors(flags=FLG_PSEUDO)485 self.custom[pseudo] = selector486 sel.selectors.append(selector)487 has_selector = True488 return has_selector489 def parse_pseudo_class(self, sel, m, has_selector, iselector, is_html):490 """Parse pseudo class."""491 complex_pseudo = False492 pseudo = util.lower(css_unescape(m.group('name')))493 if m.group('open'):494 complex_pseudo = True495 if complex_pseudo and pseudo in PSEUDO_COMPLEX:496 has_selector = self.parse_pseudo_open(sel, pseudo, has_selector, iselector, m.end(0))497 elif not complex_pseudo and pseudo in PSEUDO_SIMPLE:498 if pseudo == ':root':499 sel.flags |= ct.SEL_ROOT500 elif pseudo == ':defined':501 sel.flags |= ct.SEL_DEFINED502 is_html = True503 elif pseudo == ':scope':504 sel.flags |= ct.SEL_SCOPE505 elif pseudo == ':empty':506 sel.flags |= ct.SEL_EMPTY507 elif pseudo in (':link', ':any-link'):508 sel.selectors.append(CSS_LINK)509 elif pseudo == ':checked':510 sel.selectors.append(CSS_CHECKED)511 elif pseudo == ':default':512 sel.selectors.append(CSS_DEFAULT)513 elif pseudo == ':indeterminate':514 sel.selectors.append(CSS_INDETERMINATE)515 elif pseudo == ":disabled":516 sel.selectors.append(CSS_DISABLED)517 elif pseudo == ":enabled":518 sel.selectors.append(CSS_ENABLED)519 elif pseudo == ":required":520 sel.selectors.append(CSS_REQUIRED)521 elif pseudo == ":optional":522 sel.selectors.append(CSS_OPTIONAL)523 elif pseudo == ":read-only":524 sel.selectors.append(CSS_READ_ONLY)525 elif pseudo == ":read-write":526 sel.selectors.append(CSS_READ_WRITE)527 elif pseudo == ":in-range":528 sel.selectors.append(CSS_IN_RANGE)529 elif pseudo == ":out-of-range":530 sel.selectors.append(CSS_OUT_OF_RANGE)531 elif pseudo == ":placeholder-shown":532 sel.selectors.append(CSS_PLACEHOLDER_SHOWN)533 elif pseudo == ':first-child':534 sel.nth.append(ct.SelectorNth(1, False, 0, False, False, ct.SelectorList()))535 elif pseudo == ':last-child':536 sel.nth.append(ct.SelectorNth(1, False, 0, False, True, ct.SelectorList()))537 elif pseudo == ':first-of-type':538 sel.nth.append(ct.SelectorNth(1, False, 0, True, False, ct.SelectorList()))539 elif pseudo == ':last-of-type':540 sel.nth.append(ct.SelectorNth(1, False, 0, True, True, ct.SelectorList()))541 elif pseudo == ':only-child':542 sel.nth.extend(543 [544 ct.SelectorNth(1, False, 0, False, False, ct.SelectorList()),545 ct.SelectorNth(1, False, 0, False, True, ct.SelectorList())546 ]547 )548 elif pseudo == ':only-of-type':549 sel.nth.extend(550 [551 ct.SelectorNth(1, False, 0, True, False, ct.SelectorList()),552 ct.SelectorNth(1, False, 0, True, True, ct.SelectorList())553 ]554 )555 has_selector = True556 elif complex_pseudo and pseudo in PSEUDO_COMPLEX_NO_MATCH:557 self.parse_selectors(iselector, m.end(0), FLG_PSEUDO | FLG_OPEN)558 sel.no_match = True559 has_selector = True560 elif not complex_pseudo and pseudo in PSEUDO_SIMPLE_NO_MATCH:561 sel.no_match = True562 has_selector = True563 elif pseudo in PSEUDO_SUPPORTED:564 raise SelectorSyntaxError(565 "Invalid syntax for pseudo class '{}'".format(pseudo),566 self.pattern,567 m.start(0)568 )569 else:570 raise NotImplementedError(571 "'{}' pseudo-class is not implemented at this time".format(pseudo)572 )573 return has_selector, is_html574 def parse_pseudo_nth(self, sel, m, has_selector, iselector):575 """Parse `nth` pseudo."""576 mdict = m.groupdict()577 if mdict.get('pseudo_nth_child'):578 postfix = '_child'579 else:580 postfix = '_type'581 mdict['name'] = util.lower(css_unescape(mdict['name']))582 content = util.lower(mdict.get('nth' + postfix))583 if content == 'even':584 # 2n585 s1 = 2586 s2 = 0587 var = True588 elif content == 'odd':589 # 2n+1590 s1 = 2591 s2 = 1592 var = True593 else:594 nth_parts = RE_NTH.match(content)595 s1 = '-' if nth_parts.group('s1') and nth_parts.group('s1') == '-' else ''596 a = nth_parts.group('a')597 var = a.endswith('n')598 if a.startswith('n'):599 s1 += '1'600 elif var:601 s1 += a[:-1]602 else:603 s1 += a604 s2 = '-' if nth_parts.group('s2') and nth_parts.group('s2') == '-' else ''605 if nth_parts.group('b'):606 s2 += nth_parts.group('b')607 else:608 s2 = '0'609 s1 = int(s1, 10)610 s2 = int(s2, 10)611 pseudo_sel = mdict['name']612 if postfix == '_child':613 if m.group('of'):614 # Parse the rest of `of S`.615 nth_sel = self.parse_selectors(iselector, m.end(0), FLG_PSEUDO | FLG_OPEN)616 else:617 # Use default `*|*` for `of S`.618 nth_sel = CSS_NTH_OF_S_DEFAULT619 if pseudo_sel == ':nth-child':620 sel.nth.append(ct.SelectorNth(s1, var, s2, False, False, nth_sel))621 elif pseudo_sel == ':nth-last-child':622 sel.nth.append(ct.SelectorNth(s1, var, s2, False, True, nth_sel))623 else:624 if pseudo_sel == ':nth-of-type':625 sel.nth.append(ct.SelectorNth(s1, var, s2, True, False, ct.SelectorList()))626 elif pseudo_sel == ':nth-last-of-type':627 sel.nth.append(ct.SelectorNth(s1, var, s2, True, True, ct.SelectorList()))628 has_selector = True629 return has_selector630 def parse_pseudo_open(self, sel, name, has_selector, iselector, index):631 """Parse pseudo with opening bracket."""632 flags = FLG_PSEUDO | FLG_OPEN633 if name == ':not':634 flags |= FLG_NOT635 if name == ':has':636 flags |= FLG_RELATIVE637 sel.selectors.append(self.parse_selectors(iselector, index, flags))638 has_selector = True639 return has_selector640 def parse_has_combinator(self, sel, m, has_selector, selectors, rel_type, index):641 """Parse combinator tokens."""642 combinator = m.group('relation').strip()643 if not combinator:644 combinator = WS_COMBINATOR645 if combinator == COMMA_COMBINATOR:646 if not has_selector:647 # If we've not captured any selector parts, the comma is either at the beginning of the pattern648 # or following another comma, both of which are unexpected. Commas must split selectors.649 raise SelectorSyntaxError(650 "The combinator '{}' at postion {}, must have a selector before it".format(combinator, index),651 self.pattern,652 index653 )654 sel.rel_type = rel_type655 selectors[-1].relations.append(sel)656 rel_type = ":" + WS_COMBINATOR657 selectors.append(_Selector())658 else:659 if has_selector:660 # End the current selector and associate the leading combinator with this selector.661 sel.rel_type = rel_type662 selectors[-1].relations.append(sel)663 elif rel_type[1:] != WS_COMBINATOR:664 # It's impossible to have two whitespace combinators after each other as the patterns665 # will gobble up trailing whitespace. It is also impossible to have a whitespace666 # combinator after any other kind for the same reason. But we could have667 # multiple non-whitespace combinators. So if the current combinator is not a whitespace,668 # then we've hit the multiple combinator case, so we should fail.669 raise SelectorSyntaxError(670 'The multiple combinators at position {}'.format(index),671 self.pattern,672 index673 )674 # Set the leading combinator for the next selector.675 rel_type = ':' + combinator676 sel = _Selector()677 has_selector = False678 return has_selector, sel, rel_type679 def parse_combinator(self, sel, m, has_selector, selectors, relations, is_pseudo, index):680 """Parse combinator tokens."""681 combinator = m.group('relation').strip()682 if not combinator:683 combinator = WS_COMBINATOR684 if not has_selector:685 raise SelectorSyntaxError(686 "The combinator '{}' at postion {}, must have a selector before it".format(combinator, index),687 self.pattern,688 index689 )690 if combinator == COMMA_COMBINATOR:691 if not sel.tag and not is_pseudo:692 # Implied `*`693 sel.tag = ct.SelectorTag('*', None)694 sel.relations.extend(relations)695 selectors.append(sel)696 del relations[:]697 else:698 sel.relations.extend(relations)699 sel.rel_type = combinator700 del relations[:]701 relations.append(sel)702 sel = _Selector()703 has_selector = False704 return has_selector, sel705 def parse_class_id(self, sel, m, has_selector):706 """Parse HTML classes and ids."""707 selector = m.group(0)708 if selector.startswith('.'):709 sel.classes.append(css_unescape(selector[1:]))710 else:711 sel.ids.append(css_unescape(selector[1:]))712 has_selector = True713 return has_selector714 def parse_pseudo_contains(self, sel, m, has_selector):715 """Parse contains."""716 values = m.group('values')717 patterns = []718 for token in RE_VALUES.finditer(values):719 if token.group('split'):720 continue721 value = token.group('value')722 if value.startswith(("'", '"')):723 value = css_unescape(value[1:-1], True)724 else:725 value = css_unescape(value)726 patterns.append(value)727 sel.contains.append(ct.SelectorContains(tuple(patterns)))728 has_selector = True729 return has_selector730 def parse_pseudo_lang(self, sel, m, has_selector):731 """Parse pseudo language."""732 values = m.group('values')733 patterns = []734 for token in RE_VALUES.finditer(values):735 if token.group('split'):736 continue737 value = token.group('value')738 if value.startswith(('"', "'")):739 parts = css_unescape(value[1:-1], True).split('-')740 else:741 parts = css_unescape(value).split('-')742 new_parts = []743 first = True744 for part in parts:745 if part == '*' and first:746 new_parts.append('(?!x\b)[a-z0-9]+?')747 elif part != '*':748 new_parts.append(('' if first else '(-(?!x\b)[a-z0-9]+)*?\\-') + re.escape(part))749 if first:750 first = False751 patterns.append(re.compile(r'^{}(?:-.*)?$'.format(''.join(new_parts)), re.I))752 sel.lang.append(ct.SelectorLang(patterns))753 has_selector = True754 return has_selector755 def parse_pseudo_dir(self, sel, m, has_selector):756 """Parse pseudo direction."""757 value = ct.SEL_DIR_LTR if util.lower(m.group('dir')) == 'ltr' else ct.SEL_DIR_RTL758 sel.flags |= value759 has_selector = True760 return has_selector761 def parse_selectors(self, iselector, index=0, flags=0):762 """Parse selectors."""763 sel = _Selector()764 selectors = []765 has_selector = False766 closed = False767 relations = []768 rel_type = ":" + WS_COMBINATOR769 is_open = bool(flags & FLG_OPEN)770 is_pseudo = bool(flags & FLG_PSEUDO)771 is_relative = bool(flags & FLG_RELATIVE)772 is_not = bool(flags & FLG_NOT)773 is_html = bool(flags & FLG_HTML)774 is_default = bool(flags & FLG_DEFAULT)775 is_indeterminate = bool(flags & FLG_INDETERMINATE)776 is_in_range = bool(flags & FLG_IN_RANGE)777 is_out_of_range = bool(flags & FLG_OUT_OF_RANGE)778 if self.debug: # pragma: no cover779 if is_pseudo:780 print(' is_pseudo: True')781 if is_open:782 print(' is_open: True')783 if is_relative:784 print(' is_relative: True')785 if is_not:786 print(' is_not: True')787 if is_html:788 print(' is_html: True')789 if is_default:790 print(' is_default: True')791 if is_indeterminate:792 print(' is_indeterminate: True')793 if is_in_range:794 print(' is_in_range: True')795 if is_out_of_range:796 print(' is_out_of_range: True')797 if is_relative:798 selectors.append(_Selector())799 try:800 while True:801 key, m = next(iselector)802 # Handle parts803 if key == "at_rule":804 raise NotImplementedError("At-rules found at position {}".format(m.start(0)))805 elif key == 'pseudo_class_custom':806 has_selector = self.parse_pseudo_class_custom(sel, m, has_selector)807 elif key == 'pseudo_class':808 has_selector, is_html = self.parse_pseudo_class(sel, m, has_selector, iselector, is_html)809 elif key == 'pseudo_element':810 raise NotImplementedError("Psuedo-element found at position {}".format(m.start(0)))811 elif key == 'pseudo_contains':812 has_selector = self.parse_pseudo_contains(sel, m, has_selector)813 elif key in ('pseudo_nth_type', 'pseudo_nth_child'):814 has_selector = self.parse_pseudo_nth(sel, m, has_selector, iselector)815 elif key == 'pseudo_lang':816 has_selector = self.parse_pseudo_lang(sel, m, has_selector)817 elif key == 'pseudo_dir':818 has_selector = self.parse_pseudo_dir(sel, m, has_selector)819 # Currently only supports HTML820 is_html = True821 elif key == 'pseudo_close':822 if not has_selector:823 raise SelectorSyntaxError(824 "Expected a selector at postion {}".format(m.start(0)),825 self.pattern,826 m.start(0)827 )828 if is_open:829 closed = True830 break831 else:832 raise SelectorSyntaxError(833 "Unmatched pseudo-class close at postion {}".format(m.start(0)),834 self.pattern,835 m.start(0)836 )837 elif key == 'combine':838 if is_relative:839 has_selector, sel, rel_type = self.parse_has_combinator(840 sel, m, has_selector, selectors, rel_type, index841 )842 else:843 has_selector, sel = self.parse_combinator(844 sel, m, has_selector, selectors, relations, is_pseudo, index845 )846 elif key == 'attribute':847 has_selector = self.parse_attribute_selector(sel, m, has_selector)848 elif key == 'tag':849 if has_selector:850 raise SelectorSyntaxError(851 "Tag name found at position {} instead of at the start".format(m.start(0)),852 self.pattern,853 m.start(0)854 )855 has_selector = self.parse_tag_pattern(sel, m, has_selector)856 elif key in ('class', 'id'):857 has_selector = self.parse_class_id(sel, m, has_selector)858 index = m.end(0)859 except StopIteration:860 pass861 if is_open and not closed:862 raise SelectorSyntaxError(863 "Unclosed pseudo-class at position {}".format(index),864 self.pattern,865 index866 )867 if has_selector:868 if not sel.tag and not is_pseudo:869 # Implied `*`870 sel.tag = ct.SelectorTag('*', None)871 if is_relative:872 sel.rel_type = rel_type873 selectors[-1].relations.append(sel)874 else:875 sel.relations.extend(relations)876 del relations[:]877 selectors.append(sel)878 else:879 # We will always need to finish a selector when `:has()` is used as it leads with combining.880 raise SelectorSyntaxError(881 'Expected a selector at position {}'.format(index),882 self.pattern,883 index884 )885 # Some patterns require additional logic, such as default. We try to make these the886 # last pattern, and append the appropriate flag to that selector which communicates887 # to the matcher what additional logic is required.888 if is_default:889 selectors[-1].flags = ct.SEL_DEFAULT890 if is_indeterminate:891 selectors[-1].flags = ct.SEL_INDETERMINATE892 if is_in_range:893 selectors[-1].flags = ct.SEL_IN_RANGE894 if is_out_of_range:895 selectors[-1].flags = ct.SEL_OUT_OF_RANGE896 return ct.SelectorList([s.freeze() for s in selectors], is_not, is_html)897 def selector_iter(self, pattern):898 """Iterate selector tokens."""899 # Ignore whitespace and comments at start and end of pattern900 m = RE_WS_BEGIN.search(pattern)901 index = m.end(0) if m else 0902 m = RE_WS_END.search(pattern)903 end = (m.start(0) - 1) if m else (len(pattern) - 1)904 if self.debug: # pragma: no cover905 print('## PARSING: {!r}'.format(pattern))906 while index <= end:907 m = None908 for v in self.css_tokens:909 if not v.enabled(self.flags): # pragma: no cover910 continue911 m = v.match(pattern, index)912 if m:913 name = v.get_name()914 if self.debug: # pragma: no cover915 print("TOKEN: '{}' --> {!r} at position {}".format(name, m.group(0), m.start(0)))916 index = m.end(0)917 yield name, m918 break919 if m is None:920 c = pattern[index]921 # If the character represents the start of one of the known selector types,922 # throw an exception mentioning that the known selector type is in error;923 # otherwise, report the invalid character.924 if c == '[':925 msg = "Malformed attribute selector at position {}".format(index)926 elif c == '.':927 msg = "Malformed class selector at position {}".format(index)928 elif c == '#':929 msg = "Malformed id selector at position {}".format(index)930 elif c == ':':931 msg = "Malformed pseudo-class selector at position {}".format(index)932 else:933 msg = "Invalid character {!r} position {}".format(c, index)934 raise SelectorSyntaxError(msg, self.pattern, index)935 if self.debug: # pragma: no cover936 print('## END PARSING')937 def process_selectors(self, index=0, flags=0):938 """Process selectors."""939 return self.parse_selectors(self.selector_iter(self.pattern), index, flags)940# Precompile CSS selector lists for pseudo-classes (additional logic may be required beyond the pattern)941# A few patterns are order dependent as they use patterns previous compiled.942# CSS pattern for `:link` and `:any-link`943CSS_LINK = CSSParser(944 'html|*:is(a, area, link)[href]'945).process_selectors(flags=FLG_PSEUDO | FLG_HTML)946# CSS pattern for `:checked`947CSS_CHECKED = CSSParser(948 '''949 html|*:is(input[type=checkbox], input[type=radio])[checked],950 html|select > html|option[selected]951 '''952).process_selectors(flags=FLG_PSEUDO | FLG_HTML)953# CSS pattern for `:default` (must compile CSS_CHECKED first)954CSS_DEFAULT = CSSParser(955 '''956 :checked,957 /*958 This pattern must be at the end.959 Special logic is applied to the last selector.960 */961 html|form html|*:is(button, input)[type="submit"]962 '''963).process_selectors(flags=FLG_PSEUDO | FLG_HTML | FLG_DEFAULT)964# CSS pattern for `:indeterminate`965CSS_INDETERMINATE = CSSParser(966 '''967 html|input[type="checkbox"][indeterminate],968 html|input[type="radio"]:is(:not([name]), [name=""]):not([checked]),969 html|progress:not([value]),970 /*971 This pattern must be at the end.972 Special logic is applied to the last selector.973 */974 html|input[type="radio"][name][name!='']:not([checked])975 '''976).process_selectors(flags=FLG_PSEUDO | FLG_HTML | FLG_INDETERMINATE)977# CSS pattern for `:disabled`978CSS_DISABLED = CSSParser(979 '''980 html|*:is(input[type!=hidden], button, select, textarea, fieldset, optgroup, option, fieldset)[disabled],981 html|optgroup[disabled] > html|option,982 html|fieldset[disabled] > html|*:is(input[type!=hidden], button, select, textarea, fieldset),983 html|fieldset[disabled] >984 html|*:not(legend:nth-of-type(1)) html|*:is(input[type!=hidden], button, select, textarea, fieldset)985 '''986).process_selectors(flags=FLG_PSEUDO | FLG_HTML)987# CSS pattern for `:enabled`988CSS_ENABLED = CSSParser(989 '''990 html|*:is(input[type!=hidden], button, select, textarea, fieldset, optgroup, option, fieldset):not(:disabled)991 '''992).process_selectors(flags=FLG_PSEUDO | FLG_HTML)993# CSS pattern for `:required`994CSS_REQUIRED = CSSParser(995 'html|*:is(input, textarea, select)[required]'996).process_selectors(flags=FLG_PSEUDO | FLG_HTML)997# CSS pattern for `:optional`998CSS_OPTIONAL = CSSParser(999 'html|*:is(input, textarea, select):not([required])'1000).process_selectors(flags=FLG_PSEUDO | FLG_HTML)1001# CSS pattern for `:placeholder-shown`1002CSS_PLACEHOLDER_SHOWN = CSSParser(1003 '''1004 html|*:is(1005 input:is(1006 :not([type]),1007 [type=""],1008 [type=text],1009 [type=search],1010 [type=url],1011 [type=tel],1012 [type=email],1013 [type=password],1014 [type=number]1015 ),1016 textarea1017 )[placeholder][placeholder!='']1018 '''1019).process_selectors(flags=FLG_PSEUDO | FLG_HTML)1020# CSS pattern default for `:nth-child` "of S" feature1021CSS_NTH_OF_S_DEFAULT = CSSParser(1022 '*|*'1023).process_selectors(flags=FLG_PSEUDO)1024# CSS pattern for `:read-write` (CSS_DISABLED must be compiled first)1025CSS_READ_WRITE = CSSParser(1026 '''1027 html|*:is(1028 textarea,1029 input:is(1030 :not([type]),1031 [type=""],1032 [type=text],1033 [type=search],1034 [type=url],1035 [type=tel],1036 [type=email],1037 [type=number],1038 [type=password],1039 [type=date],1040 [type=datetime-local],1041 [type=month],1042 [type=time],1043 [type=week]1044 )1045 ):not([readonly], :disabled),1046 html|*:is([contenteditable=""], [contenteditable="true" i])1047 '''1048).process_selectors(flags=FLG_PSEUDO | FLG_HTML)1049# CSS pattern for `:read-only`1050CSS_READ_ONLY = CSSParser(1051 '''1052 html|*:not(:read-write)1053 '''1054).process_selectors(flags=FLG_PSEUDO | FLG_HTML)1055# CSS pattern for `:in-range`1056CSS_IN_RANGE = CSSParser(1057 '''1058 html|input:is(1059 [type="date"],1060 [type="month"],1061 [type="week"],1062 [type="time"],1063 [type="datetime-local"],1064 [type="number"],1065 [type="range"]1066 ):is(1067 [min],1068 [max]1069 )1070 '''1071).process_selectors(flags=FLG_PSEUDO | FLG_IN_RANGE | FLG_HTML)1072# CSS pattern for `:out-of-range`1073CSS_OUT_OF_RANGE = CSSParser(1074 '''1075 html|input:is(1076 [type="date"],1077 [type="month"],1078 [type="week"],1079 [type="time"],1080 [type="datetime-local"],1081 [type="number"],1082 [type="range"]1083 ):is(1084 [min],1085 [max]1086 )1087 '''...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run autotest automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful