"""
domonic.style
=============
CSSOM-flavoured style helpers for domonic.
This module provides ``CSSStyleDeclaration``, stylesheet lists, rule objects,
and the basic structures needed to attach style data to DOM nodes and
documents.
"""
from __future__ import annotations
import ast
import functools
import math
import re
from re import M, findall, finditer
from typing import Any, Callable
from . import _cssom
from .utils import Utils
COMMENT_REGEXP = r"/\*[\s\S]*?\*/"
_IMPORTANT_RE = re.compile(r"\s*!\s*important\s*$", re.IGNORECASE)
_KNOWN_CSS_PROPERTIES: set[str] | None = None
_CSS_STYLE_INTERNAL_NAMES = {
"cssText",
"parentRule",
"parentStyleSheet",
"_css_text",
"_declared_properties",
"_members_checked",
"_parent_node",
"_suspend_style_sync",
}
_ADDITIONAL_CSS_PROPERTY_NAMES = {
"accent-color",
"align-tracks",
"anchor-name",
"anchor-scope",
"animation-composition",
"appearance",
"aspect-ratio",
"backdrop-filter",
"block-size",
"border-block",
"border-block-color",
"border-block-end",
"border-block-end-color",
"border-block-end-style",
"border-block-end-width",
"border-block-start",
"border-block-start-color",
"border-block-start-style",
"border-block-start-width",
"border-block-style",
"border-block-width",
"border-inline",
"border-inline-color",
"border-inline-end",
"border-inline-end-color",
"border-inline-end-style",
"border-inline-end-width",
"border-inline-start",
"border-inline-start-color",
"border-inline-start-style",
"border-inline-start-width",
"border-inline-style",
"border-inline-width",
"container",
"container-name",
"container-type",
"contain-intrinsic-block-size",
"contain-intrinsic-height",
"contain-intrinsic-inline-size",
"contain-intrinsic-size",
"contain-intrinsic-width",
"content-visibility",
"field-sizing",
"font-palette",
"font-size-adjust",
"font-synthesis-position",
"font-synthesis-small-caps",
"font-synthesis-style",
"font-synthesis-weight",
"hyphenate-character",
"image-rendering",
"initial-letter",
"inline-size",
"inset",
"inset-block",
"inset-block-end",
"inset-block-start",
"inset-inline",
"inset-inline-end",
"inset-inline-start",
"interpolate-size",
"justify-tracks",
"line-clamp",
"margin-block",
"margin-block-end",
"margin-block-start",
"margin-inline",
"margin-inline-end",
"margin-inline-start",
"mask-border",
"mask-clip",
"mask-composite",
"mask-image",
"mask-mode",
"mask-origin",
"mask-position",
"mask-repeat",
"mask-size",
"max-block-size",
"max-inline-size",
"min-block-size",
"min-inline-size",
"object-fit",
"object-position",
"offset",
"offset-anchor",
"offset-distance",
"offset-path",
"offset-position",
"offset-rotate",
"overflow-anchor",
"overflow-block",
"overflow-clip-margin",
"overflow-inline",
"overscroll-behavior",
"overscroll-behavior-block",
"overscroll-behavior-inline",
"overscroll-behavior-x",
"overscroll-behavior-y",
"padding-block",
"padding-block-end",
"padding-block-start",
"padding-inline",
"padding-inline-end",
"padding-inline-start",
"place-content",
"place-items",
"place-self",
"position-anchor",
"position-area",
"position-try",
"position-try-fallbacks",
"position-try-order",
"position-visibility",
"print-color-adjust",
"rotate",
"scale",
"scrollbar-color",
"scrollbar-gutter",
"scrollbar-width",
"text-box",
"text-box-edge",
"text-box-trim",
"text-decoration-skip-ink",
"text-emphasis",
"text-emphasis-color",
"text-emphasis-position",
"text-emphasis-style",
"text-size-adjust",
"text-wrap",
"text-wrap-mode",
"text-wrap-style",
"timeline-scope",
"touch-action",
"translate",
"view-timeline",
"view-timeline-axis",
"view-timeline-inset",
"view-timeline-name",
"view-transition-class",
"view-transition-name",
}
@functools.lru_cache(maxsize=1024)
def _css_property_name(name: str) -> str:
"""Normalize DOM style names to CSS property names.
Pure function of *name* over a small, fixed universe of property/attribute
spellings, called on every style read (``getPropertyValue``, the ``style``
IDL, ``getComputedStyle``) -- cached so the regex-driven case conversion
it delegates to only ever runs once per distinct name.
"""
if name in ("cssFloat", "float"):
return "float"
if name.startswith("--"):
return name
return Utils.case_kebab(name)
@functools.lru_cache(maxsize=1024)
def _css_attribute_name(name: str) -> str:
"""Normalize CSS property names to DOM style attribute names. See
``_css_property_name`` -- cached for the same reason."""
if name == "float":
return "cssFloat"
if name.startswith("--"):
return name
return Utils.case_camel(name)
def _strip_css_comments(css_text: str) -> str:
return re.sub(COMMENT_REGEXP, "", css_text or "")
def _split_css_declarations(css_text: str) -> list[str]:
declarations: list[str] = []
start = 0
quote = None
escaped = False
depth = 0
for index, char in enumerate(css_text or ""):
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([{":
depth += 1
continue
if char in ")]}" and depth > 0:
depth -= 1
continue
if char == ";" and depth == 0:
item = css_text[start:index].strip()
if item:
declarations.append(item)
start = index + 1
item = (css_text or "")[start:].strip()
if item:
declarations.append(item)
return declarations
#: each 2-value logical axis shorthand ("start end", or one value for both)
#: -> its two physical longhands, in start-then-end order. Only LTR,
#: horizontal writing-mode is modelled (inline -> left/right, block ->
#: top/bottom), matching this project's existing lack of RTL/bidi support.
_LOGICAL_AXIS_SHORTHANDS: dict[str, tuple[str, str]] = {
"padding-inline": ("padding-left", "padding-right"),
"padding-block": ("padding-top", "padding-bottom"),
"margin-inline": ("margin-left", "margin-right"),
"margin-block": ("margin-top", "margin-bottom"),
"inset-inline": ("left", "right"),
"inset-block": ("top", "bottom"),
}
#: each single-value logical longhand -> the one physical longhand it aliases.
_LOGICAL_ALIAS_LONGHANDS: dict[str, str] = {
"padding-inline-start": "padding-left",
"padding-inline-end": "padding-right",
"padding-block-start": "padding-top",
"padding-block-end": "padding-bottom",
"margin-inline-start": "margin-left",
"margin-inline-end": "margin-right",
"margin-block-start": "margin-top",
"margin-block-end": "margin-bottom",
"inset-inline-start": "left",
"inset-inline-end": "right",
"inset-block-start": "top",
"inset-block-end": "bottom",
}
#: properties whose value CSS 2.1 requires to be non-negative (8.4's
#: "Negative values for padding are not allowed", 8.5.3's own words for
#: border-width, and each property's own definition for the rest). A
#: syntactically invalid value is dropped per the cascade -- as if that one
#: declaration were never written -- not applied literally or clamped.
_NON_NEGATIVE_LENGTH_PROPERTIES = frozenset(
{
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"padding-inline",
"padding-block",
"padding-inline-start",
"padding-inline-end",
"padding-block-start",
"padding-block-end",
"border-width",
"border-top-width",
"border-right-width",
"border-bottom-width",
"border-left-width",
"outline-width",
"width",
"height",
"min-width",
"min-height",
"font-size",
}
)
#: a single token that is unambiguously a negative length/percentage (not
#: ``var()``/``calc()``, which need substitution before they can be judged).
_NEGATIVE_LENGTH_TOKEN_RE = re.compile(
r"^-(?:\d+\.?\d*|\.\d+)(px|em|rem|%|in|cm|mm|q|pt|pc|vw|vh|vmin|vmax|ch|ex)?$", re.I
)
def _is_invalid_non_negative_declaration(name: str, value: str) -> bool:
if name not in _NON_NEGATIVE_LENGTH_PROPERTIES:
return False
low = value.lower()
if "var(" in low or "calc(" in low:
return False
return any(_NEGATIVE_LENGTH_TOKEN_RE.match(token) for token in value.split())
def _parse_css_declarations(css_text: str) -> list[tuple[str, str, str]]:
entries: list[tuple[str, str, str]] = []
index_by_name: dict[str, int] = {}
def emit(name: str, value: str, priority: str) -> None:
# a later declaration for the same (already-physical) name replaces
# an earlier one rather than sitting alongside it, keeping its first
# position -- e.g. a repeated ``padding-inline`` in the same rule, or
# a plain longhand and the logical property that aliases it.
existing = index_by_name.get(name)
if existing is None:
index_by_name[name] = len(entries)
entries.append((name, value, priority))
else:
entries[existing] = (name, value, priority)
for declaration in _split_css_declarations(css_text):
if ":" not in declaration:
continue
name, value = declaration.split(":", 1)
name = _css_property_name(name.strip())
value = value.strip()
if not name:
continue
priority = ""
if _IMPORTANT_RE.search(value):
value = _IMPORTANT_RE.sub("", value).strip()
priority = "important"
# a syntactically invalid declaration is dropped entirely -- the
# cascade behaves as if it were never written, leaving whatever the
# property's next-highest-priority declaration (or initial value)
# was, not this literal invalid value.
if _is_invalid_non_negative_declaration(name, value):
continue
# logical properties are expanded into their physical longhand(s)
# here, at the tokenizer level, so the expansion rides on the same
# cascade priority (source order, !important) as the declaration it
# came from, rather than a physical longhand from some unrelated,
# lower-priority rule winning just by already having a value.
axis = _LOGICAL_AXIS_SHORTHANDS.get(name)
if axis is not None:
parts = value.split()
if len(parts) == 1:
emit(axis[0], parts[0], priority)
emit(axis[1], parts[0], priority)
continue
if len(parts) == 2:
emit(axis[0], parts[0], priority)
emit(axis[1], parts[1], priority)
continue
# unrecognised shape -- keep verbatim rather than guess
emit(name, value, priority)
continue
alias = _LOGICAL_ALIAS_LONGHANDS.get(name)
emit(alias if alias is not None else name, value, priority)
return entries
#: <length>-valued properties where a bare ``0`` is serialised as ``0px`` (the
#: CSSOM normalises unit-optional zero). ``line-height`` is excluded -- a
#: unitless number there is a multiplier, not a length.
_ZERO_LENGTH_PROPERTIES = frozenset(
{
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"inset",
"top",
"right",
"bottom",
"left",
"width",
"height",
"min-width",
"min-height",
"max-width",
"max-height",
"border-width",
"border-top-width",
"border-right-width",
"border-bottom-width",
"border-left-width",
"outline-width",
"column-rule-width",
"column-width",
"column-gap",
"row-gap",
"gap",
"letter-spacing",
"word-spacing",
"text-indent",
"outline-offset",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
}
)
_BARE_ZERO_RE = re.compile(r"(?<![\w.#])0(?![\w.%])")
# The non-``None`` per-property defaults that ``Style.__init__`` used to assign
# one-by-one through the property/decorator machinery (~380 assignments, ~430
# us per ``Style()``). It is now built lazily: the camelCase getters
# (``style_get_decorator``) fall back to this map when a property was never
# set on the instance, so ``el.style.fontSize`` still reports ``"medium"`` etc.
# ``None`` defaults are not listed -- the getter already renders a missing /
# ``None`` value as ``"none"``. Keyed by the camelCase attribute name (the
# getter function name), matching how the old assignments were spelled.
_STYLE_INITIAL_VALUES: dict[str, Any] = {
"alignContent": "normal",
"alignItems": "normal",
"alignSelf": "auto",
"animation": "normal",
"animationDelay": 0,
"animationDirection": "normal",
"animationDuration": 0,
"animationIterationCount": 1,
"animationPlayState": "running",
"animationTimingFunction": "ease",
"backgroundAttachment": "scroll",
"border": "medium none black",
"borderBottom": "medium none black",
"borderBottomLeftRadius": "0px",
"borderBottomRightRadius": "0px",
"borderRadius": "0px",
"borderTopLeftRadius": "0px",
"borderTopRightRadius": "0px",
"columnGap": "normal",
"fontSize": "medium",
"fontStyle": "normal",
"fontWeight": "normal",
"height": "auto",
"justifyContent": "normal",
"left": "auto",
"margin": "0px",
"marginBottom": "0px",
"marginLeft": "0px",
"marginRight": "0px",
"marginTop": "0px",
"overflow": "visible",
"padding": "0px",
"paddingBottom": "0px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"pageBreakAfter": "auto",
"pageBreakBefore": "auto",
"pageBreakInside": "auto",
"right": "auto",
"tableLayout": "auto",
"textAlign": "left",
"textAlignLast": "auto",
"textOverflow": "clip",
"transitionDelay": 0,
"transitionDuration": 0,
"visibility": "visible",
"whiteSpace": "normal",
"width": "auto",
"wordBreak": "normal",
"wordWrap": "normal",
"zIndex": "auto",
}
def _normalize_zero_lengths(property_name: str, value: str) -> str:
if property_name in _ZERO_LENGTH_PROPERTIES and "0" in value:
return _BARE_ZERO_RE.sub("0px", value)
return value
def _set_css_declaration(
entries: list[tuple[str, str, str]],
property_name: str,
value: Any,
priority: str = "",
) -> list[tuple[str, str, str]]:
value = "" if value is None else str(value)
value = _normalize_zero_lengths(property_name, value)
priority = "important" if str(priority or "").lower() == "important" else ""
updated = False
next_entries: list[tuple[str, str, str]] = []
for name, current_value, current_priority in entries:
if name == property_name:
if not updated:
next_entries.append((property_name, value, priority))
updated = True
continue
next_entries.append((name, current_value, current_priority))
if not updated:
next_entries.append((property_name, value, priority))
return next_entries
def _serialize_css_declarations(entries: list[tuple[str, str, str]], compact: bool = False) -> str:
separator = ":" if compact else ": "
joiner = "" if compact else " "
declarations = [
(f"{name}{separator}{value}{joiner}!important;" if priority == "important" else f"{name}{separator}{value};")
for name, value, priority in entries
]
return "".join(declarations) if compact else " ".join(declarations)
def _split_top_level_commas(text: str) -> list[str]:
values: list[str] = []
start = 0
quote = None
escaped = False
depth = 0
for index, char in enumerate(text or ""):
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([":
depth += 1
continue
if char in ")]" and depth > 0:
depth -= 1
continue
if char == "," and depth == 0:
values.append(text[start:index].strip())
start = index + 1
tail = (text or "")[start:].strip()
if tail:
values.append(tail)
return values
def _find_matching_paren(css_text: str, open_index: int) -> int:
quote = None
escaped = False
depth = 0
for index in range(open_index, len(css_text)):
char = css_text[index]
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char == "(":
depth += 1
continue
if char == ")":
depth -= 1
if depth == 0:
return index
return -1
def _split_top_level_word(text: str, word: str) -> tuple[str, str] | None:
quote = None
escaped = False
depth = 0
lower_word = word.lower()
lower_text = (text or "").lower()
for index, char in enumerate(text or ""):
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([":
depth += 1
continue
if char in ")]" and depth > 0:
depth -= 1
continue
if depth != 0 or lower_text[index : index + len(lower_word)] != lower_word:
continue
before = text[index - 1] if index > 0 else " "
after_index = index + len(lower_word)
after = text[after_index] if after_index < len(text) else " "
if before.isspace() and after.isspace():
return text[:index], text[after_index:]
return None
[docs]
class StyleSheet:
"""Base stylesheet object exposed through document-level style collections.
Concrete CSS sheets build on this interface with parsed rules and mutable
declaration content.
"""
def __init__(self) -> None:
# whether the sheet is prevented from applying (spec default: enabled)
self._disabled: bool = False
self.href: str | None = None
self.parentStyleSheet: StyleSheet | None = None
self.ownerNode = None
self.title: str | None = None
self.type: str = "text/css"
self.media: MediaList = MediaList()
@property
def disabled(self) -> bool:
"""Whether this sheet is prevented from contributing to the cascade
(spec default: enabled). A disabled sheet's rules are excluded in
``_build_rule_index``; toggling this also bumps the same stylesheet
epoch ``insertRule``/``deleteRule``/``replace(Sync)`` already bump,
so it invalidates the rule-index and computed-style caches exactly
like any other rule change -- otherwise an element already queried
once would keep serving its pre-toggle computed style forever."""
return self._disabled
@disabled.setter
def disabled(self, value: bool) -> None:
value = bool(value)
if value != self._disabled:
self._disabled = value
_cssom.bump_stylesheet_epoch()
else:
self._disabled = value
# @property
# def href(self):
# """Returns a DOMString representing the location of the stylesheet."""
# return self.href
# @property
# def media(self):
# """Returns a MediaList representing the intended destination medium for style information."""
# raise NotImplementedError
# @property
# def ownerNode(self):
# """Returns a Node associating this style sheet with the current document."""
# raise NotImplementedError
# @property
# def parentStyleSheet(self):
# """Returns a StyleSheet including this one, if any; returns null if there aren't any."""
# raise NotImplementedError
# @property
# def title(self):
# """Returns a DOMString representing the advisory title of the current style sheet."""
# raise NotImplementedError
# @property
# def type(self):
# """Returns a DOMString representing the style sheet language for this style sheet."""
# raise NotImplementedError
[docs]
class StyleSheetList(list):
"""List-like collection returned by ``Document.styleSheets``.
It behaves like an ordered DOM collection while still being convenient to
iterate over as a Python list.
"""
def _populate_stylesheets_from_document(self, doc) -> None:
"""parse the document to find all the stylesheets and add them to the list."""
sheets = doc.querySelectorAll('link[rel="stylesheet"]')
for sheet in sheets:
ss = CSSStyleSheet()
ss._constructed = False
ss.href = sheet.href
ss.ownerNode = sheet
self.append(ss)
styles = doc.querySelectorAll("style")
for style in styles:
ss = CSSStyleSheet()
ss._constructed = False
ss.href = doc.URL
ss.ownerNode = style
ss.replaceSync(style.textContent or "")
self.append(ss)
@property
def length(self) -> int:
"""Returns the number of CSSStyleSheet objects in the collection."""
return len(self)
[docs]
def item(self, index: int) -> CSSStyleSheet | None:
"""Returns the CSSStyleSheet object at the index passed in, or null if no item exists for that index."""
try:
return self[index]
except IndexError:
return None
[docs]
class CSSRule:
"""Base class for CSSOM rules stored inside a stylesheet."""
UNKNOWN_RULE: int = 0
STYLE_RULE: int = 1
CHARSET_RULE: int = 2
IMPORT_RULE: int = 3
MEDIA_RULE: int = 4
FONT_FACE_RULE: int = 5
PAGE_RULE: int = 6
NAMESPACE_RULE: int = 7
KEYFRAMES_RULE: int = 8
KEYFRAME_RULE: int = 9
COUNTER_STYLE_RULE: int = 10
SUPPORTS_RULE: int = 11
FONT_FEATURE_VALUES_RULE: int = 12
VIEWPORT_RULE: int = 13
SUPPORTS_CONDITION_RULE: int = 14
DOCUMENT_RULE: int = 15
COLOR_PROFILE_RULE: int = 16
LAYER_BLOCK_RULE: int = 17
LAYER_STATEMENT_RULE: int = 18
CONTAINER_RULE: int = 19
SCOPE_RULE: int = 20
PROPERTY_RULE: int = 21
NESTED_DECLARATIONS_RULE: int = 22
WHEN_RULE: int = 23
ELSE_RULE: int = 24
# GROUPING_RULES = [FONT_FACE_RULE, MEDIA_RULE, NAMESPACE_RULE, KEYFRAMES_RULE, COUNTER_STYLE_RULE, SUPPORTS_RULE, SUPPORTS_CONDITION_RULE]
def __init__(self):
# self._type = None
self.parentRule: "CSSRule | None" = None
self.parentStyleSheet: "CSSStyleSheet | None" = None
self.type: int | None = None
self._cssText: str | None = None
@property
def cssText(self):
"""Represents the textual representation of the rule, e.g. "h1,h2 { font-size: 16pt }" or "@import 'url'".
Per the CSSOM spec this is read-only; the specialized rule interfaces
compute it from their own properties. The generic ``CSSRule`` fallback
reads the raw text captured in ``_cssText`` at parse time.
"""
return self._cssText or ""
def __str__(self):
return self.cssText
# @property
# def parentRule(self):
# """Returns the containing rule, otherwise null. E.g. if this rule is a style rule inside an @media block,
# the parent rule would be that CSSMediaRule."""
# raise NotImplementedError
# def parentStyleSheet(self):
# """Returns the CSSStyleSheet object for the style sheet that contains this rule"""
# raise NotImplementedError
# def type(self):
# """Returns one of the Type constants to determine which type of rule is represented."""
# raise NotImplementedError
[docs]
class CSSImportRule(CSSRule):
"""The CSSImportRule interface represents an @import rule in a CSS style sheet."""
def __init__(self):
super().__init__()
self.href: str | None = None
self.media: "MediaList | None" = None
self.layerName: str | None = None
self.supportsText: str = ""
self.styleSheet: "CSSStyleSheet | None" = None
self.type = CSSRule.IMPORT_RULE
@property
def cssText(self):
layer = ""
if self.layerName is not None:
layer = " layer" if self.layerName == "" else f" layer({self.layerName})"
supports = f" supports({self.supportsText})" if self.supportsText else ""
media = f" {self.media.mediaText}" if self.media and self.media.mediaText else ""
return f"@import {self.href}{layer}{supports}{media};"
# @property
# def href(self):
# """Returns a DOMString representing the URL of the imported style sheet."""
# raise NotImplementedError
# @property
# def media(self):
# """Returns a MediaList representing the intended destination medium for style information."""
# raise NotImplementedError
# @property
# def styleSheet(self):
# """Returns the CSSStyleSheet object representing the style sheet referenced by this rule."""
# raise NotImplementedError
[docs]
class CSSStyleRule(CSSRule):
"""The CSSStyleRule interface represents a single style rule in a CSS style sheet."""
def __init__(self):
super().__init__()
self.selectorText: str | None = None
self.style: "CSSStyleDeclaration | None" = None
self.cssRules: "CSSRuleList" = CSSRuleList()
self.type = CSSRule.STYLE_RULE
# @property
# def selectorText(self):
# """Returns the textual representation of the rule selector."""
# raise NotImplementedError
# @property
# def style(self):
# """Returns the CSSStyleDeclaration object representing the declaration block of this rule."""
# raise NotImplementedError
@property
def cssText(self):
"""Returns the textual representation of the rule."""
body = self.style.cssText if self.style is not None else ""
children = " ".join(str(rule) for rule in self.cssRules)
if children:
body = f"{body} {children}".strip()
return f"{self.selectorText} {{{body}}}"
# @cssText.setter
# def cssText(self, value):
# """Sets the textual representation of the rule."""
# raise NotImplementedError
[docs]
class CSSFontFaceRule(CSSRule):
"""The CSSFontFaceRule interface represents a @font-face rule in a CSS style sheet."""
def __init__(self):
super().__init__()
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.FONT_FACE_RULE
@property
def cssText(self):
return f"@font-face {{{self.style.cssText if self.style is not None else ''}}}"
# @property
# def style(self):
# """Returns the CSSStyleDeclaration object representing the declaration block of this rule."""
# raise NotImplementedError
# @property
# def selectorText(self):
# """Returns the textual representation of the page selector for the rule."""
# raise NotImplementedError
# @property
# def style(self):
# """Returns the CSSStyleDeclaration object representing the declaration block of this rule."""
# raise NotImplementedError
[docs]
class CSSNamespaceRule(CSSRule):
"""The CSSNamespaceRule interface represents an @namespace rule in a CSS style sheet."""
def __init__(self):
super().__init__()
self.namespaceURI: str | None = None
self.prefix: str | None = None
self.type = CSSRule.NAMESPACE_RULE
@property
def cssText(self):
prefix = f" {self.prefix}" if self.prefix else ""
return f"@namespace{prefix} {self.namespaceURI};"
# @property
# def namespaceURI(self):
# """Returns the namespace URI for this rule."""
# raise NotImplementedError
# @property
# def prefix(self):
# """Returns the namespace prefix for this rule."""
# raise NotImplementedError
[docs]
class CSSKeyframesRule(CSSRule):
"""The CSSKeyframesRule interface represents a @keyframes at-rule."""
def __init__(self):
super().__init__()
self.cssRules: "CSSRuleList" = CSSRuleList()
self.name: str | None = None
self.type = CSSRule.KEYFRAMES_RULE
@property
def cssText(self):
return f"@keyframes {self.name} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
# @property
# def name(self):
# """Returns the name of the keyframes. This is a DOMString containing the unescaped keyframes name."""
# raise NotImplementedError
[docs]
class CSSKeyframeRule(CSSRule):
"""The CSSKeyframeRule interface represents a single @keyframes at-rule."""
def __init__(self):
super().__init__()
self.cssRules: "CSSRuleList" = CSSRuleList()
self.keyText: str | None = None
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.KEYFRAME_RULE
@property
def cssText(self):
return f"{self.keyText} {{{self.style.cssText if self.style is not None else ''}}}"
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
# @property
# def keyText(self):
# """Returns the text of the keyframe rule."""
# raise NotImplementedError
# @property
# def style(self):
# """Returns the CSSStyleDeclaration object representing the declaration block of this rule."""
# raise NotImplementedError
[docs]
class CSSCounterStyleRule(CSSRule):
"""The CSSCounterStyleRule interface represents a @counter-style rule."""
def __init__(self):
super().__init__()
self.name: str | None = None
self.system: str | None = None
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.COUNTER_STYLE_RULE
@property
def cssText(self):
return f"@counter-style {self.name} {{{self.style.cssText if self.style is not None else ''}}}"
# @property
# def name(self):
# """Returns the name of the counter style."""
# raise NotImplementedError
# @property
# def system(self):
# """Returns the system of the counter style."""
# raise NotImplementedError
[docs]
class CSSDocumentRule(CSSRule):
"""The CSSDocumentRule interface represents an @document rule."""
def __init__(self):
super().__init__()
self.cssRules: "CSSRuleList" = CSSRuleList()
self.type = CSSRule.DOCUMENT_RULE
@property
def cssText(self):
return f"@document {{{' '.join(str(rule) for rule in self.cssRules or [])}}}"
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
[docs]
class CSSColorProfileRule(CSSRule):
"""The CSSColorProfileRule interface represents an @color-profile rule."""
def __init__(self):
super().__init__()
self.colorProfile: str | None = None
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.COLOR_PROFILE_RULE
@property
def cssText(self):
return f"@color-profile {self.colorProfile} {{{self.style.cssText if self.style is not None else ''}}}"
# @property
# def colorProfile(self):
# """Returns the textual representation of the rule selector."""
# raise NotImplementedError
[docs]
class CSSFontFeatureValuesRule(CSSRule):
"""The CSSFontFeatureValuesRule interface represents a @font-feature-values rule."""
def __init__(self):
super().__init__()
self.cssRules: "CSSRuleList" = CSSRuleList()
self.type = CSSRule.FONT_FEATURE_VALUES_RULE
@property
def cssText(self):
return f"@font-feature-values {{{' '.join(str(rule) for rule in self.cssRules)}}}"
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
[docs]
class CSSGroupingRule(CSSRule):
"""The CSSGroupingRule interface represents a @grouping rule."""
def __init__(self):
super().__init__()
self.cssRules: "CSSRuleList" = CSSRuleList()
# self.type = CSSRule.GROUPING_RULE
@property
def cssText(self):
return " ".join(str(rule) for rule in self.cssRules)
[docs]
def deleteRule(self, index: int):
"""Deletes the rule at the specified index from this grouping rule."""
from domonic.dom import DOMException
if index < 0 or index >= len(self.cssRules):
raise DOMException(DOMException.INDEX_SIZE_ERR, "Index is out of range.")
del self.cssRules[index]
_cssom.bump_stylesheet_epoch()
[docs]
def insertRule(self, rule: str, index: int | None = None):
"""Inserts a child rule into this grouping rule."""
from domonic.dom import DOMException
rules = _parse_rule_list(self.parentStyleSheet, _strip_css_comments(rule), self)
if len(rules) == 0:
raise DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Invalid CSS rule.")
if len(rules) > 1:
raise DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Only one rule is allowed.")
if index is None:
index = len(self.cssRules)
if index < 0 or index > len(self.cssRules):
raise DOMException(DOMException.INDEX_SIZE_ERR, "Index is out of range.")
self.cssRules.insert(index, rules[0])
_cssom.bump_stylesheet_epoch()
return index
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
[docs]
class CSSConditionRule(CSSGroupingRule):
"""The CSSConditionRule interface represents a condition rule."""
def __init__(self):
super().__init__()
self.conditionText: str | None = None
self.cssRules: "CSSRuleList" = CSSRuleList()
# self.type = CSSRule.CONDITIONAL_RULE
# @property
# def conditionText(self):
# """Returns the textual representation of the condition sub-rule."""
# raise NotImplementedError
# @property
# def cssRules(self):
# """Returns the CSSRuleList object representing the CSS rules in the media rule."""
# raise NotImplementedError
[docs]
class CSSSupportsRule(CSSConditionRule):
"""The CSSSupportsRule interface represents a @supports at-rule."""
def __init__(self):
super().__init__()
self.conditionText: str | None = None
self.cssRules: "CSSRuleList" = CSSRuleList()
self.type = CSSRule.SUPPORTS_RULE
@property
def cssText(self):
return f"@supports {self.conditionText} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSSupportsConditionRule(CSSGroupingRule):
"""The CSSSupportsConditionRule interface represents a @supports-condition rule."""
def __init__(self):
super().__init__()
self.name: str = ""
self.type = CSSRule.SUPPORTS_CONDITION_RULE
@property
def cssText(self):
return f"@supports-condition {self.name} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSWhenRule(CSSConditionRule):
"""Represents a CSS Conditional Level 5 @when rule."""
def __init__(self):
super().__init__()
self.conditionText: str = ""
self.type = CSSRule.WHEN_RULE
@property
def cssText(self):
return f"@when {self.conditionText} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSElseRule(CSSConditionRule):
"""Represents a CSS Conditional Level 5 @else rule."""
def __init__(self):
super().__init__()
self.conditionText: str = ""
self.type = CSSRule.ELSE_RULE
@property
def cssText(self):
condition = f" {self.conditionText}" if self.conditionText else ""
return f"@else{condition} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSContainerRule(CSSConditionRule):
"""The CSSContainerRule interface represents a @container rule."""
def __init__(self):
super().__init__()
self.containerName: str = ""
self.containerQuery: str = ""
self.conditions: list[dict[str, str]] = []
self.type = CSSRule.CONTAINER_RULE
@property
def cssText(self):
condition = self.conditionText or _serialize_container_conditions(self.conditions)
prelude = f" {condition}" if condition else ""
return f"@container{prelude} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSScopeRule(CSSGroupingRule):
"""The CSSScopeRule interface represents a @scope rule."""
def __init__(self):
super().__init__()
self.start: str | None = None
self.end: str | None = None
self.type = CSSRule.SCOPE_RULE
@property
def cssText(self):
start = f" ({self.start})" if self.start else ""
end = f" to ({self.end})" if self.end else ""
return f"@scope{start}{end} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSLayerBlockRule(CSSGroupingRule):
"""The CSSLayerBlockRule interface represents a @layer block rule."""
def __init__(self):
super().__init__()
self.name: str = ""
self.type = CSSRule.LAYER_BLOCK_RULE
@property
def cssText(self):
name = f" {self.name}" if self.name else ""
return f"@layer{name} {{{' '.join(str(rule) for rule in self.cssRules)}}}"
[docs]
class CSSLayerStatementRule(CSSRule):
"""The CSSLayerStatementRule interface represents a statement-style @layer rule."""
def __init__(self):
super().__init__()
self.nameList: list[str] = []
self.type = CSSRule.LAYER_STATEMENT_RULE
@property
def cssText(self):
return f"@layer {', '.join(self.nameList)};"
[docs]
class CSSPropertyRule(CSSRule):
"""The CSSPropertyRule interface represents a Houdini ``@property`` rule."""
def __init__(self):
super().__init__()
self.name: str = ""
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.PROPERTY_RULE
def _descriptor(self, name: str, default: str = "") -> str:
if self.style is None:
return default
value = self.style.getPropertyValue(name)
return value if value else default
@property
def syntax(self) -> str:
return self._descriptor("syntax", '"*"')
@property
def inherits(self) -> bool:
return self._descriptor("inherits").strip().lower() == "true"
@property
def initialValue(self) -> str | None:
return self._descriptor("initial-value") or None
def _register(self) -> None:
"""Feed this rule into the shared registered-property table so the
cascade honours its inheritance flag and initial value."""
if not self.name.startswith("--"):
return
syntax = self.syntax.strip().strip("\"'") or "*"
_cssom.register_property(
self.name,
syntax=syntax,
inherits=self.inherits,
initial_value=self.initialValue,
)
@property
def cssText(self):
return f"@property {self.name} {{{self.style.cssText if self.style is not None else ''}}}"
[docs]
class CSSNestedDeclarations(CSSRule):
"""The CSSNestedDeclarations interface represents declarations mixed with nested rules."""
def __init__(self):
super().__init__()
self.style: "CSSStyleDeclaration | None" = None
self.type = CSSRule.NESTED_DECLARATIONS_RULE
@property
def cssText(self):
return self.style.cssText if self.style is not None else ""
[docs]
class CSSRuleList(list):
"""A CSSRuleList represents an ordered collection of read-only CSSRule objects.
While the CSSRuleList object is read-only, and cannot be directly modified,
it is considered a live object, as the content can change over time.
"""
@property
def length(self) -> int:
"""Returns an integer representing the number of CSSRule objects in the collection."""
return len(self)
[docs]
def item(self, index: int) -> "CSSRule | None":
"""Gets a single CSSRule."""
try:
return self[index]
except IndexError:
return None
#: an XHTML ``<style>`` element's text wrapped in exactly one CDATA section
#: (``<![CDATA[ ... ]]>``) -- valid, common XHTML practice for keeping a
#: stylesheet well-formed as XML. domonic's HTML parser has no CDATA-section
#: mode, so the markers survive as literal text in the element's
#: ``textContent`` instead of being stripped as markup; ordinary CSS never
#: both starts with ``<![CDATA[`` and ends with ``]]>``, so stripping it here
#: is safe.
_CDATA_WRAPPER_RE = re.compile(r"^\s*<!\[CDATA\[(.*)\]\]>\s*$", re.S)
def _strip_cdata_wrapper(text: str) -> str:
match = _CDATA_WRAPPER_RE.match(text)
return match.group(1) if match else text
[docs]
class CSSStyleSheet(StyleSheet):
"""Creates a new CSSStyleSheet object."""
def __init__(self, options: "dict[str, Any] | None" = None) -> None:
super().__init__()
self.rules: list[CSSRule] = []
self.cssRules: CSSRuleList = CSSRuleList()
self.ownerRule: CSSRule | None = None
# constructed sheets (``new CSSStyleSheet(...)``) accept an options bag
# and are the only sheets ``replace`` / ``replaceSync`` may target
self._constructed = True
if isinstance(options, dict):
if options.get("media") is not None:
media = options["media"]
self.media = media if isinstance(media, MediaList) else MediaList(str(media).split(","))
self.disabled = bool(options.get("disabled", False))
if options.get("baseURL") is not None:
self.href = str(options["baseURL"])
# @property
# def cssRules(): # -> 'CSSStyleRuleList':
# """Returns a live CSSRuleList which maintains an up-to-date list of the CSSRule objects
# that comprise the stylesheet."""
# # return CSSStyleRuleList()
# raise NotImplementedError
# @property
# def ownerRule(self):
# """If this stylesheet is imported into the document using an @import rule,
# the ownerRule property returns the corresponding CSSImportRule; otherwise, this property's value is null."""
# raise NotImplementedError
[docs]
def deleteRule(self, index: int):
"""Deletes the rule at the specified index into the stylesheet's rule list."""
from domonic.dom import DOMException
if index < 0 or index >= len(self.cssRules):
raise DOMException(DOMException.INDEX_SIZE_ERR, "Index is out of range.")
del self.cssRules[index]
self.rules = self.cssRules
_cssom.bump_stylesheet_epoch()
[docs]
def insertRule(self, rule: str, index: int | None = None):
"""Inserts a new rule at the specified position in the stylesheet,
given the textual representation of the rule."""
from domonic.dom import DOMException
rules = CSSParser.parseFromString(self, rule)
if len(rules) == 0:
raise DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Invalid CSS rule.")
if len(rules) > 1:
raise DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Only one rule is allowed.")
if index is None:
index = len(self.cssRules)
if index < 0 or index > len(self.cssRules):
raise DOMException(DOMException.INDEX_SIZE_ERR, "Index is out of range.")
self.cssRules.insert(index, rules[0])
self.rules = self.cssRules
_cssom.bump_stylesheet_epoch()
return index
[docs]
def replace(self, text: str):
"""Asynchronously replaces the content of the stylesheet and returns
a Promise that resolves with the updated CSSStyleSheet."""
self.replaceSync(text)
from domonic.javascript import Promise
return Promise(lambda resolve, reject: resolve(self))
[docs]
def replaceSync(self, text: str):
"""Synchronously replaces the content of the stylesheet."""
self.cssRules = CSSParser.parseFromString(self, _strip_cdata_wrapper(text or ""))
self.rules = self.cssRules
_cssom.bump_stylesheet_epoch()
# @property
# def rules(self):
# """The rules property is functionally identical to the standard cssRules property
# it returns a live CSSRuleList which maintains an up-to-date list of all of the rules in the style sheet.
# """
# raise NotImplementedError
# Legacy methods
[docs]
def addRule(self, selectorText: str, style: str, index: int | None = None):
"""Adds a new rule to the stylesheet given the selector to which the style applies and the style block to apply
to the matching elements.
This differs from insertRule(), which takes the textual representation of the entire rule as a single string.
"""
return self.insertRule(f"{selectorText} {{{style}}}", index)
[docs]
def removeRule(self, index: int):
"""Functionally identical to deleteRule();
removes the rule at the specified index from the stylesheet's rule list."""
return self.deleteRule(index)
def __str__(self):
# converts the rules to css code
return "".join([str(rule) for rule in self.rules])
[docs]
class Style:
"""JavaScript-style accessors for CSS declarations."""
def __init__(self, parent_node=None):
self._members_checked = False
self._parent_node = parent_node # so I can update a tags returned style attributes if a style gets set
self._members_checked = True # NOTE - this ALWAYS needs to be last or all props will render
def __setattr__(self, name: str, value: Any) -> None:
descriptor = getattr(type(self), name, None)
if isinstance(descriptor, property) and descriptor.fset is not None:
super().__setattr__(name, value)
return
super().__setattr__(name, value)
if not self._should_sync_style_name(name):
return
self._set_parent_style_property(name, "none" if value is None else value)
def _should_sync_style_name(self, name: str) -> bool:
if name in _CSS_STYLE_INTERNAL_NAMES or name.startswith("_"):
return False
if getattr(self, "_suspend_style_sync", False):
return False
if not getattr(self, "_members_checked", False):
return False
return getattr(self, "_parent_node", None) is not None
def _set_parent_style_property(self, name: str, value: Any, priority: str = "") -> None:
if self._parent_node is None:
return
property_name = _css_property_name(name)
entries = _parse_css_declarations(self._parent_node.getAttribute("style") or "")
# Assigning "" (or None) to a single property removes its declaration,
# matching ``el.style.color = ""`` in a browser -- rather than emitting a
# dangling ``color: ;``.
removing = value is None or str(value) == ""
if removing:
entries = [entry for entry in entries if entry[0] != property_name]
else:
entries = _set_css_declaration(entries, property_name, value, priority)
# Normalised "prop: value; " form, the same as ``setProperty`` and a
# browser's -- assigning through ``el.style`` rewrites the attribute.
css_text = _serialize_css_declarations(entries)
object.__setattr__(self._parent_node, "_syncing_style_attr", True)
try:
self._parent_node.setAttribute("style", css_text)
finally:
object.__setattr__(self._parent_node, "_syncing_style_attr", False)
if hasattr(self, "_css_text"):
object.__setattr__(self, "_css_text", css_text)
if hasattr(self, "_declared_properties") and not property_name.startswith("--"):
declared_properties = set(getattr(self, "_declared_properties", set()))
attribute_name = _css_attribute_name(property_name)
if removing:
declared_properties.discard(attribute_name)
else:
declared_properties.add(attribute_name)
object.__setattr__(self, "_declared_properties", declared_properties)
[docs]
@staticmethod
def style_set_decorator(func: Callable[..., Any]) -> Callable[..., Any]:
from functools import wraps
@wraps(func)
def style_wrapper(self, *args, **kwargs):
value = args[0]
if value is None:
value = "none"
elif _css_property_name(func.__name__) in _ZERO_LENGTH_PROPERTIES:
normalized = _normalize_zero_lengths(_css_property_name(func.__name__), str(value))
if normalized != str(value):
value = normalized
args = (value,) + tuple(args[1:])
func(self, value, *args, **kwargs)
# to only render to the Node what gets set. allows init to run first
# note - this was a counter prior to 0.8.3 and is now a bool as counter stops custom props on extended classes
if not self._members_checked:
return
if getattr(self, "_suspend_style_sync", False):
return
# A shorthand assigned through the IDL (``el.style.margin = ...``)
# must expand to its longhands, exactly like ``setProperty`` -- so
# route it there when available (CSSStyleDeclaration).
kebab = _css_property_name(func.__name__)
if _cssom.is_shorthand(kebab) and hasattr(self, "setProperty") and str(value) not in ("", "none"):
self.setProperty(kebab, value)
return
self._set_parent_style_property(func.__name__, value)
return style_wrapper
[docs]
@staticmethod
def style_get_decorator(func: Callable[..., Any]) -> Callable[..., Any]:
from functools import wraps
@wraps(func)
def style_wrapper(value=None, *args, **kwargs):
self = value
kebab = _css_property_name(func.__name__)
try:
result = func(value, *args, **kwargs)
except AttributeError:
# The per-property private attribute was never set on this
# instance. Two reasons this happens by design:
# * ``Style.__init__`` no longer assigns all ~380 defaults
# one-by-one (it was ~430 us per ``Style()``); an unset
# property resolves lazily here instead.
# * ``ComputedStyleDeclaration`` skips ``Style.__init__``
# entirely and serves every read through the cascade.
# Prefer a real declared / computed value from the CSSOM read
# surface (so ``getComputedStyle(el).color`` matches
# ``.getPropertyValue("color")``); otherwise fall back to the
# property's initial value, exactly what ``__init__`` used to
# pre-seed (``fontSize`` -> ``"medium"``, most -> ``"none"``).
if hasattr(self, "getPropertyValue"):
got = self.getPropertyValue(kebab)
if got:
return got
return _STYLE_INITIAL_VALUES.get(func.__name__, "none")
# a shorthand (``el.style.margin``) reflects the declaration block:
# ``getPropertyValue`` returns an explicitly-set shorthand or
# reconstructs one from longhands set individually
if _cssom.is_shorthand(kebab) and hasattr(self, "getPropertyValue"):
rebuilt = self.getPropertyValue(kebab)
if rebuilt:
return rebuilt
if result is None:
result = "none"
return result
return style_wrapper
@property
@style_get_decorator
def alignContent(self):
return self.__alignContent
@alignContent.setter
@style_set_decorator
def alignContent(self, value="stretch", *args, **kwargs):
self.__alignContent = value
@property
@style_get_decorator
def alignItems(self):
return self.__alignItems
@alignItems.setter
@style_set_decorator
def alignItems(self, value=None, *args, **kwargs):
self.__alignItems = value
@property
@style_get_decorator
def alignSelf(self):
return self.__alignSelf
@alignSelf.setter
@style_set_decorator
def alignSelf(self, value=None, *args, **kwargs):
self.__alignSelf = value
@property
@style_get_decorator
def animation(self):
return self.__animation
@animation.setter
@style_set_decorator
def animation(self, value=None, *args, **kwargs):
self.__animation = value
@property
@style_get_decorator
def animationDelay(self):
return self.__animationDelay
@animationDelay.setter
@style_set_decorator
def animationDelay(self, value=None, *args, **kwargs):
self.__animationDelay = value
@property
@style_get_decorator
def animationDirection(self):
return self.__animationDirection
@animationDirection.setter
@style_set_decorator
def animationDirection(self, value=None, *args, **kwargs):
self.__animationDirection = value
@property
@style_get_decorator
def animationDuration(self):
return self.__animationDuration
@animationDuration.setter
@style_set_decorator
def animationDuration(self, value=None, *args, **kwargs):
self.__animationDuration = value
@property
@style_get_decorator
def animationFillMode(self):
return self.__animationFillMode
@animationFillMode.setter
@style_set_decorator
def animationFillMode(self, value=None, *args, **kwargs):
self.__animationFillMode = value
@property
@style_get_decorator
def animationIterationCount(self):
return self.__animationIterationCount
@animationIterationCount.setter
@style_set_decorator
def animationIterationCount(self, value=None, *args, **kwargs):
self.__animationIterationCount = value
@property
@style_get_decorator
def animationName(self):
return self.__animationName
@animationName.setter
@style_set_decorator
def animationName(self, value=None, *args, **kwargs):
self.__animationName = value
@property
@style_get_decorator
def animationTimingFunction(self):
return self.__animationTimingFunction
@animationTimingFunction.setter
@style_set_decorator
def animationTimingFunction(self, value=None, *args, **kwargs):
self.__animationTimingFunction = value
@property
@style_get_decorator
def animationPlayState(self):
return self.__animationPlayState
@animationPlayState.setter
@style_set_decorator
def animationPlayState(self, value=None, *args, **kwargs):
self.__animationPlayState = value
@property
@style_get_decorator
def background(self):
return self.__background
@background.setter
@style_set_decorator
def background(self, value=None, *args, **kwargs):
self.__background = value
@property
@style_get_decorator
def backgroundAttachment(self):
return self.__backgroundAttachment
@backgroundAttachment.setter
@style_set_decorator
def backgroundAttachment(self, value=None, *args, **kwargs):
self.__backgroundAttachment = value
@property
@style_get_decorator
def backgroundColor(self):
return self.__backgroundColor
@backgroundColor.setter
@style_set_decorator
def backgroundColor(self, value=None, *args, **kwargs):
self.__backgroundColor = value
@property
@style_get_decorator
def backgroundImage(self):
return self.__backgroundImage
@backgroundImage.setter
@style_set_decorator
def backgroundImage(self, value=None, *args, **kwargs):
self.__backgroundImage = value
@property
@style_get_decorator
def backgroundPosition(self):
return self.__backgroundPosition
@backgroundPosition.setter
@style_set_decorator
def backgroundPosition(self, value=None, *args, **kwargs):
self.__backgroundPosition = value
@property
@style_get_decorator
def backgroundRepeat(self):
return self.__backgroundRepeat
@backgroundRepeat.setter
@style_set_decorator
def backgroundRepeat(self, value=None, *args, **kwargs):
self.__backgroundRepeat = value
@property
@style_get_decorator
def backgroundClip(self):
return self.__backgroundClip
@backgroundClip.setter
@style_set_decorator
def backgroundClip(self, value=None, *args, **kwargs):
self.__backgroundClip = value
@property
@style_get_decorator
def backgroundOrigin(self):
return self.__backgroundOrigin
@backgroundOrigin.setter
@style_set_decorator
def backgroundOrigin(self, value=None, *args, **kwargs):
self.__backgroundOrigin = value
@property
@style_get_decorator
def backgroundSize(self):
return self.__backgroundSize
@backgroundSize.setter
@style_set_decorator
def backgroundSize(self, value=None, *args, **kwargs):
self.__backgroundSize = value
@property
@style_get_decorator
def backfaceVisibility(self):
return self.__backfaceVisibility
@backfaceVisibility.setter
@style_set_decorator
def backfaceVisibility(self, value=None, *args, **kwargs):
self.__backfaceVisibility = value
@property
@style_get_decorator
def border(self):
return self.__border
@border.setter
@style_set_decorator
def border(self, value=None, *args, **kwargs):
self.__border = value
@property
@style_get_decorator
def borderBottom(self):
return self.__borderBottom
@borderBottom.setter
@style_set_decorator
def borderBottom(self, value=None, *args, **kwargs):
self.__borderBottom = value
@property
@style_get_decorator
def borderBottomColor(self):
return self.__borderBottomColor
@borderBottomColor.setter
@style_set_decorator
def borderBottomColor(self, value=None, *args, **kwargs):
self.__borderBottomColor = value
@property
@style_get_decorator
def borderBottomLeftRadius(self):
return self.__borderBottomLeftRadius
@borderBottomLeftRadius.setter
@style_set_decorator
def borderBottomLeftRadius(self, value=None, *args, **kwargs):
self.__borderBottomLeftRadius = value
@property
@style_get_decorator
def borderBottomRightRadius(self):
return self.__borderBottomRightRadius
@borderBottomRightRadius.setter
@style_set_decorator
def borderBottomRightRadius(self, value=None, *args, **kwargs):
self.__borderBottomRightRadius = value
@property
@style_get_decorator
def borderBottomStyle(self):
return self.__borderBottomStyle
@borderBottomStyle.setter
@style_set_decorator
def borderBottomStyle(self, value=None, *args, **kwargs):
self.__borderBottomStyle = value
@property
@style_get_decorator
def borderBottomWidth(self):
return self.__borderBottomWidth
@borderBottomWidth.setter
@style_set_decorator
def borderBottomWidth(self, value=None, *args, **kwargs):
self.__borderBottomWidth = value
@property
@style_get_decorator
def borderCollapse(self):
return self.__borderCollapse
@borderCollapse.setter
@style_set_decorator
def borderCollapse(self, value=None, *args, **kwargs):
self.__borderCollapse = value
@property
@style_get_decorator
def borderColor(self):
return self.__borderColor
@borderColor.setter
@style_set_decorator
def borderColor(self, value=None, *args, **kwargs):
self.__borderColor = value
@property
@style_get_decorator
def borderImage(self):
return self.__borderImage
@borderImage.setter
@style_set_decorator
def borderImage(self, value=None, *args, **kwargs):
self.__borderImage = value
@property
@style_get_decorator
def borderImageOutset(self):
return self.__borderImageOutset
@borderImageOutset.setter
@style_set_decorator
def borderImageOutset(self, value=None, *args, **kwargs):
self.__borderImageOutset = value
@property
@style_get_decorator
def borderImageRepeat(self):
return self.__borderImageRepeat
@borderImageRepeat.setter
@style_set_decorator
def borderImageRepeat(self, value=None, *args, **kwargs):
self.__borderImageRepeat = value
@property
@style_get_decorator
def borderImageSlice(self):
return self.__borderImageSlice
@borderImageSlice.setter
@style_set_decorator
def borderImageSlice(self, value=None, *args, **kwargs):
self.__borderImageSlice = value
@property
@style_get_decorator
def borderImageSource(self):
return self.__borderImageSource
@borderImageSource.setter
@style_set_decorator
def borderImageSource(self, value=None, *args, **kwargs):
self.__borderImageSource = value
@property
@style_get_decorator
def borderImageWidth(self):
return self.__borderImageWidth
@borderImageWidth.setter
@style_set_decorator
def borderImageWidth(self, value=None, *args, **kwargs):
self.__borderImageWidth = value
@property
@style_get_decorator
def borderLeft(self):
return self.__borderLeft
@borderLeft.setter
@style_set_decorator
def borderLeft(self, value=None, *args, **kwargs):
self.__borderLeft = value
@property
@style_get_decorator
def borderLeftColor(self):
return self.__borderLeftColor
@borderLeftColor.setter
@style_set_decorator
def borderLeftColor(self, value=None, *args, **kwargs):
self.__borderLeftColor = value
@property
@style_get_decorator
def borderLeftStyle(self):
return self.__borderLeftStyle
@borderLeftStyle.setter
@style_set_decorator
def borderLeftStyle(self, value=None, *args, **kwargs):
self.__borderLeftStyle = value
@property
@style_get_decorator
def borderLeftWidth(self):
return self.__borderLeftWidth
@borderLeftWidth.setter
@style_set_decorator
def borderLeftWidth(self, value=None, *args, **kwargs):
self.__borderLeftWidth = value
@property
@style_get_decorator
def borderRadius(self):
return self.__borderRadius
@borderRadius.setter
@style_set_decorator
def borderRadius(self, value=None, *args, **kwargs):
self.__borderRadius = value
@property
@style_get_decorator
def borderRight(self):
return self.__borderRight
@borderRight.setter
@style_set_decorator
def borderRight(self, value=None, *args, **kwargs):
self.__borderRight = value
@property
@style_get_decorator
def borderRightColor(self):
return self.__borderRightColor
@borderRightColor.setter
@style_set_decorator
def borderRightColor(self, value=None, *args, **kwargs):
self.__borderRightColor = value
@property
@style_get_decorator
def borderRightStyle(self):
return self.__borderRightStyle
@borderRightStyle.setter
@style_set_decorator
def borderRightStyle(self, value=None, *args, **kwargs):
self.__borderRightStyle = value
@property
@style_get_decorator
def borderRightWidth(self):
return self.__borderRightWidth
@borderRightWidth.setter
@style_set_decorator
def borderRightWidth(self, value=None, *args, **kwargs):
self.__borderRightWidth = value
@property
@style_get_decorator
def borderSpacing(self):
return self.__borderSpacing
@borderSpacing.setter
@style_set_decorator
def borderSpacing(self, value=None, *args, **kwargs):
self.__borderSpacing = value
@property
@style_get_decorator
def borderStyle(self):
return self.__borderStyle
@borderStyle.setter
@style_set_decorator
def borderStyle(self, value=None, *args, **kwargs):
self.__borderStyle = value
@property
@style_get_decorator
def borderTop(self):
return self.__borderTop
@borderTop.setter
@style_set_decorator
def borderTop(self, value=None, *args, **kwargs):
self.__borderTop = value
@property
@style_get_decorator
def borderTopColor(self):
return self.__borderTopColor
@borderTopColor.setter
@style_set_decorator
def borderTopColor(self, value=None, *args, **kwargs):
self.__borderTopColor = value
@property
@style_get_decorator
def borderTopLeftRadius(self):
return self.__borderTopLeftRadius
@borderTopLeftRadius.setter
@style_set_decorator
def borderTopLeftRadius(self, value=None, *args, **kwargs):
self.__borderTopLeftRadius = value
@property
@style_get_decorator
def borderTopRightRadius(self):
return self.__borderTopRightRadius
@borderTopRightRadius.setter
@style_set_decorator
def borderTopRightRadius(self, value=None, *args, **kwargs):
self.__borderTopRightRadius = value
@property
@style_get_decorator
def borderTopStyle(self):
return self.__borderTopStyle
@borderTopStyle.setter
@style_set_decorator
def borderTopStyle(self, value=None, *args, **kwargs):
self.__borderTopStyle = value
@property
@style_get_decorator
def borderTopWidth(self):
return self.__borderTopWidth
@borderTopWidth.setter
@style_set_decorator
def borderTopWidth(self, value=None, *args, **kwargs):
self.__borderTopWidth = value
@property
@style_get_decorator
def borderWidth(self):
return self.__borderWidth
@borderWidth.setter
@style_set_decorator
def borderWidth(self, value=None, *args, **kwargs):
self.__borderWidth = value
@property
@style_get_decorator
def bottom(self):
return self.__bottom
@bottom.setter
@style_set_decorator
def bottom(self, value=None, *args, **kwargs):
self.__bottom = value
@property
@style_get_decorator
def boxDecorationBreak(self):
return self.__boxDecorationBreak
@boxDecorationBreak.setter
@style_set_decorator
def boxDecorationBreak(self, value=None, *args, **kwargs):
self.__boxDecorationBreak = value
@property
@style_get_decorator
def boxShadow(self):
return self.__boxShadow
@boxShadow.setter
@style_set_decorator
def boxShadow(self, value=None, *args, **kwargs):
self.__boxShadow = value
@property
@style_get_decorator
def boxSizing(self):
return self.__boxSizing
@boxSizing.setter
@style_set_decorator
def boxSizing(self, value=None, *args, **kwargs):
self.__boxSizing = value
@property
@style_get_decorator
def captionSide(self):
return self.__captionSide
@captionSide.setter
@style_set_decorator
def captionSide(self, value=None, *args, **kwargs):
self.__captionSide = value
@property
@style_get_decorator
def clear(self):
return self.__clear
@clear.setter
@style_set_decorator
def clear(self, value=None, *args, **kwargs):
self.__clear = value
@property
@style_get_decorator
def clip(self):
return self.__clip
@clip.setter
@style_set_decorator
def clip(self, value=None, *args, **kwargs):
self.__clip = value
@property
@style_get_decorator
def color(self):
return self.__color
@color.setter
@style_set_decorator
def color(self, value=None, *args, **kwargs):
self.__color = value
@property
@style_get_decorator
def columnCount(self):
return self.__columnCount
@columnCount.setter
@style_set_decorator
def columnCount(self, value=None, *args, **kwargs):
self.__columnCount = value
@property
@style_get_decorator
def columnFill(self):
return self.__columnFill
@columnFill.setter
@style_set_decorator
def columnFill(self, value=None, *args, **kwargs):
self.__columnFill = value
@property
@style_get_decorator
def columnGap(self):
return self.__columnGap
@columnGap.setter
@style_set_decorator
def columnGap(self, value=None, *args, **kwargs):
self.__columnGap = value
@property
@style_get_decorator
def columnRule(self):
return self.__columnRule
@columnRule.setter
@style_set_decorator
def columnRule(self, value=None, *args, **kwargs):
self.__columnRule = value
@property
@style_get_decorator
def columnRuleColor(self):
return self.__columnRuleColor
@columnRuleColor.setter
@style_set_decorator
def columnRuleColor(self, value=None, *args, **kwargs):
self.__columnRuleColor = value
@property
@style_get_decorator
def columnRuleStyle(self):
return self.__columnRuleStyle
@columnRuleStyle.setter
@style_set_decorator
def columnRuleStyle(self, value=None, *args, **kwargs):
self.__columnRuleStyle = value
@property
@style_get_decorator
def columnRuleWidth(self):
return self.__columnRuleWidth
@columnRuleWidth.setter
@style_set_decorator
def columnRuleWidth(self, value=None, *args, **kwargs):
self.__columnRuleWidth = value
@property
@style_get_decorator
def columns(self):
return self.__columns
@columns.setter
@style_set_decorator
def columns(self, value=None, *args, **kwargs):
self.__columns = value
@property
@style_get_decorator
def columnSpan(self):
return self.__columnSpan
@columnSpan.setter
@style_set_decorator
def columnSpan(self, value=None, *args, **kwargs):
self.__columnSpan = value
@property
@style_get_decorator
def columnWidth(self):
return self.__columnWidth
@columnWidth.setter
@style_set_decorator
def columnWidth(self, value=None, *args, **kwargs):
self.__columnWidth = value
@property
@style_get_decorator
def content(self):
return self.__content
@content.setter
@style_set_decorator
def content(self, value=None, *args, **kwargs):
self.__content = value
@property
@style_get_decorator
def counterIncrement(self):
return self.__counterIncrement
@counterIncrement.setter
@style_set_decorator
def counterIncrement(self, value=None, *args, **kwargs):
self.__counterIncrement = value
@property
@style_get_decorator
def counterReset(self):
return self.__counterReset
@counterReset.setter
@style_set_decorator
def counterReset(self, value=None, *args, **kwargs):
self.__counterReset = value
@property
@style_get_decorator
def cursor(self):
return self.__cursor
@cursor.setter
@style_set_decorator
def cursor(self, value=None, *args, **kwargs):
self.__cursor = value
@property
@style_get_decorator
def direction(self):
return self.__direction
@direction.setter
@style_set_decorator
def direction(self, value=None, *args, **kwargs):
self.__direction = value
@property
@style_get_decorator
def display(self):
return self.__display
@display.setter
@style_set_decorator
def display(self, value=None, *args, **kwargs):
self.__display = value
@property
@style_get_decorator
def emptyCells(self):
return self.__emptyCells
@emptyCells.setter
@style_set_decorator
def emptyCells(self, value=None, *args, **kwargs):
self.__emptyCells = value
@property
@style_get_decorator
def filter(self):
return self.__filter
@filter.setter
@style_set_decorator
def filter(self, value=None, *args, **kwargs):
self.__filter = value
@property
@style_get_decorator
def flex(self):
return self.__flex
@flex.setter
@style_set_decorator
def flex(self, value=None, *args, **kwargs):
self.__flex = value
@property
@style_get_decorator
def flexBasis(self):
return self.__flexBasis
@flexBasis.setter
@style_set_decorator
def flexBasis(self, value=None, *args, **kwargs):
self.__flexBasis = value
@property
@style_get_decorator
def flexDirection(self):
return self.__flexDirection
@flexDirection.setter
@style_set_decorator
def flexDirection(self, value=None, *args, **kwargs):
self.__flexDirection = value
@property
@style_get_decorator
def flexFlow(self):
return self.__flexFlow
@flexFlow.setter
@style_set_decorator
def flexFlow(self, value=None, *args, **kwargs):
self.__flexFlow = value
@property
@style_get_decorator
def flexGrow(self):
return self.__flexGrow
@flexGrow.setter
@style_set_decorator
def flexGrow(self, value=None, *args, **kwargs):
self.__flexGrow = value
@property
@style_get_decorator
def flexShrink(self):
return self.__flexShrink
@flexShrink.setter
@style_set_decorator
def flexShrink(self, value=None, *args, **kwargs):
self.__flexShrink = value
@property
@style_get_decorator
def flexWrap(self):
return self.__flexWrap
@flexWrap.setter
@style_set_decorator
def flexWrap(self, value=None, *args, **kwargs):
self.__flexWrap = value
@property
@style_get_decorator
def float(self):
return self.__float
@float.setter
@style_set_decorator
def float(self, value=None, *args, **kwargs):
self.__float = value
self.__cssFloat = value
@property
@style_get_decorator
def cssFloat(self):
return self.__cssFloat
@cssFloat.setter
@style_set_decorator
def cssFloat(self, value=None, *args, **kwargs):
self.__cssFloat = value
self.__float = value
@property
@style_get_decorator
def font(self):
return self.__font
@font.setter
@style_set_decorator
def font(self, value=None, *args, **kwargs):
self.__font = value
@property
@style_get_decorator
def fontFamily(self):
return self.__fontFamily
@fontFamily.setter
@style_set_decorator
def fontFamily(self, value=None, *args, **kwargs):
self.__fontFamily = value
@property
@style_get_decorator
def fontSize(self):
return self.__fontSize
@fontSize.setter
@style_set_decorator
def fontSize(self, value=None, *args, **kwargs):
self.__fontSize = value
@property
@style_get_decorator
def fontStyle(self):
return self.__fontStyle
@fontStyle.setter
@style_set_decorator
def fontStyle(self, value=None, *args, **kwargs):
self.__fontStyle = value
@property
@style_get_decorator
def fontVariant(self):
return self.__fontVariant
@fontVariant.setter
@style_set_decorator
def fontVariant(self, value=None, *args, **kwargs):
self.__fontVariant = value
@property
@style_get_decorator
def fontWeight(self):
return self.__fontWeight
@fontWeight.setter
@style_set_decorator
def fontWeight(self, value=None, *args, **kwargs):
self.__fontWeight = value
@property
@style_get_decorator
def fontSizeAdjust(self):
return self.__fontSizeAdjust
@fontSizeAdjust.setter
@style_set_decorator
def fontSizeAdjust(self, value=None, *args, **kwargs):
self.__fontSizeAdjust = value
@property
@style_get_decorator
def fontStretch(self):
return self.__fontStretch
@fontStretch.setter
@style_set_decorator
def fontStretch(self, value=None, *args, **kwargs):
self.__fontStretch = value
@property
@style_get_decorator
def hangingPunctuation(self):
return self.__hangingPunctuation
@hangingPunctuation.setter
@style_set_decorator
def hangingPunctuation(self, value=None, *args, **kwargs):
self.__hangingPunctuation = value
@property
@style_get_decorator
def height(self):
return self.__height
@height.setter
@style_set_decorator
def height(self, value=None, *args, **kwargs):
self.__height = value
@property
@style_get_decorator
def hyphens(self):
return self.__hyphens
@hyphens.setter
@style_set_decorator
def hyphens(self, value=None, *args, **kwargs):
self.__hyphens = value
@property
@style_get_decorator
def icon(self):
return self.__icon
@icon.setter
@style_set_decorator
def icon(self, value=None, *args, **kwargs):
self.__icon = value
@property
@style_get_decorator
def imageOrientation(self):
return self.__imageOrientation
@imageOrientation.setter
@style_set_decorator
def imageOrientation(self, value=None, *args, **kwargs):
self.__imageOrientation = value
@property
@style_get_decorator
def isolation(self):
return self.__isolation
@isolation.setter
@style_set_decorator
def isolation(self, value=None, *args, **kwargs):
self.__isolation = value
@property
@style_get_decorator
def justifyContent(self):
return self.__justifyContent
@justifyContent.setter
@style_set_decorator
def justifyContent(self, value=None, *args, **kwargs):
self.__justifyContent = value
@property
@style_get_decorator
def left(self):
return self.__left
@left.setter
@style_set_decorator
def left(self, value=None, *args, **kwargs):
self.__left = value
@property
@style_get_decorator
def letterSpacing(self):
return self.__letterSpacing
@letterSpacing.setter
@style_set_decorator
def letterSpacing(self, value=None, *args, **kwargs):
self.__letterSpacing = value
@property
@style_get_decorator
def lineHeight(self):
return self.__lineHeight
@lineHeight.setter
@style_set_decorator
def lineHeight(self, value=None, *args, **kwargs):
self.__lineHeight = value
@property
@style_get_decorator
def listStyle(self):
return self.__listStyle
@listStyle.setter
@style_set_decorator
def listStyle(self, value=None, *args, **kwargs):
self.__listStyle = value
@property
@style_get_decorator
def listStyleImage(self):
return self.__listStyleImage
@listStyleImage.setter
@style_set_decorator
def listStyleImage(self, value=None, *args, **kwargs):
self.__listStyleImage = value
@property
@style_get_decorator
def listStylePosition(self):
return self.__listStylePosition
@listStylePosition.setter
@style_set_decorator
def listStylePosition(self, value=None, *args, **kwargs):
self.__listStylePosition = value
@property
@style_get_decorator
def listStyleType(self):
return self.__listStyleType
@listStyleType.setter
@style_set_decorator
def listStyleType(self, value=None, *args, **kwargs):
self.__listStyleType = value
@property
@style_get_decorator
def margin(self):
return self.__margin
@margin.setter
@style_set_decorator
def margin(self, value=None, *args, **kwargs):
self.__margin = value
@property
@style_get_decorator
def marginBottom(self):
return self.__marginBottom
@marginBottom.setter
@style_set_decorator
def marginBottom(self, value=None, *args, **kwargs):
self.__marginBottom = value
@property
@style_get_decorator
def marginLeft(self):
return self.__marginLeft
@marginLeft.setter
@style_set_decorator
def marginLeft(self, value=None, *args, **kwargs):
self.__marginLeft = value
@property
@style_get_decorator
def marginRight(self):
return self.__marginRight
@marginRight.setter
@style_set_decorator
def marginRight(self, value=None, *args, **kwargs):
self.__marginRight = value
@property
@style_get_decorator
def marginTop(self):
return self.__marginTop
@marginTop.setter
@style_set_decorator
def marginTop(self, value=None, *args, **kwargs):
self.__marginTop = value
@property
@style_get_decorator
def maxHeight(self):
return self.__maxHeight
@maxHeight.setter
@style_set_decorator
def maxHeight(self, value=None, *args, **kwargs):
self.__maxHeight = value
@property
@style_get_decorator
def maxWidth(self):
return self.__maxWidth
@maxWidth.setter
@style_set_decorator
def maxWidth(self, value=None, *args, **kwargs):
self.__maxWidth = value
@property
@style_get_decorator
def minHeight(self):
return self.__minHeight
@minHeight.setter
@style_set_decorator
def minHeight(self, value=None, *args, **kwargs):
self.__minHeight = value
@property
@style_get_decorator
def minWidth(self):
return self.__minWidth
@minWidth.setter
@style_set_decorator
def minWidth(self, value=None, *args, **kwargs):
self.__minWidth = value
@property
@style_get_decorator
def navDown(self):
return self.__navDown
@navDown.setter
@style_set_decorator
def navDown(self, value=None, *args, **kwargs):
self.__navDown = value
@property
@style_get_decorator
def navIndex(self):
return self.__navIndex
@navIndex.setter
@style_set_decorator
def navIndex(self, value=None, *args, **kwargs):
self.__navIndex = value
@property
@style_get_decorator
def navLeft(self):
return self.__navLeft
@navLeft.setter
@style_set_decorator
def navLeft(self, value=None, *args, **kwargs):
self.__navLeft = value
@property
@style_get_decorator
def navRight(self):
return self.__navRight
@navRight.setter
@style_set_decorator
def navRight(self, value=None, *args, **kwargs):
self.__navRight = value
@property
@style_get_decorator
def navUp(self):
return self.__navUp
@navUp.setter
@style_set_decorator
def navUp(self, value=None, *args, **kwargs):
self.__navUp = value
@property
@style_get_decorator
def objectFit(self):
return self.__objectFit
@objectFit.setter
@style_set_decorator
def objectFit(self, value=None, *args, **kwargs):
self.__objectFit = value
@property
@style_get_decorator
def objectPosition(self):
return self.__objectPosition
@objectPosition.setter
@style_set_decorator
def objectPosition(self, value=None, *args, **kwargs):
self.__objectPosition = value
@property
@style_get_decorator
def opacity(self):
return self.__opacity
@opacity.setter
@style_set_decorator
def opacity(self, value=None, *args, **kwargs):
self.__opacity = value
@property
@style_get_decorator
def order(self):
return self.__order
@order.setter
@style_set_decorator
def order(self, value=None, *args, **kwargs):
self.__order = value
@property
@style_get_decorator
def orphans(self):
return self.__orphans
@orphans.setter
@style_set_decorator
def orphans(self, value=None, *args, **kwargs):
self.__orphans = value
@property
@style_get_decorator
def outline(self):
return self.__outline
@outline.setter
@style_set_decorator
def outline(self, value=None, *args, **kwargs):
self.__outline = value
@property
@style_get_decorator
def outlineColor(self):
return self.__outlineColor
@outlineColor.setter
@style_set_decorator
def outlineColor(self, value=None, *args, **kwargs):
self.__outlineColor = value
@property
@style_get_decorator
def outlineOffset(self):
return self.__outlineOffset
@outlineOffset.setter
@style_set_decorator
def outlineOffset(self, value=None, *args, **kwargs):
self.__outlineOffset = value
@property
@style_get_decorator
def outlineStyle(self):
return self.__outlineStyle
@outlineStyle.setter
@style_set_decorator
def outlineStyle(self, value=None, *args, **kwargs):
self.__outlineStyle = value
@property
@style_get_decorator
def outlineWidth(self):
return self.__outlineWidth
@outlineWidth.setter
@style_set_decorator
def outlineWidth(self, value=None, *args, **kwargs):
self.__outlineWidth = value
@property
@style_get_decorator
def overflow(self):
return self.__overflow
@overflow.setter
@style_set_decorator
def overflow(self, value=None, *args, **kwargs):
self.__overflow = value
@property
@style_get_decorator
def overflowX(self):
return self.__overflowX
@overflowX.setter
@style_set_decorator
def overflowX(self, value=None, *args, **kwargs):
self.__overflowX = value
@property
@style_get_decorator
def overflowY(self):
return self.__overflowY
@overflowY.setter
@style_set_decorator
def overflowY(self, value=None, *args, **kwargs):
self.__overflowY = value
@property
@style_get_decorator
def padding(self):
return self.__padding
@padding.setter
@style_set_decorator
def padding(self, value=None, *args, **kwargs):
self.__padding = value
@property
@style_get_decorator
def paddingBottom(self):
return self.__paddingBottom
@paddingBottom.setter
@style_set_decorator
def paddingBottom(self, value=None, *args, **kwargs):
self.__paddingBottom = value
@property
@style_get_decorator
def paddingLeft(self):
return self.__paddingLeft
@paddingLeft.setter
@style_set_decorator
def paddingLeft(self, value=None, *args, **kwargs):
self.__paddingLeft = value
@property
@style_get_decorator
def paddingRight(self):
return self.__paddingRight
@paddingRight.setter
@style_set_decorator
def paddingRight(self, value=None, *args, **kwargs):
self.__paddingRight = value
@property
@style_get_decorator
def paddingTop(self):
return self.__paddingTop
@paddingTop.setter
@style_set_decorator
def paddingTop(self, value=None, *args, **kwargs):
self.__paddingTop = value
@property
@style_get_decorator
def pageBreakAfter(self):
return self.__pageBreakAfter
@pageBreakAfter.setter
@style_set_decorator
def pageBreakAfter(self, value=None, *args, **kwargs):
self.__pageBreakAfter = value
@property
@style_get_decorator
def pageBreakBefore(self):
return self.__pageBreakBefore
@pageBreakBefore.setter
@style_set_decorator
def pageBreakBefore(self, value=None, *args, **kwargs):
self.__pageBreakBefore = value
@property
@style_get_decorator
def pageBreakInside(self):
return self.__pageBreakInside
@pageBreakInside.setter
@style_set_decorator
def pageBreakInside(self, value=None, *args, **kwargs):
self.__pageBreakInside = value
@property
@style_get_decorator
def perspective(self):
return self.__perspective
@perspective.setter
@style_set_decorator
def perspective(self, value=None, *args, **kwargs):
self.__perspective = value
@property
@style_get_decorator
def perspectiveOrigin(self):
return self.__perspectiveOrigin
@perspectiveOrigin.setter
@style_set_decorator
def perspectiveOrigin(self, value=None, *args, **kwargs):
self.__perspectiveOrigin = value
@property
@style_get_decorator
def position(self):
return self.__position
@position.setter
@style_set_decorator
def position(self, value=None, *args, **kwargs):
self.__position = value
@property
@style_get_decorator
def quotes(self):
return self.__quotes
@quotes.setter
@style_set_decorator
def quotes(self, value=None, *args, **kwargs):
self.__quotes = value
@property
@style_get_decorator
def resize(self):
return self.__resize
@resize.setter
@style_set_decorator
def resize(self, value=None, *args, **kwargs):
self.__resize = value
@property
@style_get_decorator
def right(self):
return self.__right
@right.setter
@style_set_decorator
def right(self, value=None, *args, **kwargs):
self.__right = value
@property
@style_get_decorator
def tableLayout(self):
return self.__tableLayout
@tableLayout.setter
@style_set_decorator
def tableLayout(self, value=None, *args, **kwargs):
self.__tableLayout = value
@property
@style_get_decorator
def tabSize(self):
return self.__tabSize
@tabSize.setter
@style_set_decorator
def tabSize(self, value=None, *args, **kwargs):
self.__tabSize = value
@property
@style_get_decorator
def textAlign(self):
return self.__textAlign
@textAlign.setter
@style_set_decorator
def textAlign(self, value=None, *args, **kwargs):
self.__textAlign = value
@property
@style_get_decorator
def textAlignLast(self):
return self.__textAlignLast
@textAlignLast.setter
@style_set_decorator
def textAlignLast(self, value=None, *args, **kwargs):
self.__textAlignLast = value
@property
@style_get_decorator
def textDecoration(self):
return self.__textDecoration
@textDecoration.setter
@style_set_decorator
def textDecoration(self, value=None, *args, **kwargs):
self.__textDecoration = value
@property
@style_get_decorator
def textDecorationColor(self):
return self.__textDecorationColor
@textDecorationColor.setter
@style_set_decorator
def textDecorationColor(self, value=None, *args, **kwargs):
self.__textDecorationColor = value
@property
@style_get_decorator
def textDecorationLine(self):
return self.__textDecorationLine
@textDecorationLine.setter
@style_set_decorator
def textDecorationLine(self, value=None, *args, **kwargs):
self.__textDecorationLine = value
@property
@style_get_decorator
def textDecorationStyle(self):
return self.__textDecorationStyle
@textDecorationStyle.setter
@style_set_decorator
def textDecorationStyle(self, value=None, *args, **kwargs):
self.__textDecorationStyle = value
@property
@style_get_decorator
def textIndent(self):
return self.__textIndent
@textIndent.setter
@style_set_decorator
def textIndent(self, value=None, *args, **kwargs):
self.__textIndent = value
@property
@style_get_decorator
def textJustify(self):
return self.__textJustify
@textJustify.setter
@style_set_decorator
def textJustify(self, value=None, *args, **kwargs):
self.__textJustify = value
@property
@style_get_decorator
def textOverflow(self):
return self.__textOverflow
@textOverflow.setter
@style_set_decorator
def textOverflow(self, value=None, *args, **kwargs):
self.__textOverflow = value
@property
@style_get_decorator
def textShadow(self):
return self.__textShadow
@textShadow.setter
@style_set_decorator
def textShadow(self, value=None, *args, **kwargs):
self.__textShadow = value
@property
@style_get_decorator
def textTransform(self):
return self.__textTransform
@textTransform.setter
@style_set_decorator
def textTransform(self, value=None, *args, **kwargs):
self.__textTransform = value
@property
@style_get_decorator
def top(self):
return self.__top
@top.setter
@style_set_decorator
def top(self, value=None, *args, **kwargs):
self.__top = value
@property
@style_get_decorator
def transform(self):
return self.__transform
@transform.setter
@style_set_decorator
def transform(self, value=None, *args, **kwargs):
self.__transform = value
@property
@style_get_decorator
def transformOrigin(self):
return self.__transformOrigin
@transformOrigin.setter
@style_set_decorator
def transformOrigin(self, value=None, *args, **kwargs):
self.__transformOrigin = value
@property
@style_get_decorator
def transformStyle(self):
return self.__transformStyle
@transformStyle.setter
@style_set_decorator
def transformStyle(self, value=None, *args, **kwargs):
self.__transformStyle = value
@property
@style_get_decorator
def transition(self):
return self.__transition
@transition.setter
@style_set_decorator
def transition(self, value=None, *args, **kwargs):
self.__transition = value
@property
@style_get_decorator
def transitionProperty(self):
return self.__transitionProperty
@transitionProperty.setter
@style_set_decorator
def transitionProperty(self, value=None, *args, **kwargs):
self.__transitionProperty = value
@property
@style_get_decorator
def transitionDuration(self):
return self.__transitionDuration
@transitionDuration.setter
@style_set_decorator
def transitionDuration(self, value=None, *args, **kwargs):
self.__transitionDuration = value
@property
@style_get_decorator
def transitionTimingFunction(self):
return self.__transitionTimingFunction
@transitionTimingFunction.setter
@style_set_decorator
def transitionTimingFunction(self, value=None, *args, **kwargs):
self.__transitionTimingFunction = value
@property
@style_get_decorator
def transitionDelay(self):
return self.__transitionDelay
@transitionDelay.setter
@style_set_decorator
def transitionDelay(self, value=None, *args, **kwargs):
self.__transitionDelay = value
@property
@style_get_decorator
def unicodeBidi(self):
return self.__unicodeBidi
@unicodeBidi.setter
@style_set_decorator
def unicodeBidi(self, value=None, *args, **kwargs):
self.__unicodeBidi = value
@property
@style_get_decorator
def userSelect(self):
return self.__userSelect
@userSelect.setter
@style_set_decorator
def userSelect(self, value=None, *args, **kwargs):
self.__userSelect = value
@property
@style_get_decorator
def verticalAlign(self):
return self.__verticalAlign
@verticalAlign.setter
@style_set_decorator
def verticalAlign(self, value=None, *args, **kwargs):
self.__verticalAlign = value
@property
@style_get_decorator
def visibility(self):
return self.__visibility
@visibility.setter
@style_set_decorator
def visibility(self, value=None, *args, **kwargs):
self.__visibility = value
@property
@style_get_decorator
def whiteSpace(self):
return self.__whiteSpace
@whiteSpace.setter
@style_set_decorator
def whiteSpace(self, value=None, *args, **kwargs):
self.__whiteSpace = value
@property
@style_get_decorator
def width(self):
return self.__width
@width.setter
@style_set_decorator
def width(self, value=None, *args, **kwargs):
self.__width = value
@property
@style_get_decorator
def wordBreak(self):
return self.__wordBreak
@wordBreak.setter
@style_set_decorator
def wordBreak(self, value=None, *args, **kwargs):
self.__wordBreak = value
@property
@style_get_decorator
def wordSpacing(self):
return self.__wordSpacing
@wordSpacing.setter
@style_set_decorator
def wordSpacing(self, value=None, *args, **kwargs):
self.__wordSpacing = value
@property
@style_get_decorator
def wordWrap(self):
return self.__wordWrap
@wordWrap.setter
@style_set_decorator
def wordWrap(self, value=None, *args, **kwargs):
self.__wordWrap = value
@property
@style_get_decorator
def widows(self):
return self.__widows
@widows.setter
@style_set_decorator
def widows(self, value=None, *args, **kwargs):
self.__widows = value
@property
@style_get_decorator
def zIndex(self):
return self.__zIndex
@zIndex.setter
@style_set_decorator
def zIndex(self, value=None, *args, **kwargs):
self.__zIndex = value
# new adds all newer css3 properties
@property
def all(self):
return self.__all
@all.setter
@style_set_decorator
def all(self, value=None, *args, **kwargs):
self.__all = value
@property
def alignmentBaseline(self):
return self.__alignmentBaseline
@alignmentBaseline.setter
@style_set_decorator
def alignmentBaseline(self, value=None, *args, **kwargs):
self.__alignmentBaseline = value
@property
def appearance(self):
return self.__appearance
@appearance.setter
@style_set_decorator
def appearance(self, value=None, *args, **kwargs):
self.__appearance = value
@property
def backdropFilter(self):
return self.__backdropFilter
@backdropFilter.setter
@style_set_decorator
def backdropFilter(self, value=None, *args, **kwargs):
self.__backdropFilter = value
@property
def backgroundBlendMode(self):
return self.__backgroundBlendMode
@backgroundBlendMode.setter
@style_set_decorator
def backgroundBlendMode(self, value=None, *args, **kwargs):
self.__backgroundBlendMode = value
@property
def backgroundPositionX(self):
return self.__backgroundPositionX
@backgroundPositionX.setter
@style_set_decorator
def backgroundPositionX(self, value=None, *args, **kwargs):
self.__backgroundPositionX = value
@property
def backgroundPositionY(self):
return self.__backgroundPositionY
@backgroundPositionY.setter
@style_set_decorator
def backgroundPositionY(self, value=None, *args, **kwargs):
self.__backgroundPositionY = value
@property
def backgroundRepeatX(self):
return self.__backgroundRepeatX
@backgroundRepeatX.setter
@style_set_decorator
def backgroundRepeatX(self, value=None, *args, **kwargs):
self.__backgroundRepeatX = value
@property
def backgroundRepeatY(self):
return self.__backgroundRepeatY
@backgroundRepeatY.setter
@style_set_decorator
def backgroundRepeatY(self, value=None, *args, **kwargs):
self.__backgroundRepeatY = value
@property
def baselineShift(self):
return self.__baselineShift
@baselineShift.setter
@style_set_decorator
def baselineShift(self, value=None, *args, **kwargs):
self.__baselineShift = value
@property
def blockSize(self):
return self.__blockSize
@blockSize.setter
@style_set_decorator
def blockSize(self, value=None, *args, **kwargs):
self.__blockSize = value
@property
def borderBlockEnd(self):
return self.__borderBlockEnd
@borderBlockEnd.setter
@style_set_decorator
def borderBlockEnd(self, value=None, *args, **kwargs):
self.__borderBlockEnd = value
@property
def borderBlockEndColor(self):
return self.__borderBlockEndColor
@borderBlockEndColor.setter
@style_set_decorator
def borderBlockEndColor(self, value=None, *args, **kwargs):
self.__borderBlockEndColor = value
@property
def borderBlockEndStyle(self):
return self.__borderBlockEndStyle
@borderBlockEndStyle.setter
@style_set_decorator
def borderBlockEndStyle(self, value=None, *args, **kwargs):
self.__borderBlockEndStyle = value
@property
def borderBlockEndWidth(self):
return self.__borderBlockEndWidth
@borderBlockEndWidth.setter
@style_set_decorator
def borderBlockEndWidth(self, value=None, *args, **kwargs):
self.__borderBlockEndWidth = value
@property
def borderBlockStart(self):
return self.__borderBlockStart
@borderBlockStart.setter
@style_set_decorator
def borderBlockStart(self, value=None, *args, **kwargs):
self.__borderBlockStart = value
@property
def borderBlockStartColor(self):
return self.__borderBlockStartColor
@borderBlockStartColor.setter
@style_set_decorator
def borderBlockStartColor(self, value=None, *args, **kwargs):
self.__borderBlockStartColor = value
@property
def borderBlockStartStyle(self):
return self.__borderBlockStartStyle
@borderBlockStartStyle.setter
@style_set_decorator
def borderBlockStartStyle(self, value=None, *args, **kwargs):
self.__borderBlockStartStyle = value
@property
def borderBlockStartWidth(self):
return self.__borderBlockStartWidth
@borderBlockStartWidth.setter
@style_set_decorator
def borderBlockStartWidth(self, value=None, *args, **kwargs):
self.__borderBlockStartWidth = value
@property
def borderInlineEnd(self):
return self.__borderInlineEnd
@borderInlineEnd.setter
@style_set_decorator
def borderInlineEnd(self, value=None, *args, **kwargs):
self.__borderInlineEnd = value
@property
def borderInlineEndColor(self):
return self.__borderInlineEndColor
@borderInlineEndColor.setter
@style_set_decorator
def borderInlineEndColor(self, value=None, *args, **kwargs):
self.__borderInlineEndColor = value
@property
def borderInlineEndStyle(self):
return self.__borderInlineEndStyle
@borderInlineEndStyle.setter
@style_set_decorator
def borderInlineEndStyle(self, value=None, *args, **kwargs):
self.__borderInlineEndStyle = value
@property
def borderInlineEndWidth(self):
return self.__borderInlineEndWidth
@borderInlineEndWidth.setter
@style_set_decorator
def borderInlineEndWidth(self, value=None, *args, **kwargs):
self.__borderInlineEndWidth = value
@property
def borderInlineStart(self):
return self.__borderInlineStart
@borderInlineStart.setter
@style_set_decorator
def borderInlineStart(self, value=None, *args, **kwargs):
self.__borderInlineStart = value
@property
def borderInlineStartColor(self):
return self.__borderInlineStartColor
@borderInlineStartColor.setter
@style_set_decorator
def borderInlineStartColor(self, value=None, *args, **kwargs):
self.__borderInlineStartColor = value
@property
def borderInlineStartStyle(self):
return self.__borderInlineStartStyle
@borderInlineStartStyle.setter
@style_set_decorator
def borderInlineStartStyle(self, value=None, *args, **kwargs):
self.__borderInlineStartStyle = value
@property
def borderInlineStartWidth(self):
return self.__borderInlineStartWidth
@borderInlineStartWidth.setter
@style_set_decorator
def borderInlineStartWidth(self, value=None, *args, **kwargs):
self.__borderInlineStartWidth = value
@property
def breakAfter(self):
return self.__breakAfter
@breakAfter.setter
@style_set_decorator
def breakAfter(self, value=None, *args, **kwargs):
self.__breakAfter = value
@property
def breakBefore(self):
return self.__breakBefore
@breakBefore.setter
@style_set_decorator
def breakBefore(self, value=None, *args, **kwargs):
self.__breakBefore = value
@property
def breakInside(self):
return self.__breakInside
@breakInside.setter
@style_set_decorator
def breakInside(self, value=None, *args, **kwargs):
self.__breakInside = value
@property
def bufferedRendering(self):
return self.__bufferedRendering
@bufferedRendering.setter
@style_set_decorator
def bufferedRendering(self, value=None, *args, **kwargs):
self.__bufferedRendering = value
@property
def caretColor(self):
return self.__caretColor
@caretColor.setter
@style_set_decorator
def caretColor(self, value=None, *args, **kwargs):
self.__caretColor = value
@property
def clipPath(self):
return self.__clipPath
@clipPath.setter
@style_set_decorator
def clipPath(self, value=None, *args, **kwargs):
self.__clipPath = value
@property
def clipRule(self):
return self.__clipRule
@clipRule.setter
@style_set_decorator
def clipRule(self, value=None, *args, **kwargs):
self.__clipRule = value
@property
def colorInterpolation(self):
return self.__colorInterpolation
@colorInterpolation.setter
@style_set_decorator
def colorInterpolation(self, value=None, *args, **kwargs):
self.__colorInterpolation = value
@property
def colorInterpolationFilters(self):
return self.__colorInterpolationFilters
@colorInterpolationFilters.setter
@style_set_decorator
def colorInterpolationFilters(self, value=None, *args, **kwargs):
self.__colorInterpolationFilters = value
@property
def colorRendering(self):
return self.__colorRendering
@colorRendering.setter
@style_set_decorator
def colorRendering(self, value=None, *args, **kwargs):
self.__colorRendering = value
@property
def colorScheme(self):
return self.__colorScheme
@colorScheme.setter
@style_set_decorator
def colorScheme(self, value=None, *args, **kwargs):
self.__colorScheme = value
@property
def contain(self):
return self.__contain
@contain.setter
@style_set_decorator
def contain(self, value=None, *args, **kwargs):
self.__contain = value
@property
def containIntrinsicSize(self):
return self.__containIntrinsicSize
@containIntrinsicSize.setter
@style_set_decorator
def containIntrinsicSize(self, value=None, *args, **kwargs):
self.__containIntrinsicSize = value
@property
def contentVisibility(self):
return self.__contentVisibility
@contentVisibility.setter
@style_set_decorator
def contentVisibility(self, value=None, *args, **kwargs):
self.__contentVisibility = value
@property
def counterSet(self):
return self.__counterSet
@counterSet.setter
@style_set_decorator
def counterSet(self, value=None, *args, **kwargs):
self.__counterSet = value
@property
def cx(self):
return self.__cx
@cx.setter
@style_set_decorator
def cx(self, value=None, *args, **kwargs):
self.__cx = value
@property
def cy(self):
return self.__cy
@cy.setter
@style_set_decorator
def cy(self, value=None, *args, **kwargs):
self.__cy = value
@property
def dominantBaseline(self):
return self.__dominantBaseline
@dominantBaseline.setter
@style_set_decorator
def dominantBaseline(self, value=None, *args, **kwargs):
self.__dominantBaseline = value
@property
def d(self):
return self.__d
@d.setter
@style_set_decorator
def d(self, value=None, *args, **kwargs):
self.__d = value
@property
def fill(self):
return self.__fill
@fill.setter
@style_set_decorator
def fill(self, value=None, *args, **kwargs):
self.__fill = value
@property
def fillOpacity(self):
return self.__fillOpacity
@fillOpacity.setter
@style_set_decorator
def fillOpacity(self, value=None, *args, **kwargs):
self.__fillOpacity = value
@property
def fillRule(self):
return self.__fillRule
@fillRule.setter
@style_set_decorator
def fillRule(self, value=None, *args, **kwargs):
self.__fillRule = value
@property
def fontDisplay(self):
return self.__fontDisplay
@fontDisplay.setter
@style_set_decorator
def fontDisplay(self, value=None, *args, **kwargs):
self.__fontDisplay = value
@property
def floodColor(self):
return self.__floodColor
@floodColor.setter
@style_set_decorator
def floodColor(self, value=None, *args, **kwargs):
self.__floodColor = value
@property
def floodOpacity(self):
return self.__floodOpacity
@floodOpacity.setter
@style_set_decorator
def floodOpacity(self, value=None, *args, **kwargs):
self.__floodOpacity = value
@property
def fontFeatureSettings(self):
return self.__fontFeatureSettings
@fontFeatureSettings.setter
@style_set_decorator
def fontFeatureSettings(self, value=None, *args, **kwargs):
self.__fontFeatureSettings = value
@property
def fontKerning(self):
return self.__fontKerning
@fontKerning.setter
@style_set_decorator
def fontKerning(self, value=None, *args, **kwargs):
self.__fontKerning = value
@property
def fontOpticalSizing(self):
return self.__fontOpticalSizing
@fontOpticalSizing.setter
@style_set_decorator
def fontOpticalSizing(self, value=None, *args, **kwargs):
self.__fontOpticalSizing = value
@property
def fontVariantCaps(self):
return self.__fontVariantCaps
@fontVariantCaps.setter
@style_set_decorator
def fontVariantCaps(self, value=None, *args, **kwargs):
self.__fontVariantCaps = value
@property
def fontVariantEastAsian(self):
return self.__fontVariantEastAsian
@fontVariantEastAsian.setter
@style_set_decorator
def fontVariantEastAsian(self, value=None, *args, **kwargs):
self.__fontVariantEastAsian = value
@property
def fontVariantLigatures(self):
return self.__fontVariantLigatures
@fontVariantLigatures.setter
@style_set_decorator
def fontVariantLigatures(self, value=None, *args, **kwargs):
self.__fontVariantLigatures = value
@property
def fontVariantNumeric(self):
return self.__fontVariantNumeric
@fontVariantNumeric.setter
@style_set_decorator
def fontVariantNumeric(self, value=None, *args, **kwargs):
self.__fontVariantNumeric = value
@property
def fontVariationSettings(self):
return self.__fontVariationSettings
@fontVariationSettings.setter
@style_set_decorator
def fontVariationSettings(self, value=None, *args, **kwargs):
self.__fontVariationSettings = value
@property
def gap(self):
return self.__gap
@gap.setter
@style_set_decorator
def gap(self, value=None, *args, **kwargs):
self.__gap = value
@property
def grid(self):
return self.__grid
@grid.setter
@style_set_decorator
def grid(self, value=None, *args, **kwargs):
self.__grid = value
@property
def gridArea(self):
return self.__gridArea
@gridArea.setter
@style_set_decorator
def gridArea(self, value=None, *args, **kwargs):
self.__gridArea = value
@property
def gridAutoColumns(self):
return self.__gridAutoColumns
@gridAutoColumns.setter
@style_set_decorator
def gridAutoColumns(self, value=None, *args, **kwargs):
self.__gridAutoColumns = value
@property
def gridAutoFlow(self):
return self.__gridAutoFlow
@gridAutoFlow.setter
@style_set_decorator
def gridAutoFlow(self, value=None, *args, **kwargs):
self.__gridAutoFlow = value
@property
def gridAutoRows(self):
return self.__gridAutoRows
@gridAutoRows.setter
@style_set_decorator
def gridAutoRows(self, value=None, *args, **kwargs):
self.__gridAutoRows = value
@property
def gridColumn(self):
return self.__gridColumn
@gridColumn.setter
@style_set_decorator
def gridColumn(self, value=None, *args, **kwargs):
self.__gridColumn = value
@property
def gridColumnEnd(self):
return self.__gridColumnEnd
@gridColumnEnd.setter
@style_set_decorator
def gridColumnEnd(self, value=None, *args, **kwargs):
self.__gridColumnEnd = value
@property
def gridColumnGap(self):
return self.__gridColumnGap
@gridColumnGap.setter
@style_set_decorator
def gridColumnGap(self, value=None, *args, **kwargs):
self.__gridColumnGap = value
@property
def gridColumnStart(self):
return self.__gridColumnStart
@gridColumnStart.setter
@style_set_decorator
def gridColumnStart(self, value=None, *args, **kwargs):
self.__gridColumnStart = value
@property
def gridGap(self):
return self.__gridGap
@gridGap.setter
@style_set_decorator
def gridGap(self, value=None, *args, **kwargs):
self.__gridGap = value
@property
def gridRow(self):
return self.__gridRow
@gridRow.setter
@style_set_decorator
def gridRow(self, value=None, *args, **kwargs):
self.__gridRow = value
@property
def gridRowEnd(self):
return self.__gridRowEnd
@gridRowEnd.setter
@style_set_decorator
def gridRowEnd(self, value=None, *args, **kwargs):
self.__gridRowEnd = value
@property
def gridRowGap(self):
return self.__gridRowGap
@gridRowGap.setter
@style_set_decorator
def gridRowGap(self, value=None, *args, **kwargs):
self.__gridRowGap = value
@property
def gridRowStart(self):
return self.__gridRowStart
@gridRowStart.setter
@style_set_decorator
def gridRowStart(self, value=None, *args, **kwargs):
self.__gridRowStart = value
@property
def gridTemplate(self):
return self.__gridTemplate
@gridTemplate.setter
@style_set_decorator
def gridTemplate(self, value=None, *args, **kwargs):
self.__gridTemplate = value
@property
def gridTemplateAreas(self):
return self.__gridTemplateAreas
@gridTemplateAreas.setter
@style_set_decorator
def gridTemplateAreas(self, value=None, *args, **kwargs):
self.__gridTemplateAreas = value
@property
def gridTemplateColumns(self):
return self.__gridTemplateColumns
@gridTemplateColumns.setter
@style_set_decorator
def gridTemplateColumns(self, value=None, *args, **kwargs):
self.__gridTemplateColumns = value
@property
def gridTemplateRows(self):
return self.__gridTemplateRows
@gridTemplateRows.setter
@style_set_decorator
def gridTemplateRows(self, value=None, *args, **kwargs):
self.__gridTemplateRows = value
@property
def imageRendering(self):
return self.__imageRendering
@imageRendering.setter
@style_set_decorator
def imageRendering(self, value=None, *args, **kwargs):
self.__imageRendering = value
@property
def inherits(self):
return self.__inherits
@inherits.setter
@style_set_decorator
def inherits(self, value=None, *args, **kwargs):
self.__inherits = value
@property
def initialValue(self):
return self.__initialValue
@initialValue.setter
@style_set_decorator
def initialValue(self, value=None, *args, **kwargs):
self.__initialValue = value
@property
def inlineSize(self):
return self.__inlineSize
@inlineSize.setter
@style_set_decorator
def inlineSize(self, value=None, *args, **kwargs):
self.__inlineSize = value
@property
def justifyItems(self):
return self.__justifyItems
@justifyItems.setter
@style_set_decorator
def justifyItems(self, value=None, *args, **kwargs):
self.__justifyItems = value
@property
def justifySelf(self):
return self.__justifySelf
@justifySelf.setter
@style_set_decorator
def justifySelf(self, value=None, *args, **kwargs):
self.__justifySelf = value
@property
def lightingColor(self):
return self.__lightingColor
@lightingColor.setter
@style_set_decorator
def lightingColor(self, value=None, *args, **kwargs):
self.__lightingColor = value
@property
def lineBreak(self):
return self.__lineBreak
@lineBreak.setter
@style_set_decorator
def lineBreak(self, value=None, *args, **kwargs):
self.__lineBreak = value
@property
def marginBlockEnd(self):
return self.__marginBlockEnd
@marginBlockEnd.setter
@style_set_decorator
def marginBlockEnd(self, value=None, *args, **kwargs):
self.__marginBlockEnd = value
@property
def marginBlockStart(self):
return self.__marginBlockStart
@marginBlockStart.setter
@style_set_decorator
def marginBlockStart(self, value=None, *args, **kwargs):
self.__marginBlockStart = value
@property
def marginInlineEnd(self):
return self.__marginInlineEnd
@marginInlineEnd.setter
@style_set_decorator
def marginInlineEnd(self, value=None, *args, **kwargs):
self.__marginInlineEnd = value
@property
def marginInlineStart(self):
return self.__marginInlineStart
@marginInlineStart.setter
@style_set_decorator
def marginInlineStart(self, value=None, *args, **kwargs):
self.__marginInlineStart = value
@property
def marker(self):
return self.__marker
@marker.setter
@style_set_decorator
def marker(self, value=None, *args, **kwargs):
self.__marker = value
@property
def markerEnd(self):
return self.__markerEnd
@markerEnd.setter
@style_set_decorator
def markerEnd(self, value=None, *args, **kwargs):
self.__markerEnd = value
@property
def markerMid(self):
return self.__markerMid
@markerMid.setter
@style_set_decorator
def markerMid(self, value=None, *args, **kwargs):
self.__markerMid = value
@property
def markerStart(self):
return self.__markerStart
@markerStart.setter
@style_set_decorator
def markerStart(self, value=None, *args, **kwargs):
self.__markerStart = value
@property
def mask(self):
return self.__mask
@mask.setter
@style_set_decorator
def mask(self, value=None, *args, **kwargs):
self.__mask = value
@property
def maskType(self):
return self.__maskType
@maskType.setter
@style_set_decorator
def maskType(self, value=None, *args, **kwargs):
self.__maskType = value
@property
def maxBlockSize(self):
return self.__maxBlockSize
@maxBlockSize.setter
@style_set_decorator
def maxBlockSize(self, value=None, *args, **kwargs):
self.__maxBlockSize = value
@property
def maxInlineSize(self):
return self.__maxInlineSize
@maxInlineSize.setter
@style_set_decorator
def maxInlineSize(self, value=None, *args, **kwargs):
self.__maxInlineSize = value
@property
def maxZoom(self):
return self.__maxZoom
@maxZoom.setter
@style_set_decorator
def maxZoom(self, value=None, *args, **kwargs):
self.__maxZoom = value
@property
def minBlockSize(self):
return self.__minBlockSize
@minBlockSize.setter
@style_set_decorator
def minBlockSize(self, value=None, *args, **kwargs):
self.__minBlockSize = value
@property
def minInlineSize(self):
return self.__minInlineSize
@minInlineSize.setter
@style_set_decorator
def minInlineSize(self, value=None, *args, **kwargs):
self.__minInlineSize = value
@property
def minZoom(self):
return self.__minZoom
@minZoom.setter
@style_set_decorator
def minZoom(self, value=None, *args, **kwargs):
self.__minZoom = value
@property
def mixBlendMode(self):
return self.__mixBlendMode
@mixBlendMode.setter
@style_set_decorator
def mixBlendMode(self, value=None, *args, **kwargs):
self.__mixBlendMode = value
# objectFit / objectPosition are defined once above with the standard
# get/set decorators; the duplicate definitions that lived here were removed.
@property
def offset(self):
return self.__offset
@offset.setter
@style_set_decorator
def offset(self, value=None, *args, **kwargs):
self.__offset = value
@property
def offsetDistance(self):
return self.__offsetDistance
@offsetDistance.setter
@style_set_decorator
def offsetDistance(self, value=None, *args, **kwargs):
self.__offsetDistance = value
@property
def offsetPath(self):
return self.__offsetPath
@offsetPath.setter
@style_set_decorator
def offsetPath(self, value=None, *args, **kwargs):
self.__offsetPath = value
@property
def offsetRotate(self):
return self.__offsetRotate
@offsetRotate.setter
@style_set_decorator
def offsetRotate(self, value=None, *args, **kwargs):
self.__offsetRotate = value
@property
def orientation(self):
return self.__orientation
@orientation.setter
@style_set_decorator
def orientation(self, value=None, *args, **kwargs):
self.__orientation = value
@property
def overflowAnchor(self):
return self.__overflowAnchor
@overflowAnchor.setter
@style_set_decorator
def overflowAnchor(self, value=None, *args, **kwargs):
self.__overflowAnchor = value
@property
def overflowWrap(self):
return self.__overflowWrap
@overflowWrap.setter
@style_set_decorator
def overflowWrap(self, value=None, *args, **kwargs):
self.__overflowWrap = value
@property
def overscrollBehavior(self):
return self.__overscrollBehavior
@overscrollBehavior.setter
@style_set_decorator
def overscrollBehavior(self, value=None, *args, **kwargs):
self.__overscrollBehavior = value
@property
def overscrollBehaviorBlock(self):
return self.__overscrollBehaviorBlock
@overscrollBehaviorBlock.setter
@style_set_decorator
def overscrollBehaviorBlock(self, value=None, *args, **kwargs):
self.__overscrollBehaviorBlock = value
@property
def overscrollBehaviorInline(self):
return self.__overscrollBehaviorInline
@overscrollBehaviorInline.setter
@style_set_decorator
def overscrollBehaviorInline(self, value=None, *args, **kwargs):
self.__overscrollBehaviorInline = value
@property
def overscrollBehaviorX(self):
return self.__overscrollBehaviorX
@overscrollBehaviorX.setter
@style_set_decorator
def overscrollBehaviorX(self, value=None, *args, **kwargs):
self.__overscrollBehaviorX = value
@property
def overscrollBehaviorY(self):
return self.__overscrollBehaviorY
@overscrollBehaviorY.setter
@style_set_decorator
def overscrollBehaviorY(self, value=None, *args, **kwargs):
self.__overscrollBehaviorY = value
@property
def paddingBlockEnd(self):
return self.__paddingBlockEnd
@paddingBlockEnd.setter
@style_set_decorator
def paddingBlockEnd(self, value=None, *args, **kwargs):
self.__paddingBlockEnd = value
@property
def paddingBlockStart(self):
return self.__paddingBlockStart
@paddingBlockStart.setter
@style_set_decorator
def paddingBlockStart(self, value=None, *args, **kwargs):
self.__paddingBlockStart = value
@property
def paddingInlineEnd(self):
return self.__paddingInlineEnd
@paddingInlineEnd.setter
@style_set_decorator
def paddingInlineEnd(self, value=None, *args, **kwargs):
self.__paddingInlineEnd = value
@property
def paddingInlineStart(self):
return self.__paddingInlineStart
@paddingInlineStart.setter
@style_set_decorator
def paddingInlineStart(self, value=None, *args, **kwargs):
self.__paddingInlineStart = value
@property
def page(self):
return self.__page
@page.setter
@style_set_decorator
def page(self, value=None, *args, **kwargs):
self.__page = value
@property
def pageOrientation(self):
return self.__pageOrientation
@pageOrientation.setter
@style_set_decorator
def pageOrientation(self, value=None, *args, **kwargs):
self.__pageOrientation = value
@property
def paintOrder(self):
return self.__paintOrder
@paintOrder.setter
@style_set_decorator
def paintOrder(self, value=None, *args, **kwargs):
self.__paintOrder = value
@property
def placeContent(self):
return self.__placeContent
@placeContent.setter
@style_set_decorator
def placeContent(self, value=None, *args, **kwargs):
self.__placeContent = value
@property
def placeItems(self):
return self.__placeItems
@placeItems.setter
@style_set_decorator
def placeItems(self, value=None, *args, **kwargs):
self.__placeItems = value
@property
def placeSelf(self):
return self.__placeSelf
@placeSelf.setter
@style_set_decorator
def placeSelf(self, value=None, *args, **kwargs):
self.__placeSelf = value
@property
def pointerEvents(self):
return self.__pointerEvents
@pointerEvents.setter
@style_set_decorator
def pointerEvents(self, value=None, *args, **kwargs):
self.__pointerEvents = value
@property
def r(self):
return self.__r
@r.setter
@style_set_decorator
def r(self, value=None, *args, **kwargs):
self.__r = value
@property
def rowGap(self):
return self.__rowGap
@rowGap.setter
@style_set_decorator
def rowGap(self, value=None, *args, **kwargs):
self.__rowGap = value
@property
def rubyPosition(self):
return self.__rubyPosition
@rubyPosition.setter
@style_set_decorator
def rubyPosition(self, value=None, *args, **kwargs):
self.__rubyPosition = value
@property
def rx(self):
return self.__rx
@rx.setter
@style_set_decorator
def rx(self, value=None, *args, **kwargs):
self.__rx = value
@property
def ry(self):
return self.__ry
@ry.setter
@style_set_decorator
def ry(self, value=None, *args, **kwargs):
self.__ry = value
@property
def scrollBehavior(self):
return self.__scrollBehavior
@scrollBehavior.setter
@style_set_decorator
def scrollBehavior(self, value=None, *args, **kwargs):
self.__scrollBehavior = value
@property
def scrollMargin(self):
return self.__scrollMargin
@scrollMargin.setter
@style_set_decorator
def scrollMargin(self, value=None, *args, **kwargs):
self.__scrollMargin = value
@property
def scrollMarginBlock(self):
return self.__scrollMarginBlock
@scrollMarginBlock.setter
@style_set_decorator
def scrollMarginBlock(self, value=None, *args, **kwargs):
self.__scrollMarginBlock = value
@property
def scrollMarginBlockEnd(self):
return self.__scrollMarginBlockEnd
@scrollMarginBlockEnd.setter
@style_set_decorator
def scrollMarginBlockEnd(self, value=None, *args, **kwargs):
self.__scrollMarginBlockEnd = value
@property
def scrollMarginBlockStart(self):
return self.__scrollMarginBlockStart
@scrollMarginBlockStart.setter
@style_set_decorator
def scrollMarginBlockStart(self, value=None, *args, **kwargs):
self.__scrollMarginBlockStart = value
@property
def scrollMarginBottom(self):
return self.__scrollMarginBottom
@scrollMarginBottom.setter
@style_set_decorator
def scrollMarginBottom(self, value=None, *args, **kwargs):
self.__scrollMarginBottom = value
@property
def scrollMarginInline(self):
return self.__scrollMarginInline
@scrollMarginInline.setter
@style_set_decorator
def scrollMarginInline(self, value=None, *args, **kwargs):
self.__scrollMarginInline = value
@property
def scrollMarginInlineEnd(self):
return self.__scrollMarginInlineEnd
@scrollMarginInlineEnd.setter
@style_set_decorator
def scrollMarginInlineEnd(self, value=None, *args, **kwargs):
self.__scrollMarginInlineEnd = value
@property
def scrollMarginInlineStart(self):
return self.__scrollMarginInlineStart
@scrollMarginInlineStart.setter
@style_set_decorator
def scrollMarginInlineStart(self, value=None, *args, **kwargs):
self.__scrollMarginInlineStart = value
@property
def scrollMarginLeft(self):
return self.__scrollMarginLeft
@scrollMarginLeft.setter
@style_set_decorator
def scrollMarginLeft(self, value=None, *args, **kwargs):
self.__scrollMarginLeft = value
@property
def scrollMarginRight(self):
return self.__scrollMarginRight
@scrollMarginRight.setter
@style_set_decorator
def scrollMarginRight(self, value=None, *args, **kwargs):
self.__scrollMarginRight = value
@property
def scrollMarginTop(self):
return self.__scrollMarginTop
@scrollMarginTop.setter
@style_set_decorator
def scrollMarginTop(self, value=None, *args, **kwargs):
self.__scrollMarginTop = value
@property
def scrollPadding(self):
return self.__scrollPadding
@scrollPadding.setter
@style_set_decorator
def scrollPadding(self, value=None, *args, **kwargs):
self.__scrollPadding = value
@property
def scrollPaddingBlock(self):
return self.__scrollPaddingBlock
@scrollPaddingBlock.setter
@style_set_decorator
def scrollPaddingBlock(self, value=None, *args, **kwargs):
self.__scrollPaddingBlock = value
@property
def scrollPaddingBlockEnd(self):
return self.__scrollPaddingBlockEnd
@scrollPaddingBlockEnd.setter
@style_set_decorator
def scrollPaddingBlockEnd(self, value=None, *args, **kwargs):
self.__scrollPaddingBlockEnd = value
@property
def scrollPaddingBlockStart(self):
return self.__scrollPaddingBlockStart
@scrollPaddingBlockStart.setter
@style_set_decorator
def scrollPaddingBlockStart(self, value=None, *args, **kwargs):
self.__scrollPaddingBlockStart = value
@property
def scrollPaddingBottom(self):
return self.__scrollPaddingBottom
@scrollPaddingBottom.setter
@style_set_decorator
def scrollPaddingBottom(self, value=None, *args, **kwargs):
self.__scrollPaddingBottom = value
@property
def scrollPaddingInline(self):
return self.__scrollPaddingInline
@scrollPaddingInline.setter
@style_set_decorator
def scrollPaddingInline(self, value=None, *args, **kwargs):
self.__scrollPaddingInline = value
@property
def scrollPaddingInlineEnd(self):
return self.__scrollPaddingInlineEnd
@scrollPaddingInlineEnd.setter
@style_set_decorator
def scrollPaddingInlineEnd(self, value=None, *args, **kwargs):
self.__scrollPaddingInlineEnd = value
@property
def scrollPaddingInlineStart(self):
return self.__scrollPaddingInlineStart
@scrollPaddingInlineStart.setter
@style_set_decorator
def scrollPaddingInlineStart(self, value=None, *args, **kwargs):
self.__scrollPaddingInlineStart = value
@property
def scrollPaddingLeft(self):
return self.__scrollPaddingLeft
@scrollPaddingLeft.setter
@style_set_decorator
def scrollPaddingLeft(self, value=None, *args, **kwargs):
self.__scrollPaddingLeft = value
@property
def scrollPaddingRight(self):
return self.__scrollPaddingRight
@scrollPaddingRight.setter
@style_set_decorator
def scrollPaddingRight(self, value=None, *args, **kwargs):
self.__scrollPaddingRight = value
@property
def scrollPaddingTop(self):
return self.__scrollPaddingTop
@scrollPaddingTop.setter
@style_set_decorator
def scrollPaddingTop(self, value=None, *args, **kwargs):
self.__scrollPaddingTop = value
@property
def scrollSnapAlign(self):
return self.__scrollSnapAlign
@scrollSnapAlign.setter
@style_set_decorator
def scrollSnapAlign(self, value=None, *args, **kwargs):
self.__scrollSnapAlign = value
@property
def scrollSnapStop(self):
return self.__scrollSnapStop
@scrollSnapStop.setter
@style_set_decorator
def scrollSnapStop(self, value=None, *args, **kwargs):
self.__scrollSnapStop = value
@property
def scrollSnapType(self):
return self.__scrollSnapType
@scrollSnapType.setter
@style_set_decorator
def scrollSnapType(self, value=None, *args, **kwargs):
self.__scrollSnapType = value
@property
def shapeImageThreshold(self):
return self.__shapeImageThreshold
@shapeImageThreshold.setter
@style_set_decorator
def shapeImageThreshold(self, value=None, *args, **kwargs):
self.__shapeImageThreshold = value
@property
def shapeMargin(self):
return self.__shapeMargin
@shapeMargin.setter
@style_set_decorator
def shapeMargin(self, value=None, *args, **kwargs):
self.__shapeMargin = value
@property
def shapeOutside(self):
return self.__shapeOutside
@shapeOutside.setter
@style_set_decorator
def shapeOutside(self, value=None, *args, **kwargs):
self.__shapeOutside = value
@property
def shapeRendering(self):
return self.__shapeRendering
@shapeRendering.setter
@style_set_decorator
def shapeRendering(self, value=None, *args, **kwargs):
self.__shapeRendering = value
@property
def size(self):
return self.__size
@size.setter
@style_set_decorator
def size(self, value=None, *args, **kwargs):
self.__size = value
@property
def speak(self):
return self.__speak
@speak.setter
@style_set_decorator
def speak(self, value=None, *args, **kwargs):
self.__speak = value
@property
def src(self):
return self.__src
@src.setter
@style_set_decorator
def src(self, value=None, *args, **kwargs):
self.__src = value
@property
def stopColor(self):
return self.__stopColor
@stopColor.setter
@style_set_decorator
def stopColor(self, value=None, *args, **kwargs):
self.__stopColor = value
@property
def stopOpacity(self):
return self.__stopOpacity
@stopOpacity.setter
@style_set_decorator
def stopOpacity(self, value=None, *args, **kwargs):
self.__stopOpacity = value
@property
def stroke(self):
return self.__stroke
@stroke.setter
@style_set_decorator
def stroke(self, value=None, *args, **kwargs):
self.__stroke = value
@property
def strokeDasharray(self):
return self.__strokeDasharray
@strokeDasharray.setter
@style_set_decorator
def strokeDasharray(self, value=None, *args, **kwargs):
self.__strokeDasharray = value
@property
def strokeDashoffset(self):
return self.__strokeDashoffset
@strokeDashoffset.setter
@style_set_decorator
def strokeDashoffset(self, value=None, *args, **kwargs):
self.__strokeDashoffset = value
@property
def strokeLinecap(self):
return self.__strokeLinecap
@strokeLinecap.setter
@style_set_decorator
def strokeLinecap(self, value=None, *args, **kwargs):
self.__strokeLinecap = value
@property
def strokeLinejoin(self):
return self.__strokeLinejoin
@strokeLinejoin.setter
@style_set_decorator
def strokeLinejoin(self, value=None, *args, **kwargs):
self.__strokeLinejoin = value
@property
def strokeMiterlimit(self):
return self.__strokeMiterlimit
@strokeMiterlimit.setter
@style_set_decorator
def strokeMiterlimit(self, value=None, *args, **kwargs):
self.__strokeMiterlimit = value
@property
def strokeOpacity(self):
return self.__strokeOpacity
@strokeOpacity.setter
@style_set_decorator
def strokeOpacity(self, value=None, *args, **kwargs):
self.__strokeOpacity = value
@property
def strokeWidth(self):
return self.__strokeWidth
@strokeWidth.setter
@style_set_decorator
def strokeWidth(self, value=None, *args, **kwargs):
self.__strokeWidth = value
@property
def syntax(self):
return self.__syntax
@syntax.setter
@style_set_decorator
def syntax(self, value=None, *args, **kwargs):
self.__syntax = value
@property
def textAnchor(self):
return self.__textAnchor
@textAnchor.setter
@style_set_decorator
def textAnchor(self, value=None, *args, **kwargs):
self.__textAnchor = value
@property
def textCombineUpright(self):
return self.__textCombineUpright
@textCombineUpright.setter
@style_set_decorator
def textCombineUpright(self, value=None, *args, **kwargs):
self.__textCombineUpright = value
@property
def textDecorationSkipInk(self):
return self.__textDecorationSkipInk
@textDecorationSkipInk.setter
@style_set_decorator
def textDecorationSkipInk(self, value=None, *args, **kwargs):
self.__textDecorationSkipInk = value
@property
def textOrientation(self):
return self.__textOrientation
@textOrientation.setter
@style_set_decorator
def textOrientation(self, value=None, *args, **kwargs):
self.__textOrientation = value
@property
def textRendering(self):
return self.__textRendering
@textRendering.setter
@style_set_decorator
def textRendering(self, value=None, *args, **kwargs):
self.__textRendering = value
@property
def textSizeAdjust(self):
return self.__textSizeAdjust
@textSizeAdjust.setter
@style_set_decorator
def textSizeAdjust(self, value=None, *args, **kwargs):
self.__textSizeAdjust = value
@property
def textUnderlinePosition(self):
return self.__textUnderlinePosition
@textUnderlinePosition.setter
@style_set_decorator
def textUnderlinePosition(self, value=None, *args, **kwargs):
self.__textUnderlinePosition = value
@property
def touchAction(self):
return self.__touchAction
@touchAction.setter
@style_set_decorator
def touchAction(self, value=None, *args, **kwargs):
self.__touchAction = value
@property
def transformBox(self):
return self.__transformBox
@transformBox.setter
@style_set_decorator
def transformBox(self, value=None, *args, **kwargs):
self.__transformBox = value
@property
def unicodeRange(self):
return self.__unicodeRange
@unicodeRange.setter
@style_set_decorator
def unicodeRange(self, value=None, *args, **kwargs):
self.__unicodeRange = value
@property
def userZoom(self):
return self.__userZoom
@userZoom.setter
@style_set_decorator
def userZoom(self, value=None, *args, **kwargs):
self.__userZoom = value
@property
def vectorEffect(self):
return self.__vectorEffect
@vectorEffect.setter
@style_set_decorator
def vectorEffect(self, value=None, *args, **kwargs):
self.__vectorEffect = value
@property
def willChange(self):
return self.__willChange
@willChange.setter
@style_set_decorator
def willChange(self, value=None, *args, **kwargs):
self.__willChange = value
@property
def writingMode(self):
return self.__writingMode
@writingMode.setter
@style_set_decorator
def writingMode(self, value=None, *args, **kwargs):
self.__writingMode = value
@property
def x(self):
return self.__x
@x.setter
@style_set_decorator
def x(self, value=None, *args, **kwargs):
self.__x = value
@property
def y(self):
return self.__y
@y.setter
@style_set_decorator
def y(self, value=None, *args, **kwargs):
self.__y = value
@property
@style_get_decorator
def zoom(self):
return self.__zoom
@zoom.setter
@style_set_decorator
def zoom(self, value=None, *args, **kwargs):
self.__zoom = value
# @property
# @style_get_decorator
# def zoomAndPan(self):
# return self.__zoomAndPan
# @zoomAndPan.setter
# @style_set_decorator
# def zoomAndPan(self, value=None, *args, **kwargs):
# self.__zoomAndPan = value
# @property
# @style_get_decorator
# def zoomAndResize(self):
# return self.__zoomAndResize
# @zoomAndResize.setter
# @style_set_decorator
# def zoomAndResize(self, value=None, *args, **kwargs):
# self.__zoomAndResize = value
# @property
# @style_get_decorator
# def zoomType(self):
# return self.__zoomType
# @zoomType.setter
# @style_set_decorator
# def zoomType(self, value=None, *args, **kwargs):
# self.__zoomType = value
# Modifies an existing CSS property or creates a new CSS property in the declaration block. """
# def setProperty(self, property, value):
# self[property] = value
[docs]
class CSSStyleDeclaration(Style):
"""The CSSStyleDeclaration interface represents an object that is a CSS declaration block,
and exposes style information and various style-related methods and properties.
A CSSStyleDeclaration object can be exposed using three different APIs:
Via HTMLElement.style, which deals with the inline styles of a single element (e.g., <div style="...">).
Via the CSSStyleSheet API. For example, document.styleSheets[0].cssRules[0].style
returns a CSSStyleDeclaration object on the first CSS rule in the document's first stylesheet.
Via Window.getComputedStyle(), which exposes the CSSStyleDeclaration object as a read-only interface.
"""
def __init__(self, parentNode=None, *args, **kwargs):
# super(Style).__init__(*args, **kwargs)
self._css_text = ""
self._declared_properties = set()
self._suspend_style_sync = False
super().__init__(parentNode, *args, **kwargs)
# self.__parentNode = parentNode
# self.__cssText = None
self.parentRule = None
self.cssText = (
parentNode.getAttribute("style") if parentNode is not None and parentNode.getAttribute("style") else ""
)
@staticmethod
def _to_kebab(name: str) -> str:
return _css_property_name(name) if name is not None else ""
@staticmethod
def _to_camel(name: str) -> str:
return _css_attribute_name(name) if name else name
def _current_css_text(self) -> str:
if getattr(self, "cssText", ""):
return self.cssText or ""
if self._parent_node is not None:
return self._parent_node.getAttribute("style") or ""
return ""
def _property_entries(self):
return _parse_css_declarations(self._current_css_text())
def _expanded_entries(self):
"""The declaration block the way a browser *enumerates* it: every set
shorthand contributes its longhand sub-properties (in canonical order),
not a single entry. This is what ``length`` / ``item`` / iteration see
-- ``margin: 1px`` is four properties. Serialisation still collapses
them back (see ``_collapse_box_shorthands`` / ``cssText``).
Shorthands ``_cssom.expand_shorthand`` cannot split losslessly
(``background``, ``transition``, ``animation``, ``grid-template``,
``grid-area``) stay as the single shorthand entry.
"""
order: list[str] = []
seen: dict[str, tuple[str, str]] = {}
for name, value, priority in self._property_entries():
expanded = _cssom.expand_shorthand(name, value) if _cssom.is_shorthand(name) else None
pairs = expanded if expanded else [(name, value)]
for long_name, long_value in pairs:
if long_name not in seen:
order.append(long_name)
seen[long_name] = (long_value, priority)
return [(n, seen[n][0], seen[n][1]) for n in order]
def _sync_css_text(self, entries):
css_text = _serialize_css_declarations(entries)
self.cssText = css_text
if self._parent_node is not None:
object.__setattr__(self._parent_node, "_syncing_style_attr", True)
try:
self._parent_node.setAttribute("style", css_text)
finally:
object.__setattr__(self._parent_node, "_syncing_style_attr", False)
@property
def cssText(self):
"""Textual representation of the declaration block."""
return getattr(self, "_css_text", "")
@cssText.setter
def cssText(self, value):
raw = "" if value is None else str(value).strip()
parsed_entries = [
(name, _normalize_zero_lengths(name, val), priority) for name, val, priority in _parse_css_declarations(raw)
]
entries = self._collapse_box_shorthands(parsed_entries)
# CSSOM: the getter always returns the *serialised* declaration block,
# regardless of the input text -- so normalise here.
text = _serialize_css_declarations(entries)
next_declared_properties = {self._to_camel(name) for name, _, _ in entries if not name.startswith("--")}
previous_declared_properties = set(getattr(self, "_declared_properties", set()))
object.__setattr__(self, "_css_text", text)
if (
getattr(self, "_parent_node", None) is not None
and getattr(self, "_members_checked", False)
and not getattr(self._parent_node, "_syncing_style_attr", False)
):
self._parent_node.setAttribute("style", text)
object.__setattr__(self, "_suspend_style_sync", True)
try:
for attribute_name in previous_declared_properties - next_declared_properties:
try:
setattr(self, attribute_name, "")
except Exception:
object.__setattr__(self, attribute_name, "")
for name, property_value, _ in entries:
if name.startswith("--"):
continue
# keep both the shorthand and its longhand camelCase accessors current
pairs = [(name, property_value)]
if _cssom.is_shorthand(name):
pairs += _cssom.expand_shorthand(name, property_value) or []
for pair_name, pair_value in pairs:
attribute_name = self._to_camel(pair_name)
try:
setattr(self, attribute_name, pair_value)
except Exception:
object.__setattr__(self, attribute_name, pair_value)
object.__setattr__(self, "_declared_properties", next_declared_properties)
finally:
object.__setattr__(self, "_suspend_style_sync", False)
@property
def length(self):
"""The number of properties in the declaration block. A set shorthand
counts as its longhand sub-properties, the way a browser reports it
(``el.style.margin = "1px"`` -> ``length`` 4)."""
return len(self._expanded_entries())
# @property
# def parentRule(self):
# """The containing CSSRule."""
# return self.__parentRule
# @parentRule.setter
# def parentRule(self, value=None, *args, **kwargs):
# self.__parentRule = value
# @property
# def cssFloat(self):
# """ Special alias for the float CSS property. """
# raise NotImplementedError
[docs]
def getPropertyPriority(self, propertyName):
"""Returns the optional priority, "important"."""
target = self._to_kebab(propertyName)
for name, _, priority in self._property_entries():
if name == target:
return priority
# a longhand inherits the priority of a set shorthand
for name, _, priority in self._expanded_entries():
if name == target:
return priority
return ""
[docs]
def getPropertyValue(self, propertyName: str) -> str:
"""Returns the value of the property with the specified name.
Shorthand / longhand are resolved against each other: asking for a
longhand pulls its value out of a set shorthand, and asking for a
shorthand reconstructs it from its longhands when possible.
"""
target = self._to_kebab(propertyName)
entries = self._property_entries()
for name, value, _ in reversed(entries):
if name == target:
return value
for shorthand in _cssom.LONGHAND_TO_SHORTHANDS.get(target, ()):
for name, value, _ in reversed(entries):
if name == shorthand:
expanded = _cssom.expand_shorthand(shorthand, value)
if expanded:
for long_name, long_value in expanded:
if long_name == target:
return long_value
if _cssom.is_shorthand(target):
built = _cssom.build_shorthand(target, self.getPropertyValue)
if built:
return built
attribute_name = self._to_camel(propertyName)
if attribute_name not in getattr(self, "_declared_properties", set()):
return ""
value = getattr(self, attribute_name, "")
return "" if value is None else value
[docs]
def item(self, index: int) -> str:
"""Returns a CSS property name by its index, or the empty string if the index is out-of-bounds.
An alternative to accessing nodeList[i] (which instead returns undefined when i is out-of-bounds).
This is mostly useful for non-JavaScript DOM implementations.
"""
entries = self._expanded_entries()
if index < 0 or index >= len(entries):
return ""
return entries[index][0]
def __getitem__(self, index):
if isinstance(index, int):
return self.item(index)
return self.getPropertyValue(index)
def __setitem__(self, propertyName, value):
self.setProperty(propertyName, value)
def __contains__(self, propertyName):
return self.getPropertyValue(propertyName) != ""
def __iter__(self):
for index in range(self.length):
yield self.item(index)
[docs]
def removeProperty(self, propertyName):
"""Removes a property from the CSS declaration block."""
current = self.getPropertyValue(propertyName)
target = self._to_kebab(propertyName)
entries = self._property_entries()
if not any(e[0] == target for e in entries):
# removing a longhand covered by a stored shorthand: expand the
# shorthand first so the other longhands survive, like a browser
for shorthand in _cssom.LONGHAND_TO_SHORTHANDS.get(target, ()):
shorthand_entry = next((e for e in entries if e[0] == shorthand), None)
if shorthand_entry is None:
continue
expanded = _cssom.expand_shorthand(shorthand, shorthand_entry[1])
if not expanded:
continue
idx = entries.index(shorthand_entry)
prio = shorthand_entry[2]
entries = entries[:idx] + [(ln, lv, prio) for ln, lv in expanded] + entries[idx + 1 :]
entries = [entry for entry in entries if entry[0] != target]
entries = self._collapse_shorthands(entries)
self._sync_css_text(entries)
try:
object.__setattr__(self, "_suspend_style_sync", True)
if not target.startswith("--"):
setattr(self, self._to_camel(propertyName), "")
except Exception:
return current
finally:
object.__setattr__(self, "_suspend_style_sync", False)
return current
# Modifies an existing CSS property or creates a new CSS property in the declaration block. """
[docs]
def setProperty(self, property, value, priority=None):
target = self._to_kebab(property)
value = "" if value is None else str(value)
priority = "" if priority is None else str(priority).strip().lower()
if priority not in ("", "important") and value:
return
if _IMPORTANT_RE.search(value):
value = _IMPORTANT_RE.sub("", value).strip()
priority = "important"
if value == "":
self.removeProperty(target)
return
value = _normalize_zero_lengths(target, value)
entries = self._property_entries()
if _cssom.is_shorthand(target):
covered = set(_cssom.longhands_for(target))
entries = [e for e in entries if e[0] not in covered and e[0] != target]
entries.append((target, value, priority))
else:
for shorthand in _cssom.LONGHAND_TO_SHORTHANDS.get(target, ()):
shorthand_entry = next((e for e in entries if e[0] == shorthand), None)
if shorthand_entry is None:
continue
expanded = _cssom.expand_shorthand(shorthand, shorthand_entry[1])
if not expanded:
continue
idx = entries.index(shorthand_entry)
prio = shorthand_entry[2]
entries = entries[:idx] + [(ln, lv, prio) for ln, lv in expanded] + entries[idx + 1 :]
entries = _set_css_declaration(entries, target, value, priority)
entries = self._collapse_box_shorthands(entries)
self._sync_css_text(entries)
if target.startswith("--"):
return
object.__setattr__(self, "_suspend_style_sync", True)
try:
setattr(self, self._to_camel(property), value)
if _cssom.is_shorthand(target):
for long_name, long_value in _cssom.expand_shorthand(target, value) or ():
setattr(self, self._to_camel(long_name), long_value)
finally:
object.__setattr__(self, "_suspend_style_sync", False)
#: shorthands re-formed from a complete longhand set when serialising the
#: declaration block, the way a browser normalises ``cssText``. Ordered
#: widest-first so ``border`` claims all 12 longhands before ``border-width``
#: / ``border-top`` get a chance at the leftovers. ``font`` / ``list-style``
#: / ``text-decoration`` / ``background`` / ``transition`` / ``animation`` /
#: ``grid-*`` are deliberately absent -- browsers only collapse those when
#: every longhand (including ones domonic does not model) is at its initial
#: value, so re-forming them here would emit shorthands a browser would not.
_COLLAPSE_ORDER = (
"border",
"border-width",
"border-style",
"border-color",
"border-top",
"border-right",
"border-bottom",
"border-left",
"border-radius",
"margin",
"padding",
"inset",
"outline",
"column-rule",
"overflow",
"gap",
"place-content",
"place-items",
"place-self",
"columns",
"flex-flow",
"flex",
)
@classmethod
def _collapse_shorthands(cls, entries):
"""Replace each complete, priority-consistent set of longhands with its
shorthand, matching how a browser serialises a declaration block."""
entries = list(entries)
for shorthand in cls._COLLAPSE_ORDER:
longs = _cssom.longhands_for(shorthand)
by_name = {name: (value, priority) for name, value, priority in entries}
if not all(ln in by_name for ln in longs):
continue
priorities = {by_name[ln][1] for ln in longs}
if len(priorities) != 1:
continue
collapsed = _cssom.build_shorthand(shorthand, lambda ln: by_name.get(ln, ("", ""))[0])
if not collapsed:
continue
priority = priorities.pop()
first_idx = min(i for i, e in enumerate(entries) if e[0] in longs)
entries = [e for e in entries if e[0] not in longs]
entries.insert(first_idx, (shorthand, collapsed, priority))
# widen the border-width / border-style / border-color trio (each a
# single, all-sides-equal value) into `border`, the way a browser does
trio = ("border-width", "border-style", "border-color")
by_name = {name: (value, priority) for name, value, priority in entries}
if all(t in by_name for t in trio):
prios = {by_name[t][1] for t in trio}
vals = [by_name[t][0] for t in trio]
if len(prios) == 1 and all(v and " " not in v for v in vals):
merged = " ".join(v for v in vals if v not in ("medium", "currentcolor")) or vals[1]
first_idx = min(i for i, e in enumerate(entries) if e[0] in trio)
entries = [e for e in entries if e[0] not in trio]
entries.insert(first_idx, ("border", merged, prios.pop()))
return entries
# back-compat alias -- this used to only handle the box + radius shorthands
_collapse_box_shorthands = _collapse_shorthands
[docs]
def getPropertyCSSValue(self, propertyName: str):
"""Only supported via getComputedStyle in Firefox. Returns the property value as a
CSSPrimitiveValue or null for shorthand properties."""
value = self.getPropertyValue(propertyName)
return value if value != "" else None
_SPECIFICITY_ID = re.compile(r"#[-\w]+")
_SPECIFICITY_CLASS = re.compile(r"\.[-\w]+|\[[^\]]+\]|(?<!:):[-\w]+")
_SPECIFICITY_TYPE = re.compile(r"(?:^|[\s>+~|])(-?[_a-zA-Z][-\w]*|\*)")
_SPECIFICITY_PSEUDO_EL = re.compile(r"::[-\w]+|:(?:before|after|first-line|first-letter)\b")
# Functional pseudo-classes whose specificity is that of their most specific
# argument (:where() contributes nothing and is handled separately).
_SPECIFICITY_MATCHES_ANY = ("is", "matches", "not", "has")
def _split_functional_pseudo(selector: str):
"""Yield ``(name, args, start, end)`` for each top-level ``:name(...)`` in
*selector*, where ``args`` is the raw content between the parentheses."""
i = 0
length = len(selector)
while i < length:
if selector[i] != ":":
i += 1
continue
match = re.match(r":{1,2}([-\w]+)\(", selector[i:])
if not match:
i += 1
continue
name = match.group(1).lower()
open_paren = i + match.end() - 1
depth = 0
j = open_paren
while j < length:
char = selector[j]
if char == "(":
depth += 1
elif char == ")":
depth -= 1
if depth == 0:
break
j += 1
yield name, selector[open_paren + 1 : j], i, j + 1
i = j + 1
def _selector_specificity(selector: str) -> tuple[int, int, int]:
"""CSS Selectors 4 specificity as an ``(#id, .class, type)`` triple.
Handles the functional pseudo-classes: ``:where()`` adds nothing,
``:is()`` / ``:not()`` / ``:has()`` take the specificity of their most
specific argument, and ``:nth-child(... of S)`` adds the specificity of
``S`` on top of the pseudo-class itself.
"""
sel = selector.strip()
if not sel:
return (0, 0, 0)
ids = classes = types = 0
replacements: list[tuple[int, int]] = []
for name, args, start, end in _split_functional_pseudo(sel):
if name == "where":
replacements.append((start, end))
continue
if name in _SPECIFICITY_MATCHES_ANY:
best = (0, 0, 0)
for part in _split_top_level_commas(args):
part = part.strip()
if part:
best = max(best, _selector_specificity(part))
ids += best[0]
classes += best[1]
types += best[2]
replacements.append((start, end))
continue
if name in ("nth-child", "nth-last-child") and " of " in f" {args} ":
_, _, sub = args.partition(" of ")
add = (0, 0, 0)
for part in _split_top_level_commas(sub):
part = part.strip()
if part:
add = max(add, _selector_specificity(part))
ids += add[0]
classes += add[1]
types += add[2]
# leave the ":nth-child(" ... ")" in place so it still counts once
# as a pseudo-class below, but blank the argument list
replacements.append((start + len(name) + 2, end - 1))
for start, end in sorted(replacements, reverse=True):
sel = sel[:start] + sel[end:]
pseudo_els = len(_SPECIFICITY_PSEUDO_EL.findall(sel))
# remove ::pseudo-elements before the class scan so their ':' is not counted
scan = _SPECIFICITY_PSEUDO_EL.sub(" ", sel)
ids += len(_SPECIFICITY_ID.findall(scan))
classes += len(_SPECIFICITY_CLASS.findall(scan))
types += len([t for t in _SPECIFICITY_TYPE.findall(scan) if t != "*"])
return ids, classes, types + pseudo_els
_RIGHTMOST_KEY = re.compile(r"([#.]?[-\w]+|\*)(?:\([^)]*\))?\s*$")
def _rule_bucket_key(selector: str) -> str:
"""The bucket a selector belongs in: ``#id``, ``.class``, ``tag`` from its
rightmost compound, or ``*`` when that cannot be determined cheaply."""
sel = selector.strip().rstrip()
# drop a trailing pseudo-element / pseudo-class for bucketing purposes; a
# selector that is *only* a pseudo (``:root``, ``:hover``, ``:not(...)``)
# has no cheap type/id/class key, so it goes in the "*" bucket
stripped = re.sub(r"::?[-\w]+(?:\([^)]*\))?\s*$", "", sel).strip()
if not stripped:
return "*"
match = _RIGHTMOST_KEY.search(stripped)
if not match:
return "*"
token = match.group(1)
if token.startswith(("#", ".")):
return token
if token == "*":
return "*"
return token.lower()
def _layer_sort_key(layer: int, important: bool) -> tuple[int, int]:
"""Cascade-layer ordering term. Unlayered normal declarations beat layered
ones and later layers beat earlier; for ``!important`` both are reversed
(earlier layer wins, unlayered important is weakest)."""
if important:
return (0, 0) if layer == 0 else (1, -layer)
return (1, 0) if layer == 0 else (0, layer)
def _has_paren_internal_whitespace(value: str) -> bool:
"""Whether *value* has whitespace nested inside a function call's
parens -- e.g. the space after ``var(--missing,`` in ``var(--missing,
4px)``, as opposed to ``var(--gap)`` (no comma/space inside at all) or
plain top-level whitespace like ``1px solid red``."""
depth = 0
for char in value:
if char == "(":
depth += 1
elif char == ")":
depth = max(depth - 1, 0)
elif char.isspace() and depth > 0:
return True
return False
def _expand_shorthands_for_cascade(entries: tuple) -> tuple:
"""A cascade-only, fully-longhand view of one rule's declarations.
``_collect_author_declarations`` picks a single winning declaration per
*property name* across all matching rules -- but a shorthand
(``border``) and one of its own longhands (``border-top``) are different
names, so without this they never actually compete for a longhand they
both cover (``border-top-style``); whichever one happens to be expanded
first in ``_resolve()`` wins regardless of which was really later in the
rule. Expanding every shorthand to physical longhands here -- before
that per-name competition ever runs -- lets it decide correctly, the
same way it already does for two declarations of the same longhand.
This is separate from ``CSSStyleRule.style``'s own declarations
(``entries``, as authored) so ``cssText``/``getPropertyValue``/
``setProperty`` keep reporting the shorthand exactly as written --
only the copy used for property-vs-property cascade matching changes."""
result: list[tuple[str, str, str]] = []
index_by_name: dict[str, int] = {}
for name, value, priority in entries:
pairs = None
# the box/border/font/background splitters below tokenize on bare
# whitespace -- safe for an unresolved var() reference with no
# fallback (``var(--gap)``, one token, splits the same as any other
# single value), but not for one with a fallback (``var(--missing,
# 4px)``): the space after its comma isn't a real token boundary,
# and naively splitting there would scatter one value's fallback
# across unrelated longhands. Left as the single shorthand entry in
# that case, exactly as authored, its var() substituted later.
if _cssom.is_shorthand(name) and not _has_paren_internal_whitespace(value):
pairs = _cssom.expand_shorthand(name, value)
for long_name, long_value in pairs if pairs is not None else ((name, value),):
existing = index_by_name.get(long_name)
if existing is None:
index_by_name[long_name] = len(result)
result.append((long_name, long_value, priority))
else:
result[existing] = (long_name, long_value, priority)
return tuple(result)
_PSEUDO_ELEMENT_SUFFIX_RE = re.compile(
r"::?(before|after|marker|placeholder|first-line|" r"first-letter|selection|backdrop|file-selector-button)\s*$"
)
def _build_rule_index(sheet_list, viewport, *, features=None, media_type: str = "screen"):
"""``{bucket_key: [(selector, specificity, order, layer, entries)]}`` for
fast element-vs-rule matching in getComputedStyle, plus the set of
pseudo-element names (``"before"``, ``"after"``, ...) any selector in
these sheets actually targets -- lets ``getComputedStyle(el, "::before")``
reject a page with no such rule at all without scanning a single bucket
(most pages never style a pseudo-element, and a caller like a renderer
asking "does this element have generated content?" pays for the whole
cascade to find out otherwise).
*features*/*media_type* are the window's ``mediaFeatures``/``mediaType``
(see ``_condition_rule_matches``), threaded through so a stylesheet's own
``@media`` blocks are filtered out consistently with what
``window.matchMedia()`` reports for the same window."""
index: dict[str, list] = {}
pseudo_names: set[str] = set()
order = 0
layers: dict[int, int] = {} # id(CSSLayerBlockRule) -> layer number
for sheet in sheet_list:
# per CSSOM, a disabled sheet contributes no rules to the cascade at
# all -- `StyleSheet.disabled`'s setter bumps the stylesheet epoch
# this index is keyed on, so toggling it also invalidates every
# cached rule index/computed style that was built while it applied.
if getattr(sheet, "disabled", False):
continue
for rule, layer in _iter_style_rules(
getattr(sheet, "cssRules", None),
viewport=viewport,
layers=layers,
features=features,
media_type=media_type,
):
entries = _expand_shorthands_for_cascade(rule.style._property_entries())
if not entries:
continue
for selector in str(rule.selectorText or "").split(","):
selector = selector.strip()
if not selector:
continue
order += 1
index.setdefault(_rule_bucket_key(selector), []).append(
(selector, _selector_specificity(selector), order, layer, entries)
)
pe = _PSEUDO_ELEMENT_SUFFIX_RE.search(selector)
if pe:
pseudo_names.add(pe.group(1))
return index, pseudo_names
def _iter_style_rules(rules, *, viewport, layers=None, layer=0, features=None, media_type: str = "screen"):
"""Yield ``(CSSStyleRule, layer_number)`` pairs, descending into ``@media``
blocks whose condition currently matches and tracking ``@layer`` nesting
(layer 0 == unlayered; each ``@layer`` block gets the next number in
document order)."""
if layers is None:
layers = {}
for rule in rules or ():
# `@layer a, b;` pre-declares layer order regardless of block order
if isinstance(rule, CSSLayerStatementRule):
for layer_name in rule.nameList:
layers.setdefault(layer_name.strip(), len(layers) + 1)
continue
inner = getattr(rule, "cssRules", None)
if isinstance(rule, CSSStyleRule):
yield rule, layer
continue
if not inner:
continue
if isinstance(rule, CSSLayerBlockRule):
layer_name = (rule.name or "").strip() or f"\x00anon{id(rule)}"
child_layer = layers.setdefault(layer_name, len(layers) + 1)
yield from _iter_style_rules(
inner, viewport=viewport, layers=layers, layer=child_layer, features=features, media_type=media_type
)
continue
if _condition_rule_matches(rule, viewport, features=features, media_type=media_type):
yield from _iter_style_rules(
inner, viewport=viewport, layers=layers, layer=layer, features=features, media_type=media_type
)
def _condition_rule_matches(rule, viewport, *, features=None, media_type: str = "screen") -> bool:
"""Whether a conditional group rule (``@media``, ``@supports``, ...)
currently applies. ``@media`` is evaluated through its parsed ``MediaList``
against the real viewport, the same matcher ``window.matchMedia()`` uses --
``neither ``conditionText`` (a plain string) nor ``MediaList`` (a plain
list) ever had a ``.matches`` attribute, so the previous
``hasattr(condition, "matches")`` check was always false and every
conditional block was treated as always matching. ``@supports`` is
evaluated through ``CSS.supports()``, the same ``not``/``and``/``or``/
nested-parens evaluator ``CSS.supports()`` itself exposes -- it was
already correct, just never wired in here. Other condition rules
(``@when``, ``@else``) have no evaluator here yet and keep the previous
always-match behaviour.
*features* and *media_type* mirror what ``window.matchMedia()`` uses for
the same element's window (``window.mediaFeatures`` /
``window.mediaType``) -- passed through so a stylesheet's own ``@media
(prefers-color-scheme: dark)`` or ``@media print`` agrees with what
``matchMedia()`` would report for the same window, instead of always
falling back to the class defaults regardless of what the window
actually has configured."""
if isinstance(rule, CSSMediaRule):
media_text = rule.media.mediaText if rule.media else ""
if not media_text:
return True
from domonic.window import MediaQueryList
width, height = viewport
return MediaQueryList._evaluate(
media_text, width=width or 0, height=height or 0, features=features, media_type=media_type
)
if isinstance(rule, CSSSupportsRule):
return _supports_condition(rule.conditionText or "")
return True
def _computed_style_cache_key(element) -> tuple:
"""Everything that can make a previously resolved *cascade* stale: the DOM
epoch (any attribute/structure change anywhere), the stylesheet epoch (any
rule change anywhere), and the viewport (a resize can flip which
``@media`` rules apply). Deliberately whole-document/whole-process
granularity, not per-element -- a conservative but correct starting point
(see the domonic-css-performance notes for the narrower alternative).
The layout epoch is deliberately *not* part of this key. Selector
matching, inheritance, and shorthand resolution never consult a
``LayoutBox`` -- only a handful of used-value conversions do (``auto``
box fields and ``%`` lengths, in ``_to_used_length``/``_percent_base_px``)
-- so a relayout would otherwise invalidate the entire cascade for every
element just to re-derive the few properties that actually depend on
geometry. Those are cached and invalidated separately, per property, in
``ComputedStyleDeclaration.getPropertyValue``."""
document = getattr(element, "ownerDocument", None) or getattr(element, "rootNode", None)
window = getattr(document, "defaultView", None) if document is not None else None
viewport = (getattr(window, "innerWidth", None), getattr(window, "innerHeight", None))
return (_cssom.dom_style_epoch(), _cssom.stylesheet_epoch(), viewport)
[docs]
class ComputedStyleDeclaration(CSSStyleDeclaration):
"""Read-only ``CSSStyleDeclaration`` returned by ``window.getComputedStyle``.
It resolves each property through a light cascade: matching author rules
(by specificity, then source order, with ``!important`` on top), then the
inline ``style`` attribute, then inherited values from the parent, then the
property's initial value.
"""
# populated in __init__ via self.__dict__ (Style.__init__ is skipped)
_resolved: Any
_element: Any
_pseudo: Any
_chain_cache: dict
def __init__(self, element, pseudo=None, _chain_cache=None):
# Repeated getComputedStyle() calls on an unchanged element (the
# common case for a layout engine, or anything polling geometry every
# frame) would otherwise redo the whole cascade -- selector matching,
# shorthand expansion, inheritance walk up to the root -- from
# scratch every time. Cache the resolved state on the element itself,
# keyed by everything that can make it stale; a hit just copies the
# previous state across instead of recomputing it. This also makes
# resolving an ancestor's style cheap the second time any sibling
# queries it, since _parent_computed()/_root_font_size_px() below
# construct ancestor instances through this same __init__.
cache_key = _computed_style_cache_key(element)
element_dict = getattr(element, "__dict__", None)
store = element_dict.setdefault("_computed_style_cache", {}) if element_dict is not None else None
cached = store.get(pseudo) if store is not None else None
if cached is not None and cached[0] == cache_key:
self.__dict__.update(cached[1])
return
# Deliberately skip ``Style.__init__`` (its ~380 attribute assignments):
# this class overrides the whole read surface, so all it needs is the
# handful of internal fields the CSSOM helpers look for.
state = self.__dict__
state["_css_text"] = ""
state["_declared_properties"] = set()
state["_members_checked"] = True
state["_parent_node"] = None
state["_suspend_style_sync"] = True
state["parentRule"] = None
state["_element"] = element
state["_pseudo"] = pseudo
# shared across a single ancestor walk so getComputedStyle on a deep
# tree stays linear rather than O(depth^2)
state["_chain_cache"] = {} if _chain_cache is None else _chain_cache
state["_resolved"] = self._resolve()
if store is not None:
store[pseudo] = (cache_key, state)
# -- cascade ---------------------------------------------------------
def _collect_author_declarations(self):
element = self._element
document = getattr(element, "ownerDocument", None) or getattr(element, "rootNode", None)
sheets = None
for attr in ("styleSheets", "stylesheets"):
sheets = getattr(document, attr, None) if document is not None else None
if sheets:
break
try:
sheet_list = list(sheets) if sheets is not None else []
except TypeError:
sheet_list = []
# constructable stylesheets adopted on the document or the element's
# shadow root also feed the cascade
for holder in (document, getattr(element, "getRootNode", lambda: None)()):
adopted = getattr(holder, "adoptedStyleSheets", None)
if adopted:
try:
sheet_list = sheet_list + list(adopted)
except TypeError: # nosec B110 - adoptedStyleSheets not iterable: skip it
pass
window = getattr(document, "defaultView", None) if document else None
viewport = (
getattr(window, "innerWidth", None),
getattr(window, "innerHeight", None),
)
# Same media context ``window.matchMedia()`` would evaluate against,
# so a stylesheet's own ``@media (prefers-color-scheme: dark)`` /
# ``@media print`` agrees with what the window reports -- previously
# this always fell back to the class defaults here regardless of
# ``window.mediaFeatures``/``window.mediaType``.
media_features = getattr(window, "mediaFeatures", None)
media_type = getattr(window, "mediaType", None) or "screen"
# The parsed index is keyed on everything that could make it stale: the
# stylesheet epoch (bumped by insertRule/deleteRule/replace(Sync) on
# any stylesheet), the viewport (a resize can change which @media
# rules apply), the media features/type (changing either can too),
# and the identity of the sheets actually in play (so reassigning
# document.adoptedStyleSheets, or adding a <style> element, is picked
# up without a dedicated hook for either).
rule_cache_key = (
_cssom.stylesheet_epoch(),
viewport,
tuple(id(sheet) for sheet in sheet_list),
tuple(sorted(media_features.items())) if media_features else None,
media_type,
)
cache = self._chain_cache
entry = cache.get("__rule_index__")
if entry is None and document is not None:
entry = getattr(document, "_cssom_rule_index", None)
if entry is None or entry[0] != rule_cache_key:
entry = (
rule_cache_key,
*_build_rule_index(sheet_list, viewport, features=media_features, media_type=media_type),
)
if document is not None:
try:
document._cssom_rule_index = entry
except Exception: # nosec B110 - index cache is best-effort; cache["__rule_index__"] still set below
pass
cache["__rule_index__"] = entry
index = entry[1]
want_pseudo = ""
if self._pseudo:
want_pseudo = str(self._pseudo).strip().lower().lstrip(":")
cascade: dict[str, tuple[tuple, str, bool]] = {}
# An empty index (no stylesheets at all) and "no rule in these sheets
# targets this pseudo-element" both mean the bucket scan below has
# nothing to find -- skip it, but (for a real, non-pseudo element)
# still fall through to the presentational-hint step afterward: a
# page with zero author stylesheets is exactly the case a legacy
# ``bgcolor``-style hint most needs to still apply in.
if index and (not want_pseudo or want_pseudo in entry[2]):
# only look at rules whose rightmost compound could match this element
tag_name = (element.tagName or "").lower()
local_name = tag_name.rpartition(":")[2] or tag_name
buckets = ["*", tag_name] if local_name == tag_name else ["*", tag_name, local_name]
el_id = element.getAttribute("id")
if el_id:
buckets.append(f"#{el_id}")
for cls in str(element.getAttribute("class") or "").split():
buckets.append(f".{cls}")
for bucket in buckets:
for selector, spec, order, layer, entries in index.get(bucket, ()):
pe = _PSEUDO_ELEMENT_SUFFIX_RE.search(selector)
sel_pseudo = pe.group(1) if pe else ""
if sel_pseudo != want_pseudo:
continue
base_selector = selector[: pe.start()].strip() if pe else selector
if not base_selector:
base_selector = "*"
try:
# ``_matchElement`` only understands a small self-contained
# subset of pseudo-classes (see ``_STRUCTURAL_PSEUDO_
# CLASSES``) and no combinators at all; anything else
# (``:nth-of-type()``, ``:hover``, ``:is()``, a combinator
# chain, ...) makes it return ``False`` without that
# meaning "genuinely doesn't match" -- ``_parse_simple_
# selector``'s tokenizer isn't a strict validator (a
# combinator selector still "parses", just into a bogus
# single compound, rather than failing outright), so a
# parse-failure check here cannot reliably tell the two
# apart. Always falling through to the heavier bs4-backed
# matcher on a ``False`` -- same as ``Element.matches()``
# does -- is the only way that stays correct for every
# selector shape.
ok = element._matchElement(element, base_selector) or (
element._matches_selector_chain(base_selector) is True
)
except Exception:
ok = False
if not ok:
continue
for name, value, priority in entries:
important = priority == "important"
key = (important, _layer_sort_key(layer, important), spec, order)
if name not in cascade or key > cascade[name][0]:
cascade[name] = (key, value, important)
result = {name: (value, important) for name, (_, value, important) in cascade.items()}
resolver = _PRESENTATIONAL_HINT_RESOLVER
# A pseudo-element has no HTML attributes of its own to carry a
# presentational hint about -- only a real element does.
if resolver is not None and not want_pseudo:
# A presentational hint (a legacy HTML attribute a browser treats
# as implicit style -- ``bgcolor``, an ``<img>``'s ``width``/
# ``height``, ...) is the weakest declaration in the real CSS
# cascade: weaker than any author rule for the same property
# regardless of specificity or source order, and weaker than an
# inline ``style=""`` too (merged in afterwards, in ``_resolve``).
# domonic has no opinion on which HTML attributes map to which
# properties -- that mapping is rendering policy for whatever is
# built on top of this -- so this only owns the one thing only
# the cascade can get right: filling a hint in *exclusively* for
# a property no real author rule declared at all, so it's already
# too late for it to compete with, or override, any real
# declaration found above, and a later CSSOM write still simply
# overrides it downstream the normal way, with no special-casing
# needed anywhere else in the cascade.
try:
hints = resolver(element) or {}
except Exception:
hints = {}
for name, value in hints.items():
if value is not None and name not in result:
result[name] = (str(value), False)
return result
def _resolve(self):
element = self._element
# 1 + 2: author rules then the inline style attribute. Per the cascade,
# normal inline beats normal author but loses to important author;
# important inline beats everything here. Author declarations already
# arrive fully expanded to physical longhands (``_build_rule_index``
# expands every shorthand before its own per-name cascade competition
# runs), so only the inline attribute -- not indexed the same way --
# still needs its own shorthands expanded here. Merging author-then-
# inline and expanding together (rather than as two separate passes)
# means a later inline shorthand/longhand correctly overrides an
# earlier author one for a longhand they both cover, the same way a
# later declaration always beats an earlier one for the same property.
author = self._collect_author_declarations()
important_author = {name for name, (_, imp) in author.items() if imp}
merged: list[tuple[str, str, str]] = [
(name, value, "important" if imp else "") for name, (value, imp) in author.items()
]
inline = getattr(element, "getAttribute", lambda *_: "")("style") or ""
for name, value, priority in _parse_css_declarations(inline):
if priority != "important":
covering = _cssom.LONGHAND_TO_SHORTHANDS.get(name, ())
if name in important_author or any(shorthand in important_author for shorthand in covering):
continue
merged.append((name, value, priority))
resolved: dict[str, str] = {
name: value for name, value, _ in _expand_shorthands_for_cascade(tuple(merged))
}
# 3: inherited values from the parent's computed style. A pseudo-
# element has no DOM node of its own -- ``element`` is still the real
# element it's attached to -- so per CSS Pseudo-Elements ("the parent
# of a generated box is the element to whose box tree the
# pseudo-element is attached"), a ::before/::after inherits from
# *that* element's own computed style, not its DOM parent's (which
# would skip right over any inherited property, e.g. font-family,
# that the element's own rules set).
cache = self._chain_cache
parent_computed = None
if self._pseudo:
parent_computed = cache.get(id(element))
if parent_computed is None:
parent_computed = ComputedStyleDeclaration(element, None, _chain_cache=cache)
cache[id(element)] = parent_computed
else:
parent = getattr(element, "parentNode", None)
if parent is not None and _is_inheritance_source(parent):
parent_computed = cache.get(id(parent))
if parent_computed is None:
parent_computed = ComputedStyleDeclaration(parent, None, _chain_cache=cache)
cache[id(parent)] = parent_computed
return _ResolvedView(resolved, parent_computed)
# -- read-only CSSOM surface --------------------------------------
def _custom_property(self, name: str) -> str:
"""Computed value of a custom property (they inherit by default)."""
return self._resolved.get(name) or ""
[docs]
def getPropertyValue(self, propertyName: str) -> str:
target = self._to_kebab(propertyName)
# This instance is immutable once resolved (it's a read-only snapshot
# -- see the class docstring), so the *cascaded* value for a given
# longhand never changes across repeated reads. Memoize it: a caller
# that asks for the same property more than once (or a shorthand that
# rebuilds itself from the same longhands `getPropertyValue` recurses
# into below) does the unit/colour/keyword conversion once, not every
# time. Lives in ``self.__dict__`` alongside ``_resolved`` and
# ``_font_size_px_cache``, so it rides along whenever this instance is
# reused from the per-element computed-style cache too.
#
# A small minority of properties are *also* layout-dependent: an
# ``auto`` box field or a ``%`` length reads the element's (or its
# parent's) attached ``LayoutBox`` (see ``_to_used_length`` /
# ``_percent_base_px``), so their used value can go stale on a
# relayout even though nothing about the cascade changed. Rather than
# invalidate the whole cache on every layout epoch bump (which would
# throw away selector-matching and inheritance work for properties a
# relayout never touches), each entry that is layout-dependent also
# carries the layout epoch it was computed under, and is recomputed
# only when that epoch has moved; every other entry is cached for the
# lifetime of this cascade, same as before.
cache = self.__dict__.get("_property_value_cache")
if cache is None:
cache = {}
self.__dict__["_property_value_cache"] = cache
else:
cached = cache.get(target)
if cached is not None:
cached_layout_epoch, cached_value = cached
if cached_layout_epoch is None or cached_layout_epoch == _cssom.layout_epoch():
return cached_value
value = self._compute_property_value(target)
cache[target] = (self._layout_epoch_of(target) if value is not None else None, value)
return value
def _layout_epoch_of(self, target: str) -> "int | None":
"""The layout epoch to stamp a just-computed property value with, or
``None`` if it could not have consulted a ``LayoutBox`` and is safe to
cache regardless of future relayouts. Mirrors exactly the two
conditions under which ``_to_used_length``/``_percent_base_px`` ever
look at a box: an ``auto`` value on one of the handful of properties
a box reports (``width``/``height``/``margin-*``), or any length
containing a literal ``%``."""
if target not in _USED_LENGTH_PROPERTIES:
return None
raw = (self._resolved.get(target) or "").strip()
if not raw:
return None
if "%" in raw:
return _cssom.layout_epoch()
if target in _AUTO_BOX_FIELDS and raw.lower() == "auto":
return _cssom.layout_epoch()
return None
def _compute_property_value(self, target: str) -> str:
# A border's width computes to 0 when its style is none/hidden --
# the width is never actually rendered, so a browser doesn't report
# whatever length happens to be sitting in border-width (per spec,
# and matching the default: border-style's initial value is "none",
# so an element with no border styling at all reports 0px here, not
# "medium").
style_prop = _BORDER_WIDTH_TO_STYLE.get(target)
if style_prop is not None:
border_style = (self._resolved.get(style_prop) or "").strip().lower()
if border_style in ("none", "hidden"):
return "0px"
# a minimal UA-stylesheet default: these tags are never content, so
# a real browser's UA stylesheet gives them display:none outright --
# it's why a <script> tag's source text never shows up as a
# paragraph on the page. Weakest possible priority, exactly like a
# real UA stylesheet: only applies when nothing (author rule,
# inline style) ever declared `display` at all, so any actual
# cascade value for it is completely unaffected.
if (
target == "display"
and not self._pseudo
and "display" not in self._resolved._declared
and (getattr(self._element, "tagName", "") or "").lower() in _UA_DISPLAY_NONE_TAGS
):
return "none"
# a shorthand is rebuilt from its (already used-value-resolved) longhands
# -- but only once its own raw value has no unresolved var() left in
# it: a fallback's internal comma/space isn't safe to split into
# per-longhand pieces (see _has_paren_internal_whitespace), so this
# shorthand's own longhands were never actually given a real,
# correctly-substituted value to rebuild from -- each one falls back
# to its bare initial value instead, which "rebuilds" into a
# confidently wrong result rather than the real one below.
raw_target_value = self._resolved.get(target) or ""
if (
_cssom.is_shorthand(target)
and "var(" not in raw_target_value.lower()
and _cssom.expand_shorthand(target, raw_target_value) is not None
):
built = _cssom.build_shorthand(target, self.getPropertyValue)
if built:
return built
value = self._resolved.get(target)
# min-width/min-height's initial (and only auto) value never actually
# behaves as "no minimum" in a browser -- Chrome (and every other
# engine) reports it as 0px rather than the literal keyword.
if target in _MIN_SIZE_PROPERTIES and (value or "auto").strip().lower() == "auto":
return "0px"
if value:
if not target.startswith("--") and "var(" in value:
value = _expand_var_references(value, self._custom_property).strip()
if target in _USED_LENGTH_PROPERTIES:
return self._to_used_length(target, value)
if target in _COLOR_PROPERTIES or target.endswith("color"):
return self._to_used_color(target, value)
if target == "transform":
composed = _transform_to_matrix(
value,
em_px=self._font_size_px(),
rem_px=self._root_font_size_px(),
)
return composed if composed is not None else value
if target == "display":
return self._computed_display(value)
mapped = _COMPUTED_KEYWORD_MAP.get(target)
if mapped is not None:
return mapped.get(value.strip().lower(), value)
return value
if _cssom.is_shorthand(target):
return _cssom.build_shorthand(target, self.getPropertyValue)
return value
# -- used-value resolution (font-relative + absolute length units) ----
def _to_used_color(self, target: str, value: str) -> str:
# currentColor (anywhere but `color` itself) -> the computed color
if target != "color" and "currentcolor" in value.lower():
current = self.getPropertyValue("color")
if current and current.lower() != "currentcolor":
# ``current`` is page-controlled CSS text. Passing it as a
# replacement string would make ``re.sub`` interpret any
# backslash-number sequence as a group reference; a callback
# returns it literally regardless of its contents.
value = re.sub(r"(?i)\bcurrentcolor\b", lambda _match: current, value)
normalized = _cssom.normalize_color(value)
return normalized if normalized is not None else value
def _font_size_px(self) -> float:
cached = self.__dict__.get("_font_size_px_cache")
if cached is not None:
return cached
parent = self._parent_computed()
parent_px = parent._font_size_px() if parent is not None else 16.0
raw = str(self._resolved.get("font-size") or "medium").strip()
# Every other property's used-value path expands a `var()` reference
# before parsing the result as a length (see the generic dispatch in
# `_compute_property_value`) -- this one has to do it too, and
# explicitly, rather than relying on a caller having already done it:
# `_font_size_px` is not just reached through `getPropertyValue`, it
# is also the em/rem base every *other* used-length resolution goes
# through (`_to_used_length`, `_eval_calc_to_px`, line-height's
# unitless multiplier, ...), so leaving `var(--x)` unexpanded here
# silently fails to parse as a length and falls back to the
# *inherited* size instead -- wrong for the common case of a
# design-token stylesheet (`font-size: var(--font-size-small,
# 0.875rem)`), not just a synthetic one.
if "var(" in raw.lower():
raw = _expand_var_references(raw, self._custom_property).strip()
if "calc(" in raw.lower():
px = _eval_calc_to_px(raw, em_px=parent_px, rem_px=self._root_font_size_px())
else:
px = _length_string_to_px(
raw,
em_px=parent_px,
rem_px=self._root_font_size_px(),
percent_px=parent_px,
)
if px is None:
px = _ABSOLUTE_FONT_SIZE_KEYWORDS.get(raw.lower(), parent_px)
px = self._apply_monospace_font_size_quirk(px, parent)
self.__dict__["_font_size_px_cache"] = px
return px
def _apply_monospace_font_size_quirk(self, px: float, parent: "ComputedStyleDeclaration | None") -> float:
"""The "monospace font-size quirk" every real browser applies (Blink:
``FontBuilder::CheckForGenericFamilyChange``): an element whose
``font-family`` resolves to exactly the single generic ``monospace``
keyword gets its font-size scaled by 13/16 relative to what it would
otherwise be (the reverse, 16/13, on leaving monospace) -- the reason
a bare monospace element renders visibly smaller than surrounding
text, and why ``font-family: monospace, monospace`` is the
well-known trick to opt back out of it. It only fires at the exact
point monospace-ness *changes* between an element and its parent (a
descendant that inherits an already-monospace, already-scaled
ancestor is not scaled again), and only when this element's own
``font-size`` was not itself specified as an absolute length -- an
explicit ``font-size: 16px`` on the monospace element itself always
means exactly ``16px``, never a scaled value.
Confirmed against ``wpt/css/CSS2/tables/table-anonymous-objects-
059.xht``. This is squarely a cascade-level default, not a rendering
one -- the 13/16 ratio is a fixed constant, not something derived
from real glyph metrics, so it needs no font data domonic doesn't
have."""
own_declared = str(self._resolved._declared.get("font-size") or "").strip()
if own_declared and "var(" in own_declared.lower():
own_declared = _expand_var_references(own_declared, self._custom_property).strip()
own_match = _LENGTH_TOKEN_RE.match(own_declared) if own_declared else None
if own_match and own_match.group(2).lower() in _ABSOLUTE_LENGTH_UNITS:
return px # an explicit absolute length always opts out
own_monospace = _is_exactly_generic_monospace(self.getPropertyValue("font-family"))
parent_monospace = _is_exactly_generic_monospace(parent.getPropertyValue("font-family")) if parent else False
if own_monospace == parent_monospace:
return px
return px * _MONOSPACE_FONT_SIZE_SCALE if own_monospace else px / _MONOSPACE_FONT_SIZE_SCALE
def _root_font_size_px(self) -> float:
cache = self._chain_cache
if "__root_font_px__" in cache:
return cache["__root_font_px__"]
node = self._element
root = node
while True:
parent = getattr(node, "parentNode", None)
if parent is None or not _is_inheritance_source(parent):
root = node
break
node = parent
px = 16.0
if root is not self._element:
rc = ComputedStyleDeclaration(root, None, _chain_cache=cache)
px = rc._font_size_px()
cache["__root_font_px__"] = px
return px
def _parent_computed(self):
parent = getattr(self._element, "parentNode", None)
if parent is None or not _is_inheritance_source(parent):
return None
cache = self._chain_cache
got = cache.get(id(parent))
if got is None:
got = ComputedStyleDeclaration(parent, None, _chain_cache=cache)
cache[id(parent)] = got
return got
def _to_used_length(self, target: str, value: str) -> str:
if target == "font-size":
return _px_str(self._font_size_px())
# border-width's keywords (thin/medium/thick) resolve to a fixed px
# length; a "none"/"hidden" border-style already short-circuits to
# "0px" in _compute_property_value before this is reached, so a
# keyword seen here always belongs to a style that actually draws.
if target in _BORDER_WIDTH_TO_STYLE:
keyword_px = _BORDER_WIDTH_KEYWORD_PX.get(value.strip().lower())
if keyword_px is not None:
return _px_str(keyword_px)
# Once a layout engine has attached geometry to this element, its own
# box holds the real used value for whichever of these was "auto" --
# not a guess. (A percentage is handled separately below, against
# the *parent's* box -- resolving it doesn't need this element's own
# geometry, only its containing block's.)
auto_field = _AUTO_BOX_FIELDS.get(target)
if auto_field is not None and value.strip().lower() == "auto":
box = _element_layout_box(self._element)
if box is not None:
if target in ("width", "height"):
used = self._content_size_from_box(box, vertical=(target == "height"))
else:
used = getattr(box, auto_field, None)
if used is not None:
return _px_str(used)
font_px = self._font_size_px()
if "calc(" in value.lower():
evaluated = _eval_calc_to_px(value, em_px=font_px, rem_px=self._root_font_size_px())
if evaluated is not None:
# see the ``target == "line-height"`` branch below for why a
# negative result here becomes ``normal`` instead of a real
# negative used length -- ``calc()`` is the one other path
# that can produce a negative line-height.
if target == "line-height" and evaluated < 0:
return "normal"
return _px_str(evaluated)
return value
if target == "line-height":
low = value.strip().lower()
if low in ("normal", "inherit", "initial", "unset", ""):
return value
match = _LENGTH_TOKEN_RE.match(value.strip())
# CSS 2.1 10.8.1: line-height "does not allow negative values" --
# a negative multiplier, percentage, or length makes the whole
# declaration invalid, so a real browser discards it and the used
# value falls back to ``normal`` instead of a real negative
# length (which would otherwise drive a negative line-box strut
# height downstream). Domonic has no general cascade-level
# "declaration validity" concept that would let a different,
# lower-priority but valid declaration win instead when one
# exists -- narrower than the full spec behaviour, but correct
# for the common case where line-height has only the one,
# invalid declaration. Checked before the per-form branches below
# so it covers all three forms -- 1.5, 150%, and 20px alike --
# with one check instead of three.
if match and float(match.group(1)) < 0:
return "normal"
if match and match.group(2) == "": # unitless multiplier
return _px_str(float(match.group(1)) * font_px)
if match and match.group(2) == "%": # % of the computed font-size
return _px_str(float(match.group(1)) / 100.0 * font_px)
percent_px = self._percent_base_px(target) if "%" in value else None
parts = []
changed = False
for token in value.split():
px = _length_string_to_px(
token,
em_px=font_px,
rem_px=self._root_font_size_px(),
percent_px=percent_px,
)
if px is None:
parts.append(token)
else:
changed = True
parts.append(_px_str(px))
return " ".join(parts) if changed else value
def _content_size_from_box(self, box: Any, vertical: bool) -> "float | None":
"""The width/height a ``LayoutBox`` contributes to getComputedStyle.
``box.content_width``/``content_height`` is used directly when the
layout engine set it explicitly. Otherwise, fall back to the concrete
box dimensions the engine handed back so consumers can round-trip real
layout geometry without also calculating content dimensions."""
explicit = box.content_height if vertical else box.content_width
if explicit is not None:
return explicit
return box.height if vertical else box.width
#: two-value ``display`` syntax -> the legacy single keyword a browser
#: reports (CSS Display 3 "computed value" column)
_DISPLAY_TWO_VALUE = {
"block flow": "block",
"inline flow": "inline",
"block flow-root": "flow-root",
"inline flow-root": "inline-block",
"block flex": "flex",
"inline flex": "inline-flex",
"block grid": "grid",
"inline grid": "inline-grid",
"block table": "table",
"inline table": "inline-table",
"block list-item": "list-item",
"flow list-item": "list-item",
"block flow list-item": "list-item",
}
_DISPLAY_BLOCKIFY = {
"inline": "block",
"inline-block": "block",
"run-in": "block",
"inline-table": "table",
"inline-flex": "flex",
"inline-grid": "grid",
"-webkit-inline-box": "-webkit-box",
}
def _computed_display(self, value: str) -> str:
display = value.strip().lower()
display = self._DISPLAY_TWO_VALUE.get(display, display)
if display in ("none", "contents"):
return display
float_val = str(self._resolved.get("float") or "none").strip().lower()
position = str(self._resolved.get("position") or "static").strip().lower()
if float_val != "none" or position in ("absolute", "fixed"):
return self._DISPLAY_BLOCKIFY.get(display, display)
return display
def _percent_base_px(self, target: str) -> "float | None":
"""The px a ``%`` resolves against for *target*: the used inline size
of the containing block. Once a layout engine has attached a box to
the parent, its actual content size is used -- exact, and correct
for any percentage-eligible property, not just the ones
``_to_used_length`` special-cases for ``auto``. Failing that, this
falls back to an approximation found without full layout: the
nearest ancestor with an explicit px ``width`` (or ``height`` for the
vertical properties), else the viewport for a root-level element."""
vertical = target in ("height", "min-height", "max-height", "top", "bottom")
prop = "height" if vertical else "width"
node = getattr(self._element, "parentNode", None)
parent_box = _element_layout_box(node)
if parent_box is not None:
parent_computed = self._parent_computed()
if parent_computed is not None:
used = parent_computed._content_size_from_box(parent_box, vertical)
if used is not None:
return used
cache = self._chain_cache
while node is not None and _is_inheritance_source(node):
declared = _parse_css_declarations(getattr(node, "getAttribute", lambda *_: "")("style") or "")
for name, val, _ in declared:
if name == prop:
px = _length_string_to_px(
val.strip(),
em_px=16.0,
rem_px=self._root_font_size_px(),
percent_px=None,
)
if px is not None:
return px
node = getattr(node, "parentNode", None)
# root-level: fall back to the viewport inline size
window = cache.get("__window__")
if window is None:
document = getattr(self._element, "ownerDocument", None)
window = getattr(document, "defaultView", None) if document else None
cache["__window__"] = window
if window is not None:
return float(getattr(window, "innerHeight" if vertical else "innerWidth", 0) or 0) or None
return None
def _property_entries(self):
# getComputedStyle enumerates longhands (and custom properties) only,
# never the shorthand entries the cascade also parked in _resolved,
# and reports used values
return [
(name, self.getPropertyValue(name), "")
for name in self._resolved.names()
if name.startswith("--") or not _cssom.is_shorthand(name)
]
def _expanded_entries(self):
# a computed style is already a flat set of resolved longhands
return self._property_entries()
@property
def cssText(self):
if not getattr(self, "_resolved", None):
return getattr(self, "_css_text", "")
return _serialize_css_declarations(self._property_entries())
@cssText.setter
def cssText(self, value):
if getattr(self, "_resolved", None) is None:
object.__setattr__(self, "_css_text", "" if value is None else str(value))
return
raise Exception("NoModificationAllowedError: getComputedStyle is read-only")
[docs]
def setProperty(self, *_a, **_k):
raise Exception("NoModificationAllowedError: getComputedStyle is read-only")
[docs]
def removeProperty(self, *_a, **_k):
raise Exception("NoModificationAllowedError: getComputedStyle is read-only")
#: properties whose computed value getComputedStyle reports as a used <length>
#: in px. ``%`` / ``auto`` / ``calc()`` / keywords are left untouched (they need
#: layout); only font-relative and absolute units are resolved.
#: a border-*-width longhand -> the border-*-style longhand that gates it
#: (its computed value is 0 whenever that style is "none"/"hidden")
_BORDER_WIDTH_TO_STYLE = {
"border-top-width": "border-top-style",
"border-right-width": "border-right-style",
"border-bottom-width": "border-bottom-style",
"border-left-width": "border-left-style",
}
#: border-width's three keywords, CSS 2.1 8.5.3 -- the spec leaves their
#: exact widths up to the UA beyond "thin < medium < thick"; 1px/3px/5px is
#: the convention every browser (Chrome included) uses.
_BORDER_WIDTH_KEYWORD_PX = {"thin": 1.0, "medium": 3.0, "thick": 5.0}
#: tags a real browser's UA stylesheet gives display:none outright, since
#: nothing about them is content.
_UA_DISPLAY_NONE_TAGS = frozenset({"head", "title", "script", "style", "meta", "link", "noscript", "template"})
#: min-width/min-height's initial value is "auto", but no engine treats an
#: unresolved "auto" as the reported computed value -- see _compute_property_value
_MIN_SIZE_PROPERTIES = frozenset({"min-width", "min-height"})
def _is_inheritance_source(node) -> bool:
"""Whether *node* is a real ancestor to inherit computed style from.
Ordinarily that means ``nodeType == ELEMENT_NODE``. But domonic's ``html``
tag doubles as the ``Document`` (the last one created becomes the active
document -- see the dom guide), so the actual root ``<html>`` element
reports ``nodeType == DOCUMENT_NODE`` like any other ``Document``. Without
this, nothing set on ``<html>`` itself (an attribute selector, a class, a
tag rule) ever reaches ``<body>`` or anything below it via inheritance --
a real ``Document`` is excluded since it has no ``tagName`` of its own."""
node_type = getattr(node, "nodeType", None)
if node_type == 1: # Node.ELEMENT_NODE
return True
return node_type == 9 and bool(getattr(node, "tagName", "")) # Node.DOCUMENT_NODE
#: an "auto" longhand -> the LayoutBox field holding its real used value,
#: once a layout engine has attached one (see _to_used_length)
_AUTO_BOX_FIELDS = {
"width": "content_width",
"height": "content_height",
"margin-top": "margin_top",
"margin-right": "margin_right",
"margin-bottom": "margin_bottom",
"margin-left": "margin_left",
}
def _element_layout_box(element):
"""The ``domonic.layout.LayoutBox`` attached to *element*, or ``None``
if no layout engine has supplied one (or *element* is ``None``, e.g. a
root element's parent). A lazy import: ``domonic.layout`` imports this
module for the cascade, so importing it back at module scope here would
be circular."""
if element is None:
return None
try:
from domonic.layout import get_layout_box
except ImportError: # pragma: no cover - domonic.layout always ships with domonic
return None
return get_layout_box(element)
_USED_LENGTH_PROPERTIES = frozenset(
{
"font-size",
"line-height",
"letter-spacing",
"word-spacing",
"text-indent",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"border-top-width",
"border-right-width",
"border-bottom-width",
"border-left-width",
"outline-width",
"column-rule-width",
"column-width",
"column-gap",
"row-gap",
"border-spacing",
"width",
"height",
"min-width",
"min-height",
"max-width",
"max-height",
"top",
"right",
"bottom",
"left",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
}
)
#: enumerated computed values a browser reports in a canonical form
_COMPUTED_KEYWORD_MAP: dict[str, dict[str, str]] = {
"font-weight": {"normal": "400", "bold": "700"},
}
#: properties whose computed value getComputedStyle reports as ``rgb()`` /
#: ``rgba()`` (also everything matching ``*color`` -- see getPropertyValue)
_COLOR_PROPERTIES = frozenset(
{
"color",
"background-color",
"border-top-color",
"border-right-color",
"border-bottom-color",
"border-left-color",
"outline-color",
"column-rule-color",
"text-decoration-color",
"caret-color",
"text-emphasis-color",
"fill",
"stroke",
"stop-color",
"flood-color",
"lighting-color",
}
)
_ABSOLUTE_FONT_SIZE_KEYWORDS = {
"xx-small": 9.6,
"x-small": 12.0,
"small": 13.333,
"medium": 16.0,
"large": 18.667,
"x-large": 24.0,
"xx-large": 32.0,
"xxx-large": 48.0,
}
#: the fixed 13/16 ratio behind the "monospace font-size quirk" -- see
#: ``ComputedStyleDeclaration._apply_monospace_font_size_quirk``.
_MONOSPACE_FONT_SIZE_SCALE = 13.0 / 16.0
def _is_exactly_generic_monospace(font_family: "str | None") -> bool:
"""Whether a resolved ``font-family`` is the single generic keyword
``monospace`` and nothing else -- ``monospace, Arial`` or a quoted
family name does not count, only the exact generic keyword alone (the
well-known ``font-family: monospace, monospace`` trick relies on the
second entry making this ``False`` again)."""
return (font_family or "").strip().strip("'\"").lower() == "monospace"
#: absolute length unit -> px (CSS reference pixel: 1in == 96px)
_ABSOLUTE_LENGTH_UNITS = {
"px": 1.0,
"in": 96.0,
"cm": 96.0 / 2.54,
"mm": 96.0 / 25.4,
"q": 96.0 / 25.4 / 4,
"pt": 96.0 / 72.0,
"pc": 16.0,
}
_LENGTH_TOKEN_RE = re.compile(r"^([+-]?(?:\d+\.?\d*|\.\d+))([a-z%]*)$", re.I)
def _px_str(px: float) -> str:
rounded = round(px, 4)
if rounded == int(rounded):
return f"{int(rounded)}px"
return f"{rounded}px"
_PX_VALUE_RE = re.compile(r"^(-?\d+\.?\d*)px$")
def _px_value(value: "str | None") -> "float | None":
"""The number out of an already-resolved ``"Npx"`` string, or ``None`` if
*value* isn't one (used-value resolution failed, or it's still a keyword/
percentage) -- lets ``_content_size_from_box`` bail out to the ordinary
fallback instead of raising."""
if not value:
return None
match = _PX_VALUE_RE.match(value.strip())
return float(match.group(1)) if match else None
def _angle_to_deg(token: str) -> "float | None":
match = re.match(r"^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|grad|turn)?$", token.strip(), re.I)
if not match:
return None
number = float(match.group(1))
unit = (match.group(2) or "deg").lower()
return {
"deg": number,
"rad": math.degrees(number),
"grad": number * 0.9,
"turn": number * 360.0,
}[unit]
def _transform_to_matrix(value: str, *, em_px: float, rem_px: float) -> "str | None":
"""Compose a CSS ``transform`` list into ``matrix(...)`` / ``matrix3d(...)``
the way ``getComputedStyle`` reports it, or ``None`` to keep it verbatim
(a percentage length, or an unsupported function)."""
text = value.strip()
if text.lower() in ("", "none"):
return "none"
functions = re.findall(r"([a-zA-Z0-9]+)\s*\(([^)]*)\)", text)
if not functions:
return None
from domonic.dom import DOMMatrix
result = DOMMatrix()
def length(tok: str) -> "float | None":
return _length_string_to_px(tok, em_px=em_px, rem_px=rem_px, percent_px=None)
# apply left-to-right: DOMMatrix.multiplySelf post-multiplies (self . other,
# spec order), so composing in written order -- I . f1 . f2 . ... -- gives
# the same matrix CSS does (the rightmost function applied to the point
# first).
for name, raw_args in functions:
fn = name.lower()
args = [a.strip() for a in raw_args.split(",") if a.strip()]
try:
if fn in ("translate", "translatex", "translatey", "translate3d"):
xs = args + ["0", "0", "0"]
tx = length(xs[0]) if fn != "translatey" else 0.0
ty = (
length(xs[0])
if fn == "translatey"
else length(xs[1]) if fn in ("translate", "translate3d") else 0.0
)
if tx is None or ty is None:
return None
result.translateSelf(tx, ty)
elif fn == "translatez":
if length(args[0]) is None:
return None
elif fn in ("scale", "scalex", "scaley", "scale3d"):
if fn == "scalex":
sx, sy = float(args[0]), 1.0
elif fn == "scaley":
sx, sy = 1.0, float(args[0])
else:
sx = float(args[0])
sy = float(args[1]) if len(args) > 1 else sx
result.scaleSelf(sx, sy)
elif fn in ("rotate", "rotatez"):
deg = _angle_to_deg(args[0])
if deg is None:
return None
result.rotateSelf(deg)
elif fn in ("rotatex", "rotatey"):
deg = _angle_to_deg(args[0])
if deg is None:
return None
if fn == "rotatex":
result.rotateSelf(deg, 0, 0)
else:
result.rotateSelf(0, deg, 0)
elif fn == "rotate3d":
x, y, z = float(args[0]), float(args[1]), float(args[2])
deg = _angle_to_deg(args[3])
if deg is None:
return None
result.rotateSelf(x * deg, y * deg, z * deg)
elif fn in ("skew", "skewx", "skewy"):
ax = _angle_to_deg(args[0]) if fn != "skewy" else 0.0
ay = (
_angle_to_deg(args[0])
if fn == "skewy"
else _angle_to_deg(args[1]) if fn == "skew" and len(args) > 1 else 0.0
)
if ax is None or ay is None:
return None
if ay:
result.skewYSelf(ay)
if ax:
result.skewXSelf(ax)
elif fn in ("matrix", "matrix3d"):
result.multiplySelf(DOMMatrix(*[float(a) for a in args]))
elif fn == "perspective":
if length(args[0]) is None:
return None
else:
return None
except (ValueError, IndexError):
return None
return result.toString()
_CH_EX_RESOLVER: "Callable[[str, float, float], float | None] | None" = None
[docs]
def set_ch_ex_resolver(resolver: "Callable[[str, float, float], float | None] | None") -> None:
"""Register a callback used to resolve ``ch``/``ex`` length units instead
of the CSS-spec 0.5em approximation ``getComputedStyle`` falls back to by
default. Domonic itself has no glyph metrics to measure a real character
advance or x-height against, but a renderer built on top of it (which
does have a font) can supply one.
Called as ``resolver(unit, number, em_px) -> float | None`` -- *unit* is
``"ch"`` or ``"ex"``, *number* the numeric part of the token (``2`` for
``"2ch"``), *em_px* the element's computed font-size in px -- only once
the parser has already identified the token as one of these two units, so
every other length (the overwhelming majority) never pays for the call.
Returning ``None`` falls back to the default approximation for that one
conversion. Pass ``None`` to remove a previously registered resolver."""
global _CH_EX_RESOLVER
_CH_EX_RESOLVER = resolver
_PRESENTATIONAL_HINT_RESOLVER: "Callable[[Any], dict[str, Any] | None] | None" = None
[docs]
def set_presentational_hint_resolver(resolver: "Callable[[Any], dict[str, Any] | None] | None") -> None:
"""Register a callback that supplies presentational hints -- the implicit
style a legacy HTML attribute carries (``bgcolor``, an ``<img>``'s
``width``/``height``, ...) -- for ``getComputedStyle``'s cascade to fold
in at the one priority a real browser gives them: weaker than *any*
author stylesheet rule for the same property, and weaker than an inline
``style=""``, but still stronger than a property's plain initial value.
Domonic has no opinion on which HTML attributes should become which CSS
properties -- that mapping is rendering policy for whatever is built on
top of it -- so this only owns the cascade-priority slot only the
cascade itself can get right.
Called as ``resolver(element) -> {property_name: value} | None`` once per
cascade resolution, kebab-case longhand property names (``"background-
color"``, not ``backgroundColor``). A returned entry is used only for a
property no real author rule declared at all; any real declaration --
of any specificity, source order, or a later CSSOM write -- overrides it
normally, with no special-casing needed anywhere else in the cascade.
"Once per cascade resolution" means the same thing it already means for
an author stylesheet: consulted fresh on a cache miss (the first read,
or after a DOM/stylesheet mutation invalidates the cached cascade), not
re-consulted just because whatever *resolver* itself reads changed on
its own with nothing else moving -- so a host that computes hints from
an element's own attributes should set them before that element's style
is first read, the same ordering it would already need for a real
``style=""`` attribute set via ``setAttribute``.
Pass ``None`` to remove a previously registered resolver."""
global _PRESENTATIONAL_HINT_RESOLVER
_PRESENTATIONAL_HINT_RESOLVER = resolver
def _length_string_to_px(token: str, *, em_px: float, rem_px: float, percent_px: "float | None") -> "float | None":
"""Convert a single length token to px, or ``None`` if it is not a plain
length this resolver handles (``auto``, ``calc(...)``, a bare keyword, or a
``%`` with no base)."""
stripped = token.strip()
# Fast path for the tokens a stylesheet overwhelmingly actually uses --
# a bare integer ``px`` length (``12px``, ``0px``) or a bare ``0`` -- so
# the common case skips the regex match, the ``.lower()`` call, and the
# unit-table lookup below. Anything else (a decimal, a different unit, a
# keyword) falls through to the general path unchanged.
if stripped == "0":
return 0.0
if stripped.endswith("px"):
head = stripped[:-2]
if head and (head.isdigit() or (head[0] in "+-" and head[1:].isdigit())):
return float(head)
match = _LENGTH_TOKEN_RE.match(stripped)
if not match:
return None
number, unit = float(match.group(1)), match.group(2).lower()
if unit in ("",) and number == 0:
return 0.0
if unit == "%":
return None if percent_px is None else number / 100.0 * percent_px
if unit in ("em", "rem"):
return number * (em_px if unit == "em" else rem_px)
if unit in ("ch", "ex"):
# domonic has no real glyph metrics ("0" advance / x-height) of its
# own to measure against, so it defaults to the value the CSS spec
# itself mandates for exactly this case: 0.5em for both units. A
# host that does have real font metrics (a renderer built on top of
# domonic) can register a resolver to use them instead -- called
# only once the unit is already known to be ``ch``/``ex``, so a
# plain ``px``/``em`` length never pays for the dispatch.
if _CH_EX_RESOLVER is not None:
resolved = _CH_EX_RESOLVER(unit, number, em_px)
if resolved is not None:
return resolved
return number * 0.5 * em_px
if unit == "":
return None # a unitless non-zero number is not a length
return None if unit not in _ABSOLUTE_LENGTH_UNITS else number * _ABSOLUTE_LENGTH_UNITS[unit]
_CALC_LENGTH_TERM_RE = re.compile(r"(?<![\w.])([+-]?(?:\d+\.?\d*|\.\d+))(px|em|rem|ch|ex|in|cm|mm|q|pt|pc)\b", re.I)
def _eval_calc_to_px(expr: str, *, em_px: float, rem_px: float) -> "float | None":
"""Evaluate a ``calc(...)`` body to px, or ``None`` if it mixes in a
percentage / viewport / other unit that needs layout."""
body = expr.strip()
if body.lower().startswith("calc(") and body.endswith(")"):
body = body[5:-1]
# any unit we cannot resolve to px -> bail (keep the calc() verbatim)
if re.search(r"(?<![\w.])[+-]?(?:\d+\.?\d*|\.\d+)" r"(%|vw|vh|vmin|vmax|fr|svh|lvh|dvh)\b", body, re.I):
return None
if "calc(" in body.lower():
return None # nested calc -- keep it simple
def _sub(match: "re.Match[str]") -> str:
px = _length_string_to_px(match.group(0), em_px=em_px, rem_px=rem_px, percent_px=None)
return repr(px) if px is not None else match.group(0)
numeric = _CALC_LENGTH_TERM_RE.sub(_sub, body)
if re.search(r"[a-z]", numeric, re.I): # an unresolved unit slipped through
return None
if not re.fullmatch(r"[-+*/()\d.eE\s]+", numeric):
return None
try:
node: Any = ast.parse(numeric, "<calc>", "eval")
except SyntaxError:
return None
def _ev(n: Any) -> float:
if isinstance(n, ast.Expression):
return _ev(n.body)
if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)):
return float(n.value)
if isinstance(n, ast.UnaryOp) and isinstance(n.op, (ast.UAdd, ast.USub)):
v = _ev(n.operand)
return v if isinstance(n.op, ast.UAdd) else -v
if isinstance(n, ast.BinOp) and isinstance(n.op, (ast.Add, ast.Sub, ast.Mult, ast.Div)):
a, b = _ev(n.left), _ev(n.right)
if isinstance(n.op, ast.Add):
return a + b
if isinstance(n.op, ast.Sub):
return a - b
if isinstance(n.op, ast.Mult):
return a * b
return a / b if b else 0.0
raise ValueError("unsupported calc expression")
try:
return _ev(node)
except (ValueError, ZeroDivisionError):
return None
def _expand_var_references(value: str, resolve, _depth: int = 0) -> str:
"""Substitute ``var(--name[, fallback])`` references in a computed value.
``resolve(name)`` returns the computed value of custom property ``name``
(``""`` when unset). Follows the spec fallback rule and is paren-aware so
nested ``var()`` in the fallback works; bails out on deep recursion (a
custom-property cycle).
"""
if _depth > 20 or "var(" not in value:
return value
out: list[str] = []
i = 0
length = len(value)
while i < length:
if value.startswith("var(", i):
depth = 0
j = i + 3
while j < length:
if value[j] == "(":
depth += 1
elif value[j] == ")":
depth -= 1
if depth == 0:
break
j += 1
inner = value[i + 4 : j]
comma = _find_next_top_level_char(inner, 0, ",")
if comma == -1:
name, fallback = inner.strip(), None
else:
name = inner[:comma].strip()
fallback = inner[comma + 1 :].strip()
substituted = resolve(name) if name.startswith("--") else ""
if substituted and substituted.strip():
out.append(_expand_var_references(substituted, resolve, _depth + 1))
elif fallback is not None:
out.append(_expand_var_references(fallback, resolve, _depth + 1))
# else: unresolved with no fallback -> contributes nothing
i = j + 1
continue
out.append(value[i])
i += 1
return "".join(out)
class _ResolvedView:
"""Lazy property resolver: explicit value -> inherited -> initial."""
def __init__(self, declared: dict[str, str], parent_computed):
self._declared = declared
self._parent = parent_computed
def names(self):
return list(self._declared)
def _inherited(self, name: str) -> str:
if self._parent is not None:
value = self._parent.getPropertyValue(name)
if value:
return value
return _cssom.initial_value(name)
def get(self, name: str) -> str:
if name in self._declared:
value = self._declared[name]
low = value.strip().lower()
# CSS-wide keywords as explicit values
if low == "inherit":
return self._inherited(name)
if low == "initial":
return _cssom.initial_value(name)
if low in ("unset", "revert", "revert-layer"):
return self._inherited(name) if _cssom.inherits(name) else _cssom.initial_value(name)
return value
if _cssom.inherits(name) and self._parent is not None:
inherited = self._parent.getPropertyValue(name)
if inherited:
return inherited
return _cssom.initial_value(name)
def _known_css_property_names() -> set[str]:
global _KNOWN_CSS_PROPERTIES
if _KNOWN_CSS_PROPERTIES is None:
style = Style()
names = set(_ADDITIONAL_CSS_PROPERTY_NAMES)
for name in style.__dict__:
if name.startswith("_") or name in _CSS_STYLE_INTERNAL_NAMES:
continue
names.add(_css_property_name(name))
for name, descriptor in Style.__dict__.items():
if isinstance(descriptor, property):
names.add(_css_property_name(name))
_KNOWN_CSS_PROPERTIES = names
return _KNOWN_CSS_PROPERTIES
def _has_balanced_css_delimiters(text: str) -> bool:
quote = None
escaped = False
stack: list[str] = []
pairs = {")": "(", "]": "[", "}": "{"}
for char in text or "":
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([{":
stack.append(char)
continue
if char in ")]}":
if not stack or stack[-1] != pairs[char]:
return False
stack.pop()
return quote is None and not stack
def _supports_property_value(property_name: str, value: str) -> bool:
if property_name != property_name.strip() or not property_name:
return False
if value is None or not str(value).strip() or _IMPORTANT_RE.search(str(value)):
return False
if not _has_balanced_css_delimiters(str(value)):
return False
normalized = _css_property_name(property_name)
if normalized.startswith("--"):
return len(normalized) > 2
return normalized in _known_css_property_names()
def _supports_condition(condition_text: str) -> bool:
text = (condition_text or "").strip()
if not text:
return False
split = _split_top_level_word(text, "or")
if split:
return _supports_condition(split[0]) or _supports_condition(split[1])
split = _split_top_level_word(text, "and")
if split:
return _supports_condition(split[0]) and _supports_condition(split[1])
if text.lower().startswith("not "):
return not _supports_condition(text[4:].strip())
text = _strip_wrapping_parens(text)
lower = text.lower()
for function_name in (
"selector",
"font-tech",
"font-format",
"named-feature",
"env",
):
function_match = _consume_css_function(text, function_name)
if function_match and function_match[0]:
return True
at_rule_match = _consume_css_function(text, "at-rule")
if at_rule_match:
return at_rule_match[0].strip().lower() in {
"@container",
"@counter-style",
"@font-face",
"@font-feature-values",
"@import",
"@keyframes",
"@layer",
"@media",
"@namespace",
"@page",
"@property",
"@scope",
"@supports",
}
if lower.startswith("selector("):
return False
entries = _parse_css_declarations(text)
return len(entries) == 1 and _supports_property_value(entries[0][0], entries[0][1])
[docs]
class CSSStyleValue:
"""Base of the CSS Typed OM value hierarchy."""
def __str__(self) -> str: # pragma: no cover - overridden
return ""
[docs]
def toString(self) -> str:
return str(self)
[docs]
@staticmethod
def parse(property_name: str, css_text: str) -> "CSSStyleValue":
return _parse_css_style_value(str(css_text))
[docs]
@staticmethod
def parseAll(property_name: str, css_text: str) -> "list[CSSStyleValue]":
return [_parse_css_style_value(part) for part in _split_top_level_commas(str(css_text))] or [
_parse_css_style_value(str(css_text))
]
[docs]
class CSSKeywordValue(CSSStyleValue):
def __init__(self, value: Any) -> None:
self.value = str(value)
def __str__(self) -> str:
return self.value
def __eq__(self, other: object) -> bool:
if isinstance(other, CSSKeywordValue):
return self.value == other.value
return NotImplemented
def __hash__(self) -> int:
return hash(self.value)
[docs]
class CSSUnitValue(CSSStyleValue):
"""A numeric value with a unit -- ``CSS.px(10)`` / ``CSSUnitValue(10, "px")``."""
def __init__(self, value: float, unit: str) -> None:
self.value = float(value)
# CSSOM unit names: "number", "percent", or the CSS unit token lowercased
self.unit = {"": "number", "%": "percent"}.get(unit, unit.lower())
@property
def _token(self) -> str:
return {"number": "", "percent": "%"}.get(self.unit, self.unit)
def __str__(self) -> str:
number = int(self.value) if self.value == int(self.value) else self.value
return f"{number}{self._token}"
[docs]
def to(self, unit: str) -> "CSSUnitValue":
target = {"": "number", "%": "percent"}.get(unit, unit.lower())
if target == self.unit:
return CSSUnitValue(self.value, unit)
factor = _CSS_UNIT_CANONICAL.get(self.unit)
target_factor = _CSS_UNIT_CANONICAL.get(target)
if factor is None or target_factor is None:
raise TypeError(f"cannot convert {self.unit!r} to {target!r}")
return CSSUnitValue(self.value * factor / target_factor, unit)
def _combine(self, other: Any, op: Callable[[float, float], float]) -> "CSSUnitValue":
if isinstance(other, (int, float)):
return CSSUnitValue(op(self.value, float(other)), self._token)
if isinstance(other, CSSUnitValue) and other.unit == self.unit:
return CSSUnitValue(op(self.value, other.value), self._token)
raise TypeError("CSSUnitValue operands must share a unit")
def __add__(self, other: Any) -> "CSSUnitValue":
return self._combine(other, lambda a, b: a + b)
def __sub__(self, other: Any) -> "CSSUnitValue":
return self._combine(other, lambda a, b: a - b)
def __mul__(self, other: Any) -> "CSSUnitValue":
return self._combine(other, lambda a, b: a * b)
def __truediv__(self, other: Any) -> "CSSUnitValue":
return self._combine(other, lambda a, b: a / b)
def __eq__(self, other: object) -> bool:
if isinstance(other, CSSUnitValue):
return self.value == other.value and self.unit == other.unit
return NotImplemented
def __hash__(self) -> int:
return hash((self.value, self.unit))
#: canonical-unit factors for CSSUnitValue.to() within a compatible group
_CSS_UNIT_CANONICAL = {
"px": 1.0,
"in": 96.0,
"cm": 96.0 / 2.54,
"mm": 96.0 / 25.4,
"q": 96.0 / 25.4 / 4,
"pt": 96.0 / 72.0,
"pc": 16.0,
"deg": 1.0,
"grad": 0.9,
"rad": 180.0 / math.pi,
"turn": 360.0,
"s": 1.0,
"ms": 0.001,
"hz": 1.0,
"khz": 1000.0,
"dppx": 1.0,
"dpi": 1.0 / 96.0,
"dpcm": 2.54 / 96.0,
}
_CSS_TYPED_OM_UNIT_RE = re.compile(r"^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)\s*([a-z%]*)\s*$", re.I)
def _parse_css_style_value(css_text: str) -> CSSStyleValue:
text = str(css_text).strip()
match = _CSS_TYPED_OM_UNIT_RE.match(text)
if match:
return CSSUnitValue(float(match.group(1)), match.group(2))
return CSSKeywordValue(text)
[docs]
class StylePropertyMap:
"""Minimal ``StylePropertyMap`` / ``StylePropertyMapReadOnly``.
Backed by a live ``CSSStyleDeclaration``; ``get`` returns a Typed OM value,
``getAll`` a list, and (when writable) ``set`` / ``append`` / ``delete`` /
``clear`` proxy to the declaration.
"""
def __init__(self, declaration, *, read_only: bool = False) -> None:
self._decl = declaration
self._read_only = read_only
def _guard(self) -> None:
if self._read_only:
raise Exception("NoModificationAllowedError: this map is read-only")
[docs]
def get(self, property_name: str):
value = self._decl.getPropertyValue(property_name)
return _parse_css_style_value(value) if value else None
[docs]
def getAll(self, property_name: str) -> list:
value = self._decl.getPropertyValue(property_name)
if not value:
return []
return [_parse_css_style_value(part) for part in _split_top_level_commas(value)] or [
_parse_css_style_value(value)
]
[docs]
def has(self, property_name: str) -> bool:
return self._decl.getPropertyValue(property_name) != ""
[docs]
def set(self, property_name: str, value: Any) -> None:
self._guard()
self._decl.setProperty(property_name, str(value))
[docs]
def append(self, property_name: str, value: Any) -> None:
self._guard()
existing = self._decl.getPropertyValue(property_name)
combined = f"{existing}, {value}" if existing else str(value)
self._decl.setProperty(property_name, combined)
[docs]
def delete(self, property_name: str) -> None:
self._guard()
self._decl.removeProperty(property_name)
[docs]
def clear(self) -> None:
self._guard()
for name in list(self._decl):
self._decl.removeProperty(name)
def __iter__(self):
for name in self._decl:
yield name, self.get(name)
@property
def size(self) -> int:
return self._decl.length
[docs]
class CSS:
"""Namespace for CSS utility functions."""
# -- CSS Typed OM numeric factories -----------------------------------
number = staticmethod(lambda v=0: CSSUnitValue(v, ""))
percent = staticmethod(lambda v=0: CSSUnitValue(v, "%"))
px = staticmethod(lambda v=0: CSSUnitValue(v, "px"))
em = staticmethod(lambda v=0: CSSUnitValue(v, "em"))
rem = staticmethod(lambda v=0: CSSUnitValue(v, "rem"))
ex = staticmethod(lambda v=0: CSSUnitValue(v, "ex"))
ch = staticmethod(lambda v=0: CSSUnitValue(v, "ch"))
vw = staticmethod(lambda v=0: CSSUnitValue(v, "vw"))
vh = staticmethod(lambda v=0: CSSUnitValue(v, "vh"))
vmin = staticmethod(lambda v=0: CSSUnitValue(v, "vmin"))
vmax = staticmethod(lambda v=0: CSSUnitValue(v, "vmax"))
cm = staticmethod(lambda v=0: CSSUnitValue(v, "cm"))
mm = staticmethod(lambda v=0: CSSUnitValue(v, "mm"))
Q = staticmethod(lambda v=0: CSSUnitValue(v, "Q"))
pt = staticmethod(lambda v=0: CSSUnitValue(v, "pt"))
pc = staticmethod(lambda v=0: CSSUnitValue(v, "pc"))
deg = staticmethod(lambda v=0: CSSUnitValue(v, "deg"))
grad = staticmethod(lambda v=0: CSSUnitValue(v, "grad"))
rad = staticmethod(lambda v=0: CSSUnitValue(v, "rad"))
turn = staticmethod(lambda v=0: CSSUnitValue(v, "turn"))
s = staticmethod(lambda v=0: CSSUnitValue(v, "s"))
ms = staticmethod(lambda v=0: CSSUnitValue(v, "ms"))
fr = staticmethod(lambda v=0: CSSUnitValue(v, "fr"))
dpi = staticmethod(lambda v=0: CSSUnitValue(v, "dpi"))
dpcm = staticmethod(lambda v=0: CSSUnitValue(v, "dpcm"))
dppx = staticmethod(lambda v=0: CSSUnitValue(v, "dppx"))
[docs]
@staticmethod
def escape(ident: Any) -> str:
"""Serialize a string for safe use as a CSS identifier."""
ident = "" if ident is None else str(ident)
result: list[str] = []
length = len(ident)
for index, char in enumerate(ident):
code = ord(char)
if code == 0:
result.append("\ufffd")
elif 0x0001 <= code <= 0x001F or code == 0x007F:
result.append(f"\\{code:x} ")
elif index == 0 and char.isdigit():
result.append(f"\\{code:x} ")
elif index == 1 and char.isdigit() and ident[0] == "-":
result.append(f"\\{code:x} ")
elif index == 0 and char == "-" and length == 1:
result.append("\\-")
elif code >= 0x0080 or char in "-_" or char.isalnum():
result.append(char)
else:
result.append(f"\\{char}")
return "".join(result)
[docs]
@staticmethod
def supports(property: str, value: str | None = None) -> bool:
"""Best-effort syntax-level equivalent of ``CSS.supports()``."""
if value is None:
return _supports_condition(str(property))
return _supports_property_value(str(property), str(value))
[docs]
@staticmethod
def registerProperty(definition) -> None:
"""Register a custom property (CSS Properties and Values API).
``definition`` is a mapping with ``name`` (a ``--`` custom property),
``syntax`` (default ``"*"``), ``inherits`` (bool, required by the spec),
and an optional ``initialValue``. Once registered, the property's
inheritance and initial value are honoured by
``window.getComputedStyle``.
"""
if not isinstance(definition, dict):
definition = {
key: getattr(definition, key)
for key in ("name", "syntax", "inherits", "initialValue")
if hasattr(definition, key)
}
name = str(definition.get("name", ""))
if not name.startswith("--") or len(name) < 3:
raise SyntaxError(f"registerProperty: invalid custom property name {name!r}")
if _cssom.registered_property(name) is not None:
raise Exception(f"InvalidModificationError: {name} has already been registered")
syntax = str(definition.get("syntax", "*")).strip().strip("\"'") or "*"
initial_value = definition.get("initialValue")
if initial_value is not None:
initial_value = str(initial_value)
if syntax != "*" and initial_value is None:
raise SyntaxError("registerProperty: initialValue is required when syntax is not '*'")
_cssom.register_property(
name,
syntax=syntax,
inherits=bool(definition.get("inherits", False)),
initial_value=initial_value,
)
def _normalize_declaration_text(css_text: str) -> str:
text = " ".join(line.strip() for line in (css_text or "").strip().splitlines() if line.strip())
text = re.sub(r"\s+", " ", text)
entries = _parse_css_declarations(text)
if not entries:
return text.strip()
serialized = _serialize_css_declarations(entries)
if len(entries) == 1 and not text.rstrip().endswith(";"):
return serialized[:-1]
return serialized
def _skip_css_whitespace(css_text: str, index: int) -> int:
while index < len(css_text) and css_text[index].isspace():
index += 1
return index
def _find_next_rule_boundary(css_text: str, start: int) -> tuple[int, str]:
quote = None
escaped = False
depth = 0
for index in range(start, len(css_text)):
char = css_text[index]
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([":
depth += 1
continue
if char in ")]" and depth > 0:
depth -= 1
continue
if depth == 0 and char in "{;":
return index, char
return -1, ""
def _find_next_top_level_char(css_text: str, start: int, target: str) -> int:
quote = None
escaped = False
depth = 0
for index in range(start, len(css_text)):
char = css_text[index]
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char in "([":
depth += 1
continue
if char in ")]" and depth > 0:
depth -= 1
continue
if depth == 0 and char == target:
return index
return -1
def _find_matching_brace(css_text: str, open_index: int) -> int:
quote = None
escaped = False
depth = 0
for index in range(open_index, len(css_text)):
char = css_text[index]
if quote is not None:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"'):
quote = char
continue
if char == "{":
depth += 1
continue
if char == "}":
depth -= 1
if depth == 0:
return index
return -1
def _style_from_declarations(css_text: str, parentRule: CSSRule) -> CSSStyleDeclaration:
style = CSSStyleDeclaration()
style.cssText = _normalize_declaration_text(css_text)
style.parentRule = parentRule
return style
def _extract_style_block_parts(block: str) -> tuple[str, list[tuple[str, str, str]]]:
declarations: list[str] = []
nested_parts: list[tuple[str, str, str]] = []
cursor = 0
seen_nested_rule = False
while True:
open_index = _find_next_top_level_char(block, cursor, "{")
if open_index == -1:
tail = block[cursor:]
if seen_nested_rule:
if _parse_css_declarations(tail):
nested_parts.append(("declarations", "", tail))
else:
declarations.append(tail)
break
segment = block[cursor:open_index]
split_at = segment.rfind(";")
if split_at == -1:
declarations_text = ""
nested_prelude = segment.strip()
else:
declarations_text = segment[: split_at + 1]
nested_prelude = segment[split_at + 1 :].strip()
if seen_nested_rule:
if _parse_css_declarations(declarations_text):
nested_parts.append(("declarations", "", declarations_text))
else:
declarations.append(declarations_text)
close_index = _find_matching_brace(block, open_index)
if close_index == -1:
if seen_nested_rule:
if _parse_css_declarations(block[open_index:]):
nested_parts.append(("declarations", "", block[open_index:]))
else:
declarations.append(block[open_index:])
break
nested_parts.append(("rule", nested_prelude, block[open_index + 1 : close_index]))
seen_nested_rule = True
cursor = close_index + 1
return "".join(declarations), nested_parts
def _parse_single_container_condition(value: str) -> dict[str, str]:
value = (value or "").strip()
if not value:
return {"name": "", "query": ""}
lower = value.lower()
query_starters = ("(", "style(", "scroll-state(", "not ", "not(", "and ", "or ")
if lower.startswith(query_starters):
return {"name": "", "query": value}
parts = value.split(None, 1)
if len(parts) == 1:
return {"name": "", "query": parts[0]}
return {"name": parts[0], "query": parts[1].strip()}
def _parse_container_conditions(prelude: str) -> list[dict[str, str]]:
value = prelude.replace("@container", "", 1).strip()
if not value:
return []
return [_parse_single_container_condition(item) for item in _split_top_level_commas(value)]
def _serialize_container_conditions(conditions: list[dict[str, str]]) -> str:
parts: list[str] = []
for condition in conditions or []:
name = condition.get("name", "")
query = condition.get("query", "")
parts.append(f"{name} {query}".strip())
return ", ".join(part for part in parts if part)
def _parse_container_prelude(prelude: str) -> tuple[str, str, list[dict[str, str]]]:
conditions = _parse_container_conditions(prelude)
if len(conditions) == 1:
condition = conditions[0]
return condition["name"], condition["query"], conditions
return "", "", conditions
def _strip_wrapping_parens(value: str) -> str:
value = (value or "").strip()
if value.startswith("("):
close_index = _find_matching_paren(value, 0)
if close_index == len(value) - 1:
return value[1:-1].strip()
return value
def _parse_scope_prelude(prelude: str) -> tuple[str | None, str | None]:
value = prelude.replace("@scope", "", 1).strip()
if not value:
return None, None
split = _split_top_level_word(value, "to")
if split:
start, end = split
return _strip_wrapping_parens(start), _strip_wrapping_parens(end)
return _strip_wrapping_parens(value), None
def _consume_css_function(text: str, function_name: str) -> tuple[str, str] | None:
text = (text or "").lstrip()
prefix = f"{function_name}("
if not text.lower().startswith(prefix):
return None
open_index = len(function_name)
close_index = _find_matching_paren(text, open_index)
if close_index == -1:
return None
return text[open_index + 1 : close_index].strip(), text[close_index + 1 :].strip()
def _parse_import_tail(text: str) -> tuple[str | None, str, str]:
layer_name: str | None = None
supports_text = ""
media_text = (text or "").strip()
layer_match = _consume_css_function(media_text, "layer")
if layer_match:
layer_name, media_text = layer_match
elif media_text.lower() == "layer" or media_text.lower().startswith("layer "):
layer_name = ""
media_text = media_text[5:].strip()
supports_match = _consume_css_function(media_text, "supports")
if supports_match:
supports_text, media_text = supports_match
return layer_name, supports_text, media_text.strip()
def _parse_statement_at_rule(
prelude: str,
parentStyleSheet: "CSSStyleSheet | None",
parentRule: CSSRule | None,
) -> CSSRule | None:
# rule is rebound to a different CSSRule subclass per branch.
rule: Any
lower = prelude.lower()
if not lower.startswith("@") and parentRule is not None and _parse_css_declarations(prelude):
rule = CSSNestedDeclarations()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(prelude, rule)
return rule
if lower.startswith("@import"):
rule = CSSImportRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rest = prelude[len("@import") :].strip()
href_match = re.match(r"(url\([^)]+\)|\"[^\"]+\"|'[^']+')\s*(.*)$", rest)
if href_match:
rule.href = href_match.group(1)
layer_name, supports_text, media_text = _parse_import_tail(href_match.group(2).strip())
rule.layerName = layer_name
rule.supportsText = supports_text
rule.media = MediaList([item for item in _split_top_level_commas(media_text) if item])
else:
rule.href = rest
rule.media = MediaList()
return rule
if lower.startswith("@namespace"):
rule = CSSNamespaceRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rest = prelude[len("@namespace") :].strip()
parts = rest.split(None, 1)
if len(parts) == 2:
rule.prefix = parts[0]
rule.namespaceURI = parts[1]
else:
rule.namespaceURI = rest
return rule
if lower.startswith("@layer"):
rule = CSSLayerStatementRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.nameList = [name for name in _split_top_level_commas(prelude[len("@layer") :].strip()) if name]
return rule
rule = CSSRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.type = CSSRule.CHARSET_RULE if lower.startswith("@charset") else CSSRule.UNKNOWN_RULE
rule._cssText = f"{prelude};"
return rule
def _parse_keyframe_rules(
parentStyleSheet: "CSSStyleSheet | None", parentRule: CSSKeyframesRule, block: str
) -> CSSRuleList:
rules = CSSRuleList()
index = 0
while index < len(block):
index = _skip_css_whitespace(block, index)
boundary, kind = _find_next_rule_boundary(block, index)
if boundary == -1 or kind != "{":
break
key_text = block[index:boundary].strip()
close_index = _find_matching_brace(block, boundary)
if close_index == -1:
break
rule = CSSKeyframeRule()
rule.keyText = key_text
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block[boundary + 1 : close_index], rule)
rules.append(rule)
index = close_index + 1
return rules
def _parse_style_rule(
parentStyleSheet: "CSSStyleSheet | None",
parentRule: CSSRule | None,
selectorText: str,
block: str,
) -> CSSStyleRule:
rule = CSSStyleRule()
rule.selectorText = selectorText.strip()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
declarations, nested_rules = _extract_style_block_parts(block)
rule.style = _style_from_declarations(declarations, rule)
for nested_type, nested_prelude, nested_block in nested_rules:
if nested_type == "declarations":
nested = CSSNestedDeclarations()
nested.parentStyleSheet = parentStyleSheet
nested.parentRule = rule
nested.style = _style_from_declarations(nested_block, nested)
if nested.style.length:
rule.cssRules.append(nested)
continue
if not nested_prelude:
continue
rule.cssRules.append(_parse_block_rule(parentStyleSheet, rule, nested_prelude, nested_block))
return rule
def _parse_block_rule(
parentStyleSheet: "CSSStyleSheet | None",
parentRule: CSSRule | None,
prelude: str,
block: str,
) -> CSSRule:
# rule is rebound to a different CSSRule subclass per branch.
rule: Any
lower = prelude.lower()
if lower.startswith("@media"):
rule = CSSMediaRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
for medium in _split_top_level_commas(prelude[len("@media") :].strip()):
if medium:
rule.media.appendMedium(medium)
rule.conditionText = rule.media.mediaText
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@supports-condition"):
rule = CSSSupportsConditionRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.name = prelude[len("@supports-condition") :].strip()
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@supports"):
rule = CSSSupportsRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.conditionText = prelude[len("@supports") :].strip()
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@when"):
rule = CSSWhenRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.conditionText = prelude[len("@when") :].strip()
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@else"):
rule = CSSElseRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.conditionText = prelude[len("@else") :].strip()
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@container"):
rule = CSSContainerRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.containerName, rule.containerQuery, rule.conditions = _parse_container_prelude(prelude)
rule.conditionText = _serialize_container_conditions(rule.conditions)
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@scope"):
rule = CSSScopeRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.start, rule.end = _parse_scope_prelude(prelude)
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@layer"):
rule = CSSLayerBlockRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.name = prelude[len("@layer") :].strip()
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
if lower.startswith("@keyframes") or lower.startswith("@-webkit-keyframes"):
rule = CSSKeyframesRule()
rule.name = prelude.split(None, 1)[1].strip() if len(prelude.split(None, 1)) > 1 else ""
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.cssRules = _parse_keyframe_rules(parentStyleSheet, rule, block)
return rule
if lower.startswith("@font-face"):
rule = CSSFontFaceRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block, rule)
return rule
if lower.startswith("@page"):
rule = CSSPageRule()
rule.selectorText = prelude[len("@page") :].strip()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block, rule)
return rule
if lower.startswith("@property"):
rule = CSSPropertyRule()
rule.name = prelude[len("@property") :].strip()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block, rule)
rule._register()
return rule
if lower.startswith("@counter-style"):
rule = CSSCounterStyleRule()
rule.name = prelude[len("@counter-style") :].strip()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block, rule)
rule.system = rule.style.getPropertyValue("system")
return rule
if lower.startswith("@color-profile"):
rule = CSSColorProfileRule()
rule.colorProfile = prelude[len("@color-profile") :].strip()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.style = _style_from_declarations(block, rule)
return rule
if lower.startswith("@font-feature-values"):
rule = CSSFontFeatureValuesRule()
rule.parentStyleSheet = parentStyleSheet
rule.parentRule = parentRule
rule.cssRules = _parse_rule_list(parentStyleSheet, block, rule)
return rule
return _parse_style_rule(parentStyleSheet, parentRule, prelude, block)
def _parse_rule_list(
parentStyleSheet: "CSSStyleSheet | None",
cssText: str,
parentRule: CSSRule | None = None,
) -> CSSRuleList:
rules = CSSRuleList()
css = cssText or ""
index = 0
while index < len(css):
index = _skip_css_whitespace(css, index)
if index >= len(css):
break
boundary, kind = _find_next_rule_boundary(css, index)
if boundary == -1:
break
prelude = css[index:boundary].strip()
if not prelude:
index = boundary + 1
continue
if kind == ";":
rule = _parse_statement_at_rule(prelude, parentStyleSheet, parentRule)
if rule is not None:
rules.append(rule)
index = boundary + 1
continue
close_index = _find_matching_brace(css, boundary)
if close_index == -1:
break
block = css[boundary + 1 : close_index]
rules.append(_parse_block_rule(parentStyleSheet, parentRule, prelude, block))
index = close_index + 1
return rules
[docs]
class CSSParser:
[docs]
@staticmethod
def parseFromString(parentStyleSheet: CSSStyleSheet, cssText: str):
return _parse_rule_list(parentStyleSheet, _strip_css_comments(cssText))