"""CSS4 selectors for Python. cssselect2 is a straightforward implementation of CSS4 Selectors for markup documents (HTML, XML, etc.) that can be read by ElementTree-like parsers (including cElementTree, lxml, html5lib, etc.) """ from webencodings import ascii_lower # Classes are imported here to expose them at the top level of the module from .compiler import compile_selector_list # noqa from .parser import SelectorError # noqa from .tree import ElementWrapper # noqa VERSION = __version__ = '0.8.0' class Matcher: """A CSS selectors storage that can match against HTML elements.""" def __init__(self): self.id_selectors = {} self.class_selectors = {} self.lower_local_name_selectors = {} self.namespace_selectors = {} self.lang_attr_selectors = [] self.other_selectors = [] self.order = 0 def add_selector(self, selector, payload): """Add a selector and its payload to the matcher. :param selector: A :class:`compiler.CompiledSelector` object. :param payload: Some data associated to the selector, such as :class:`declarations ` parsed from the :attr:`tinycss2.ast.QualifiedRule.content` of a style rule. It can be any Python object, and will be returned as-is by :meth:`match`. """ self.order += 1 if selector.never_matches: return entry = ( selector.test, selector.specificity, self.order, selector.pseudo_element, payload) if selector.id is not None: self.id_selectors.setdefault(selector.id, []).append(entry) elif selector.class_name is not None: self.class_selectors.setdefault(selector.class_name, []).append(entry) elif selector.local_name is not None: self.lower_local_name_selectors.setdefault( selector.lower_local_name, []).append(entry) elif selector.namespace is not None: self.namespace_selectors.setdefault(selector.namespace, []).append(entry) elif selector.requires_lang_attr: self.lang_attr_selectors.append(entry) else: self.other_selectors.append(entry) def match(self, element): """Match selectors against the given element. :param element: An :class:`ElementWrapper`. :returns: A list of the payload objects associated to selectors that match element, in order of lowest to highest :attr:`compiler.CompiledSelector` specificity and in order of addition with :meth:`add_selector` among selectors of equal specificity. """ relevant_selectors = [] if element.id is not None and element.id in self.id_selectors: self.add_relevant_selectors( element, self.id_selectors[element.id], relevant_selectors) for class_name in element.classes: if class_name in self.class_selectors: self.add_relevant_selectors( element, self.class_selectors[class_name], relevant_selectors) lower_name = ascii_lower(element.local_name) if lower_name in self.lower_local_name_selectors: self.add_relevant_selectors( element, self.lower_local_name_selectors[lower_name], relevant_selectors) if element.namespace_url in self.namespace_selectors: self.add_relevant_selectors( element, self.namespace_selectors[element.namespace_url], relevant_selectors) if 'lang' in element.etree_element.attrib: self.add_relevant_selectors( element, self.lang_attr_selectors, relevant_selectors) self.add_relevant_selectors(element, self.other_selectors, relevant_selectors) relevant_selectors.sort() return relevant_selectors @staticmethod def add_relevant_selectors(element, selectors, relevant_selectors): for test, specificity, order, pseudo, payload in selectors: if test(element): relevant_selectors.append((specificity, order, pseudo, payload))