Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/turbohtml/cssom.py: 80%
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.cssom: the CSS Object Model cascade and computed style.
4The parse, selector-match, and cascade work lives in the C core (``turbohtml._html``): :func:`computed_style` runs the
5CSS Cascade -- collecting every ``<style>`` sheet plus the element chain's inline ``style``, matching the native
6selector engine, ordering by origin importance, the style attribute, specificity, and source order, then folding in
7inheritance and each property's initial value. The typed, read-only result shapes -- :class:`StyleSheet`,
8:class:`RuleList`, :class:`StyleRule`, :class:`StyleDeclaration`, and :class:`ComputedStyle` -- are the turbohtml-native
9spelling of the CSSOM ``CSSStyleSheet`` / ``CSSRuleList`` / ``CSSStyleRule`` / ``CSSStyleDeclaration`` interfaces.
11The value :func:`computed_style` returns is the *computed* value in the cascade sense: the winning specified value once
12``inherit`` / ``initial`` / ``unset`` are resolved. It is not the *used* value. turbohtml renders nothing, so no layout
13runs: lengths, percentages, relative units, and system colors come back as written rather than resolved to pixels. This
14is the same boundary jsdom and cssstyle draw -- see :doc:`the explanation </explanation/cssom>`.
15"""
17from __future__ import annotations
19from typing import TYPE_CHECKING, final
21from ._html import (
22 _css_computed_style,
23 _css_declaration_index,
24 _css_declaration_text,
25 _css_parse_declarations,
26 _css_parse_rules,
27)
29if TYPE_CHECKING:
30 from collections.abc import Iterator
32 from ._html import Element
34__all__ = ["ComputedStyle", "RuleList", "StyleDeclaration", "StyleRule", "StyleSheet", "computed_style"]
37@final
38class StyleDeclaration:
39 """
40 A read-only block of CSS declarations, the turbohtml spelling of CSSOM's ``CSSStyleDeclaration``.
42 Each declaration keeps its property name, value, and ``!important`` flag in source order. A property may appear more
43 than once (an important declaration wins, then the later duplicate); :meth:`get` and indexing return the winning
44 value, while iteration and :meth:`properties` visit each distinct property once, in first-seen order.
45 """
47 __slots__ = ("_items", "_last")
49 def __init__(self, items: tuple[tuple[str, str, bool] | tuple[str, str, bool, str], ...]) -> None:
50 """Wrap the parsed declarations in source order."""
51 self._items = items
52 self._last = _css_declaration_index(items)
54 @classmethod
55 def parse(cls, text: str) -> StyleDeclaration:
56 """
57 Parse a declaration block -- an inline ``style`` attribute or a rule body -- into a declaration.
59 :param text: the CSS declaration text, such as ``"color: red; margin: 0 auto"``.
60 :returns: the parsed declaration.
61 """
62 return cls(_css_parse_declarations(text))
64 def get(self, name: str, default: str | None = None) -> str | None:
65 """
66 Return a property's winning value, or ``default`` when it is not set.
68 :param name: the property name.
69 :param default: the value to return when the property is absent.
70 :returns: the property value, or ``default``.
71 """
72 index = self._last.get(name)
73 return self._items[index][1] if index is not None else self._fallback_value(name, default)
75 def important(self, name: str) -> bool:
76 """
77 Return whether a property's winning declaration carries ``!important``.
79 :param name: the property name.
80 :returns: whether the property is set and marked important.
81 """
82 index = self._last.get(name)
83 return self._items[index][2] if index is not None else self._fallback_important(name)
85 def properties(self) -> tuple[str, ...]:
86 """
87 Return the distinct property names, in first-seen order.
89 :returns: the property names.
90 """
91 return tuple(self._last)
93 @property
94 def text(self) -> str:
95 """The declaration serialized as ``name: value`` pairs joined by ``"; "``."""
96 return _css_declaration_text(self._items)
98 def __getitem__(self, name: str) -> str:
99 """Return a property's winning value, raising :class:`KeyError` when it is not set."""
100 index = self._last.get(name)
101 if index is None:
102 return self._missing_value(name)
103 return self._items[index][1]
105 def __contains__(self, name: str) -> bool:
106 """Return whether a property is set."""
107 return name in self._last or self._fallback_index(name) is not None
109 def _fallback_index(self, name: str) -> int | None:
110 if name.startswith("--"):
111 return None
112 if name.isascii():
113 return self._last.get(name.lower())
114 return self._last.get("".join(chr(ord(char) + 32) if "A" <= char <= "Z" else char for char in name))
116 def _fallback_value(self, name: str, default: str | None) -> str | None:
117 index = self._fallback_index(name)
118 return default if index is None else self._items[index][1]
120 def _fallback_important(self, name: str) -> bool:
121 index = self._fallback_index(name)
122 return index is not None and self._items[index][2]
124 def _missing_value(self, name: str) -> str:
125 index = self._fallback_index(name)
126 if index is None:
127 raise KeyError(name)
128 return self._items[index][1]
130 def __iter__(self) -> Iterator[str]:
131 """Iterate the distinct property names, in first-seen order."""
132 return iter(self._last)
134 def __len__(self) -> int:
135 """Return the number of distinct properties."""
136 return len(self._last)
138 def __repr__(self) -> str:
139 """Return the declaration as ``StyleDeclaration('...')``."""
140 return f"StyleDeclaration({self.text!r})"
143@final
144class StyleRule:
145 """One CSS style rule, the turbohtml spelling of CSSOM's ``CSSStyleRule``: a selector and its declarations."""
147 __slots__ = ("_style", "selector_text")
149 selector_text: str
150 """The rule's selector list, verbatim (for example ``"a, .box"``)."""
152 def __init__(self, selector_text: str, style: StyleDeclaration) -> None:
153 """Bind a selector to its declaration block."""
154 self.selector_text = selector_text
155 self._style = style
157 @property
158 def style(self) -> StyleDeclaration:
159 """The rule's declaration block."""
160 return self._style
162 def __repr__(self) -> str:
163 """Return the rule as ``StyleRule('selector { ... }')``."""
164 return f"StyleRule({self.selector_text!r} {{ {self._style.text} }})"
167@final
168class RuleList:
169 """A read-only sequence of :class:`StyleRule`, the turbohtml spelling of CSSOM's ``CSSRuleList``."""
171 __slots__ = ("_rules",)
173 def __init__(self, rules: tuple[StyleRule, ...]) -> None:
174 """Wrap the rules in document order."""
175 self._rules = rules
177 def __getitem__(self, index: int) -> StyleRule:
178 """Return the rule at an index."""
179 return self._rules[index]
181 def __iter__(self) -> Iterator[StyleRule]:
182 """Iterate the rules in document order."""
183 return iter(self._rules)
185 def __len__(self) -> int:
186 """Return the number of rules."""
187 return len(self._rules)
189 def __repr__(self) -> str:
190 """Return the rule list as ``RuleList([...])``."""
191 return f"RuleList({list(self._rules)!r})"
194@final
195class StyleSheet:
196 """
197 A parsed stylesheet, the turbohtml spelling of CSSOM's ``CSSStyleSheet``.
199 At-rules (``@media``, ``@import``, and the like) are skipped: only top-level style rules join :attr:`rules`.
201 :param text: the CSS source, such as the text of a ``<style>`` element.
202 """
204 __slots__ = ("_rules",)
206 def __init__(self, text: str) -> None:
207 """Parse CSS source into a stylesheet."""
208 self._rules = RuleList(
209 tuple(StyleRule(selector, StyleDeclaration(decls)) for selector, decls in _css_parse_rules(text))
210 )
212 @property
213 def rules(self) -> RuleList:
214 """The stylesheet's style rules, in document order."""
215 return self._rules
217 def __repr__(self) -> str:
218 """Return the stylesheet as ``StyleSheet(<n> rules)``."""
219 return f"StyleSheet({len(self._rules)} rules)"
222@final
223class ComputedStyle:
224 """
225 An element's resolved computed style, the read-only result of :func:`computed_style`.
227 It exposes one value per supported longhand property (never a shorthand), each already resolved through the cascade,
228 inheritance, and initial values. Missing properties fall back to their initial value, so :meth:`get` never returns
229 ``None`` for a supported property.
230 """
232 __slots__ = ("_values",)
234 def __init__(self, values: tuple[tuple[str, str], ...]) -> None:
235 """Wrap the ``(name, value)`` pairs the cascade resolved."""
236 self._values = dict(values)
238 def get(self, name: str, default: str | None = None) -> str | None:
239 """
240 Return a property's computed value, or ``default`` when it is not a supported property.
242 :param name: the longhand property name.
243 :param default: the value to return for an unsupported property.
244 :returns: the computed value, or ``default``.
245 """
246 return self._values.get(name, default)
248 def properties(self) -> tuple[str, ...]:
249 """
250 Return every supported property name, in the cascade's canonical order.
252 :returns: the property names.
253 """
254 return tuple(self._values)
256 def __getitem__(self, name: str) -> str:
257 """Return a property's computed value, raising :class:`KeyError` for an unsupported property."""
258 return self._values[name]
260 def __contains__(self, name: str) -> bool:
261 """Return whether a property is a supported longhand."""
262 return name in self._values
264 def __iter__(self) -> Iterator[str]:
265 """Iterate the supported property names."""
266 return iter(self._values)
268 def __len__(self) -> int:
269 """Return the number of supported properties."""
270 return len(self._values)
272 def __repr__(self) -> str:
273 """Return the computed style as ``ComputedStyle(<n> properties)``."""
274 return f"ComputedStyle({len(self._values)} properties)"
277def computed_style(element: Element) -> ComputedStyle:
278 """
279 Resolve the CSS cascade for one element and return its computed style.
281 The cascade reads every ``<style>`` element in the element's document plus the inline ``style`` attributes along its
282 ancestor chain, matches the native selector engine, and orders declarations by importance, the style attribute,
283 specificity, and source order before applying inheritance and initial values.
285 :param element: the element to compute the style of.
286 :returns: the resolved, read-only computed style.
287 """
288 return ComputedStyle(_css_computed_style(element))