Skip to content

REF: refactor/cleanup CSSResolver #36581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 102 additions & 77 deletions pandas/io/formats/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

import re
from typing import Optional
from typing import Dict, Optional
import warnings


Expand All @@ -12,8 +12,6 @@ class CSSWarning(UserWarning):
This CSS syntax cannot currently be parsed.
"""

pass


def _side_expander(prop_fmt: str):
def expand(self, prop, value: str):
Expand All @@ -34,7 +32,64 @@ class CSSResolver:
A callable for parsing and resolving CSS to atomic properties.
"""

def __call__(self, declarations_str, inherited=None):
UNIT_RATIOS = {
"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
}
)

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")

def __call__(
self,
declarations_str: str,
inherited: Optional[Dict[str, str]] = None,
) -> Dict[str, str]:
"""
The given declarations to atomic properties.

Expand Down Expand Up @@ -76,100 +131,78 @@ def __call__(self, declarations_str, inherited=None):
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

for prop, val in list(props.items()):
new_props = props.copy()
for prop, val in props.items():
if val == "inherit":
val = inherited.get(prop, "initial")
if val == "initial":
val = None

if val is None:
if val in ("initial", None):
# we do not define a complete initial stylesheet
del props[prop]
del new_props[prop]
else:
props[prop] = val

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
font_size: Optional[float]
if props.get("font-size"):
if "font-size" in inherited:
em_pt = inherited["font-size"]
assert em_pt[-2:] == "pt"
em_pt = float(em_pt[:-2])
else:
em_pt = None
props["font-size"] = self.size_to_pt(
props["font-size"], em_pt, conversions=self.FONT_SIZE_RATIOS
props["font-size"],
self._get_font_size(inherited),
conversions=self.FONT_SIZE_RATIOS,
)
return props

font_size = float(props["font-size"][:-2])
else:
font_size = None
def _get_font_size(self, props: Dict[str, str]) -> Optional[float]:
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
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
props[prop],
em_pt=font_size,
conversions=self.MARGIN_RATIOS,
)

return props

UNIT_RATIOS = {
"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
}
)

def size_to_pt(self, in_val, em_pt=None, conversions=UNIT_RATIOS):
def _error():
warnings.warn(f"Unhandled size: {repr(in_val)}", CSSWarning)
Expand Down Expand Up @@ -222,14 +255,6 @@ def atomize(self, declarations):
for prop, value in expand(prop, value):
yield prop, value

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")

expand_border_color = _side_expander("border-{:s}-color")
expand_border_style = _side_expander("border-{:s}-style")
expand_border_width = _side_expander("border-{:s}-width")
Expand Down
7 changes: 4 additions & 3 deletions pandas/io/formats/excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ class CSSToExcelConverter:
# and __call__ make use of instance attributes. We leave them as
# instancemethods so that users can easily experiment with extensions
# without monkey-patching.
inherited: Optional[Dict[str, str]]

def __init__(self, inherited: Optional[str] = None):
if inherited is not None:
inherited = self.compute_css(inherited)

self.inherited = inherited
self.inherited = self.compute_css(inherited)
else:
self.inherited = None

compute_css = CSSResolver()

Expand Down