Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/turbohtml/convert.py: 95%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2turbohtml.convert: translate between the query languages turbohtml speaks.
4:func:`css_to_xpath` turns a CSS selector list into an equivalent XPath 1.0 expression, replacing `cssselect
5<https://github.com/scrapy/cssselect>`__ (the translator behind lxml's ``cssselect()``, parsel, and pyquery). turbohtml
6runs both a CSS selector engine and an XPath engine in-process, so the translation is a C pass over the parsed
7selector, and a differential suite checks the output selects the same nodes as the native CSS engine. The emitted
8string is not cssselect's: every predicate is context-free, and the HTML semantics are the ones turbohtml's own engine
9implements (the WHATWG case-insensitive attribute set, Selectors 4 ``:empty``, the exact ``fieldset``/``legend`` rule
10for ``:disabled``).
12:class:`GenericTranslator` and :class:`HTMLTranslator` mirror cssselect's classes so ports are mechanical; both
13delegate to the same engine, which always applies the HTML rules (element and attribute names lowercase).
14"""
16from __future__ import annotations
18from ._html import _css_specificity, _css_to_xpath
19from ._internal._selectors import SelectorSyntaxError
21__all__ = [
22 "ExpressionError",
23 "GenericTranslator",
24 "HTMLTranslator",
25 "SelectorError",
26 "SelectorSyntaxError",
27 "css_specificity",
28 "css_to_xpath",
29]
32class SelectorError(Exception):
33 """Parent of :class:`ExpressionError`, mirroring cssselect's error base for a valid-but-untranslatable selector."""
36class ExpressionError(SelectorError, RuntimeError):
37 """The selector is valid CSS but has no XPath 1.0 equivalent (for example ``:dir()`` or ``*:first-of-type``)."""
40def css_to_xpath(selector: str, *, prefix: str = "descendant-or-self::") -> str:
41 """
42 Translate a CSS selector list to an equivalent XPath 1.0 expression.
44 The XPath selects the same nodes as the selector does under :meth:`turbohtml.Node.select`; a comma-separated list
45 becomes a ``|`` union with the prefix prepended to each arm.
47 :param selector: the CSS selector list to translate.
48 :param prefix: prepended to each arm; the default scopes the expression to the context node's subtree, matching
49 cssselect.
50 :raises SelectorSyntaxError: the selector does not parse.
51 :raises ExpressionError: the selector parses but cannot be expressed in XPath 1.0.
52 :returns: the XPath 1.0 expression.
53 """
54 try:
55 return _css_to_xpath(selector, prefix)
56 except NotImplementedError as error:
57 raise ExpressionError(str(error)) from None
60def css_specificity(selector: str) -> list[tuple[int, int, int]]:
61 """
62 Return the specificity of each selector in a comma-separated list.
64 Each triple is ``(a, b, c)`` per `CSS Selectors Level 4 §17
65 <https://www.w3.org/TR/selectors-4/#specificity-rules>`_: ``a`` counts id selectors, ``b`` counts class, attribute,
66 and pseudo-class selectors, and ``c`` counts type and pseudo-element selectors. ``:is()``, ``:not()``, and
67 ``:has()`` take the specificity of their most specific argument; ``:where()`` contributes zero. The list is one
68 triple per comma-separated selector, the shape ``cssselect``'s ``Selector.specificity()`` returns per parsed arm.
70 :param selector: the CSS selector list to weigh.
71 :raises SelectorSyntaxError: the selector does not parse.
72 :returns: one ``(a, b, c)`` triple per selector in the list, in source order.
73 """
74 return _css_specificity(selector)
77class GenericTranslator:
78 """
79 A cssselect-shaped translator: :meth:`css_to_xpath` is a method here, as in cssselect.
81 Unlike cssselect's generic (XML) translator, the translation always follows the HTML rules turbohtml's selector
82 engine implements: element and attribute names are matched lowercase, and the WHATWG case-insensitive attribute set
83 compares values case-insensitively.
84 """
86 def css_to_xpath(self, css: str, prefix: str = "descendant-or-self::") -> str: # ruff:ignore[no-self-use] # cssselect shape needs an instance method
87 """
88 Translate a CSS selector list to an equivalent XPath 1.0 expression.
90 :param css: the CSS selector list to translate.
91 :param prefix: prepended to each arm of the translated union.
92 :raises SelectorSyntaxError: the selector does not parse.
93 :raises ExpressionError: the selector parses but cannot be expressed in XPath 1.0.
94 :returns: the XPath 1.0 expression.
95 """
96 return css_to_xpath(css, prefix=prefix)
99class HTMLTranslator(GenericTranslator):
100 """
101 The cssselect ``HTMLTranslator`` shape.
103 :param xhtml: accepted for signature compatibility with cssselect; the translation applies the HTML lowercasing
104 rules either way, because that is what turbohtml's parser and selector engine do.
105 """
107 def __init__(self, xhtml: bool = False) -> None: # ruff:ignore[boolean-type-hint-positional-argument, boolean-default-value-positional-argument] # positional bool mirrors cssselect
108 """Record the cssselect-compatible ``xhtml`` flag."""
109 self.xhtml = xhtml