Adding all project files
This commit is contained in:
parent
6c9e127bdc
commit
cd4316ad0f
42289 changed files with 8009643 additions and 0 deletions
421
venv/Lib/site-packages/pandas/io/formats/css.py
Normal file
421
venv/Lib/site-packages/pandas/io/formats/css.py
Normal file
|
@ -0,0 +1,421 @@
|
|||
"""
|
||||
Utilities for interpreting CSS from Stylers for formatting non-HTML outputs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
)
|
||||
import warnings
|
||||
|
||||
from pandas.errors import CSSWarning
|
||||
from pandas.util._exceptions import find_stack_level
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import (
|
||||
Generator,
|
||||
Iterable,
|
||||
Iterator,
|
||||
)
|
||||
|
||||
|
||||
def _side_expander(prop_fmt: str) -> Callable:
|
||||
"""
|
||||
Wrapper to expand shorthand property into top, right, bottom, left properties
|
||||
|
||||
Parameters
|
||||
----------
|
||||
side : str
|
||||
The border side to expand into properties
|
||||
|
||||
Returns
|
||||
-------
|
||||
function: Return to call when a 'border(-{side}): {value}' string is encountered
|
||||
"""
|
||||
|
||||
def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]:
|
||||
"""
|
||||
Expand shorthand property into side-specific property (top, right, bottom, left)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
prop (str): CSS property name
|
||||
value (str): String token for property
|
||||
|
||||
Yields
|
||||
------
|
||||
Tuple (str, str): Expanded property, value
|
||||
"""
|
||||
tokens = value.split()
|
||||
try:
|
||||
mapping = self.SIDE_SHORTHANDS[len(tokens)]
|
||||
except KeyError:
|
||||
warnings.warn(
|
||||
f'Could not expand "{prop}: {value}"',
|
||||
CSSWarning,
|
||||
stacklevel=find_stack_level(),
|
||||
)
|
||||
return
|
||||
for key, idx in zip(self.SIDES, mapping):
|
||||
yield prop_fmt.format(key), tokens[idx]
|
||||
|
||||
return expand
|
||||
|
||||
|
||||
def _border_expander(side: str = "") -> Callable:
|
||||
"""
|
||||
Wrapper to expand 'border' property into border color, style, and width properties
|
||||
|
||||
Parameters
|
||||
----------
|
||||
side : str
|
||||
The border side to expand into properties
|
||||
|
||||
Returns
|
||||
-------
|
||||
function: Return to call when a 'border(-{side}): {value}' string is encountered
|
||||
"""
|
||||
if side != "":
|
||||
side = f"-{side}"
|
||||
|
||||
def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]:
|
||||
"""
|
||||
Expand border into color, style, and width tuples
|
||||
|
||||
Parameters
|
||||
----------
|
||||
prop : str
|
||||
CSS property name passed to styler
|
||||
value : str
|
||||
Value passed to styler for property
|
||||
|
||||
Yields
|
||||
------
|
||||
Tuple (str, str): Expanded property, value
|
||||
"""
|
||||
tokens = value.split()
|
||||
if len(tokens) == 0 or len(tokens) > 3:
|
||||
warnings.warn(
|
||||
f'Too many tokens provided to "{prop}" (expected 1-3)',
|
||||
CSSWarning,
|
||||
stacklevel=find_stack_level(),
|
||||
)
|
||||
|
||||
# TODO: Can we use current color as initial value to comply with CSS standards?
|
||||
border_declarations = {
|
||||
f"border{side}-color": "black",
|
||||
f"border{side}-style": "none",
|
||||
f"border{side}-width": "medium",
|
||||
}
|
||||
for token in tokens:
|
||||
if token.lower() in self.BORDER_STYLES:
|
||||
border_declarations[f"border{side}-style"] = token
|
||||
elif any(ratio in token.lower() for ratio in self.BORDER_WIDTH_RATIOS):
|
||||
border_declarations[f"border{side}-width"] = token
|
||||
else:
|
||||
border_declarations[f"border{side}-color"] = token
|
||||
# TODO: Warn user if item entered more than once (e.g. "border: red green")
|
||||
|
||||
# Per CSS, "border" will reset previous "border-*" definitions
|
||||
yield from self.atomize(border_declarations.items())
|
||||
|
||||
return expand
|
||||
|
||||
|
||||
class CSSResolver:
|
||||
"""
|
||||
A callable for parsing and resolving CSS to atomic properties.
|
||||
"""
|
||||
|
||||
UNIT_RATIOS = {
|
||||
"pt": ("pt", 1),
|
||||
"em": ("em", 1),
|
||||
"rem": ("pt", 12),
|
||||
"ex": ("em", 0.5),
|
||||
# 'ch':
|
||||
"px": ("pt", 0.75),
|
||||
"pc": ("pt", 12),
|
||||
"in": ("pt", 72),
|
||||
"cm": ("in", 1 / 2.54),
|
||||
"mm": ("in", 1 / 25.4),
|
||||
"q": ("mm", 0.25),
|
||||
"!!default": ("em", 0),
|
||||
}
|
||||
|
||||
FONT_SIZE_RATIOS = UNIT_RATIOS.copy()
|
||||
FONT_SIZE_RATIOS.update(
|
||||
{
|
||||
"%": ("em", 0.01),
|
||||
"xx-small": ("rem", 0.5),
|
||||
"x-small": ("rem", 0.625),
|
||||
"small": ("rem", 0.8),
|
||||
"medium": ("rem", 1),
|
||||
"large": ("rem", 1.125),
|
||||
"x-large": ("rem", 1.5),
|
||||
"xx-large": ("rem", 2),
|
||||
"smaller": ("em", 1 / 1.2),
|
||||
"larger": ("em", 1.2),
|
||||
"!!default": ("em", 1),
|
||||
}
|
||||
)
|
||||
|
||||
MARGIN_RATIOS = UNIT_RATIOS.copy()
|
||||
MARGIN_RATIOS.update({"none": ("pt", 0)})
|
||||
|
||||
BORDER_WIDTH_RATIOS = UNIT_RATIOS.copy()
|
||||
BORDER_WIDTH_RATIOS.update(
|
||||
{
|
||||
"none": ("pt", 0),
|
||||
"thick": ("px", 4),
|
||||
"medium": ("px", 2),
|
||||
"thin": ("px", 1),
|
||||
# Default: medium only if solid
|
||||
}
|
||||
)
|
||||
|
||||
BORDER_STYLES = [
|
||||
"none",
|
||||
"hidden",
|
||||
"dotted",
|
||||
"dashed",
|
||||
"solid",
|
||||
"double",
|
||||
"groove",
|
||||
"ridge",
|
||||
"inset",
|
||||
"outset",
|
||||
"mediumdashdot",
|
||||
"dashdotdot",
|
||||
"hair",
|
||||
"mediumdashdotdot",
|
||||
"dashdot",
|
||||
"slantdashdot",
|
||||
"mediumdashed",
|
||||
]
|
||||
|
||||
SIDE_SHORTHANDS = {
|
||||
1: [0, 0, 0, 0],
|
||||
2: [0, 1, 0, 1],
|
||||
3: [0, 1, 2, 1],
|
||||
4: [0, 1, 2, 3],
|
||||
}
|
||||
|
||||
SIDES = ("top", "right", "bottom", "left")
|
||||
|
||||
CSS_EXPANSIONS = {
|
||||
**{
|
||||
(f"border-{prop}" if prop else "border"): _border_expander(prop)
|
||||
for prop in ["", "top", "right", "bottom", "left"]
|
||||
},
|
||||
**{
|
||||
f"border-{prop}": _side_expander(f"border-{{:s}}-{prop}")
|
||||
for prop in ["color", "style", "width"]
|
||||
},
|
||||
"margin": _side_expander("margin-{:s}"),
|
||||
"padding": _side_expander("padding-{:s}"),
|
||||
}
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
declarations: str | Iterable[tuple[str, str]],
|
||||
inherited: dict[str, str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""
|
||||
The given declarations to atomic properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
declarations_str : str | Iterable[tuple[str, str]]
|
||||
A CSS string or set of CSS declaration tuples
|
||||
e.g. "font-weight: bold; background: blue" or
|
||||
{("font-weight", "bold"), ("background", "blue")}
|
||||
inherited : dict, optional
|
||||
Atomic properties indicating the inherited style context in which
|
||||
declarations_str is to be resolved. ``inherited`` should already
|
||||
be resolved, i.e. valid output of this method.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Atomic CSS 2.2 properties.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> resolve = CSSResolver()
|
||||
>>> inherited = {'font-family': 'serif', 'font-weight': 'bold'}
|
||||
>>> out = resolve('''
|
||||
... border-color: BLUE RED;
|
||||
... font-size: 1em;
|
||||
... font-size: 2em;
|
||||
... font-weight: normal;
|
||||
... font-weight: inherit;
|
||||
... ''', inherited)
|
||||
>>> sorted(out.items()) # doctest: +NORMALIZE_WHITESPACE
|
||||
[('border-bottom-color', 'blue'),
|
||||
('border-left-color', 'red'),
|
||||
('border-right-color', 'red'),
|
||||
('border-top-color', 'blue'),
|
||||
('font-family', 'serif'),
|
||||
('font-size', '24pt'),
|
||||
('font-weight', 'bold')]
|
||||
"""
|
||||
if isinstance(declarations, str):
|
||||
declarations = self.parse(declarations)
|
||||
props = dict(self.atomize(declarations))
|
||||
if inherited is None:
|
||||
inherited = {}
|
||||
|
||||
props = self._update_initial(props, inherited)
|
||||
props = self._update_font_size(props, inherited)
|
||||
return self._update_other_units(props)
|
||||
|
||||
def _update_initial(
|
||||
self,
|
||||
props: dict[str, str],
|
||||
inherited: dict[str, str],
|
||||
) -> dict[str, str]:
|
||||
# 1. resolve inherited, initial
|
||||
for prop, val in inherited.items():
|
||||
if prop not in props:
|
||||
props[prop] = val
|
||||
|
||||
new_props = props.copy()
|
||||
for prop, val in props.items():
|
||||
if val == "inherit":
|
||||
val = inherited.get(prop, "initial")
|
||||
|
||||
if val in ("initial", None):
|
||||
# we do not define a complete initial stylesheet
|
||||
del new_props[prop]
|
||||
else:
|
||||
new_props[prop] = val
|
||||
return new_props
|
||||
|
||||
def _update_font_size(
|
||||
self,
|
||||
props: dict[str, str],
|
||||
inherited: dict[str, str],
|
||||
) -> dict[str, str]:
|
||||
# 2. resolve relative font size
|
||||
if props.get("font-size"):
|
||||
props["font-size"] = self.size_to_pt(
|
||||
props["font-size"],
|
||||
self._get_font_size(inherited),
|
||||
conversions=self.FONT_SIZE_RATIOS,
|
||||
)
|
||||
return props
|
||||
|
||||
def _get_font_size(self, props: dict[str, str]) -> float | None:
|
||||
if props.get("font-size"):
|
||||
font_size_string = props["font-size"]
|
||||
return self._get_float_font_size_from_pt(font_size_string)
|
||||
return None
|
||||
|
||||
def _get_float_font_size_from_pt(self, font_size_string: str) -> float:
|
||||
assert font_size_string.endswith("pt")
|
||||
return float(font_size_string.rstrip("pt"))
|
||||
|
||||
def _update_other_units(self, props: dict[str, str]) -> dict[str, str]:
|
||||
font_size = self._get_font_size(props)
|
||||
# 3. TODO: resolve other font-relative units
|
||||
for side in self.SIDES:
|
||||
prop = f"border-{side}-width"
|
||||
if prop in props:
|
||||
props[prop] = self.size_to_pt(
|
||||
props[prop],
|
||||
em_pt=font_size,
|
||||
conversions=self.BORDER_WIDTH_RATIOS,
|
||||
)
|
||||
|
||||
for prop in [f"margin-{side}", f"padding-{side}"]:
|
||||
if prop in props:
|
||||
# TODO: support %
|
||||
props[prop] = self.size_to_pt(
|
||||
props[prop],
|
||||
em_pt=font_size,
|
||||
conversions=self.MARGIN_RATIOS,
|
||||
)
|
||||
return props
|
||||
|
||||
def size_to_pt(self, in_val, em_pt=None, conversions=UNIT_RATIOS) -> str:
|
||||
def _error():
|
||||
warnings.warn(
|
||||
f"Unhandled size: {repr(in_val)}",
|
||||
CSSWarning,
|
||||
stacklevel=find_stack_level(),
|
||||
)
|
||||
return self.size_to_pt("1!!default", conversions=conversions)
|
||||
|
||||
match = re.match(r"^(\S*?)([a-zA-Z%!].*)", in_val)
|
||||
if match is None:
|
||||
return _error()
|
||||
|
||||
val, unit = match.groups()
|
||||
if val == "":
|
||||
# hack for 'large' etc.
|
||||
val = 1
|
||||
else:
|
||||
try:
|
||||
val = float(val)
|
||||
except ValueError:
|
||||
return _error()
|
||||
|
||||
while unit != "pt":
|
||||
if unit == "em":
|
||||
if em_pt is None:
|
||||
unit = "rem"
|
||||
else:
|
||||
val *= em_pt
|
||||
unit = "pt"
|
||||
continue
|
||||
|
||||
try:
|
||||
unit, mul = conversions[unit]
|
||||
except KeyError:
|
||||
return _error()
|
||||
val *= mul
|
||||
|
||||
val = round(val, 5)
|
||||
if int(val) == val:
|
||||
size_fmt = f"{int(val):d}pt"
|
||||
else:
|
||||
size_fmt = f"{val:f}pt"
|
||||
return size_fmt
|
||||
|
||||
def atomize(self, declarations: Iterable) -> Generator[tuple[str, str], None, None]:
|
||||
for prop, value in declarations:
|
||||
prop = prop.lower()
|
||||
value = value.lower()
|
||||
if prop in self.CSS_EXPANSIONS:
|
||||
expand = self.CSS_EXPANSIONS[prop]
|
||||
yield from expand(self, prop, value)
|
||||
else:
|
||||
yield prop, value
|
||||
|
||||
def parse(self, declarations_str: str) -> Iterator[tuple[str, str]]:
|
||||
"""
|
||||
Generates (prop, value) pairs from declarations.
|
||||
|
||||
In a future version may generate parsed tokens from tinycss/tinycss2
|
||||
|
||||
Parameters
|
||||
----------
|
||||
declarations_str : str
|
||||
"""
|
||||
for decl in declarations_str.split(";"):
|
||||
if not decl.strip():
|
||||
continue
|
||||
prop, sep, val = decl.partition(":")
|
||||
prop = prop.strip().lower()
|
||||
# TODO: don't lowercase case sensitive parts of values (strings)
|
||||
val = val.strip().lower()
|
||||
if sep:
|
||||
yield prop, val
|
||||
else:
|
||||
warnings.warn(
|
||||
f"Ill-formatted attribute: expected a colon in {repr(decl)}",
|
||||
CSSWarning,
|
||||
stacklevel=find_stack_level(),
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue