Skip to content

Commit d65417c

Browse files
authored
REF: refactor/cleanup CSSResolver (#36581)
1 parent dee2c55 commit d65417c

File tree

2 files changed

+106
-80
lines changed

2 files changed

+106
-80
lines changed

pandas/io/formats/css.py

+102-77
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44

55
import re
6-
from typing import Optional
6+
from typing import Dict, Optional
77
import warnings
88

99

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

15-
pass
16-
1715

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

37-
def __call__(self, declarations_str, inherited=None):
35+
UNIT_RATIOS = {
36+
"rem": ("pt", 12),
37+
"ex": ("em", 0.5),
38+
# 'ch':
39+
"px": ("pt", 0.75),
40+
"pc": ("pt", 12),
41+
"in": ("pt", 72),
42+
"cm": ("in", 1 / 2.54),
43+
"mm": ("in", 1 / 25.4),
44+
"q": ("mm", 0.25),
45+
"!!default": ("em", 0),
46+
}
47+
48+
FONT_SIZE_RATIOS = UNIT_RATIOS.copy()
49+
FONT_SIZE_RATIOS.update(
50+
{
51+
"%": ("em", 0.01),
52+
"xx-small": ("rem", 0.5),
53+
"x-small": ("rem", 0.625),
54+
"small": ("rem", 0.8),
55+
"medium": ("rem", 1),
56+
"large": ("rem", 1.125),
57+
"x-large": ("rem", 1.5),
58+
"xx-large": ("rem", 2),
59+
"smaller": ("em", 1 / 1.2),
60+
"larger": ("em", 1.2),
61+
"!!default": ("em", 1),
62+
}
63+
)
64+
65+
MARGIN_RATIOS = UNIT_RATIOS.copy()
66+
MARGIN_RATIOS.update({"none": ("pt", 0)})
67+
68+
BORDER_WIDTH_RATIOS = UNIT_RATIOS.copy()
69+
BORDER_WIDTH_RATIOS.update(
70+
{
71+
"none": ("pt", 0),
72+
"thick": ("px", 4),
73+
"medium": ("px", 2),
74+
"thin": ("px", 1),
75+
# Default: medium only if solid
76+
}
77+
)
78+
79+
SIDE_SHORTHANDS = {
80+
1: [0, 0, 0, 0],
81+
2: [0, 1, 0, 1],
82+
3: [0, 1, 2, 1],
83+
4: [0, 1, 2, 3],
84+
}
85+
86+
SIDES = ("top", "right", "bottom", "left")
87+
88+
def __call__(
89+
self,
90+
declarations_str: str,
91+
inherited: Optional[Dict[str, str]] = None,
92+
) -> Dict[str, str]:
3893
"""
3994
The given declarations to atomic properties.
4095
@@ -76,100 +131,78 @@ def __call__(self, declarations_str, inherited=None):
76131
if inherited is None:
77132
inherited = {}
78133

134+
props = self._update_initial(props, inherited)
135+
props = self._update_font_size(props, inherited)
136+
return self._update_other_units(props)
137+
138+
def _update_initial(
139+
self,
140+
props: Dict[str, str],
141+
inherited: Dict[str, str],
142+
) -> Dict[str, str]:
79143
# 1. resolve inherited, initial
80144
for prop, val in inherited.items():
81145
if prop not in props:
82146
props[prop] = val
83147

84-
for prop, val in list(props.items()):
148+
new_props = props.copy()
149+
for prop, val in props.items():
85150
if val == "inherit":
86151
val = inherited.get(prop, "initial")
87-
if val == "initial":
88-
val = None
89152

90-
if val is None:
153+
if val in ("initial", None):
91154
# we do not define a complete initial stylesheet
92-
del props[prop]
155+
del new_props[prop]
93156
else:
94-
props[prop] = val
95-
157+
new_props[prop] = val
158+
return new_props
159+
160+
def _update_font_size(
161+
self,
162+
props: Dict[str, str],
163+
inherited: Dict[str, str],
164+
) -> Dict[str, str]:
96165
# 2. resolve relative font size
97-
font_size: Optional[float]
98166
if props.get("font-size"):
99-
if "font-size" in inherited:
100-
em_pt = inherited["font-size"]
101-
assert em_pt[-2:] == "pt"
102-
em_pt = float(em_pt[:-2])
103-
else:
104-
em_pt = None
105167
props["font-size"] = self.size_to_pt(
106-
props["font-size"], em_pt, conversions=self.FONT_SIZE_RATIOS
168+
props["font-size"],
169+
self._get_font_size(inherited),
170+
conversions=self.FONT_SIZE_RATIOS,
107171
)
172+
return props
108173

109-
font_size = float(props["font-size"][:-2])
110-
else:
111-
font_size = None
174+
def _get_font_size(self, props: Dict[str, str]) -> Optional[float]:
175+
if props.get("font-size"):
176+
font_size_string = props["font-size"]
177+
return self._get_float_font_size_from_pt(font_size_string)
178+
return None
179+
180+
def _get_float_font_size_from_pt(self, font_size_string: str) -> float:
181+
assert font_size_string.endswith("pt")
182+
return float(font_size_string.rstrip("pt"))
112183

184+
def _update_other_units(self, props: Dict[str, str]) -> Dict[str, str]:
185+
font_size = self._get_font_size(props)
113186
# 3. TODO: resolve other font-relative units
114187
for side in self.SIDES:
115188
prop = f"border-{side}-width"
116189
if prop in props:
117190
props[prop] = self.size_to_pt(
118-
props[prop], em_pt=font_size, conversions=self.BORDER_WIDTH_RATIOS
191+
props[prop],
192+
em_pt=font_size,
193+
conversions=self.BORDER_WIDTH_RATIOS,
119194
)
195+
120196
for prop in [f"margin-{side}", f"padding-{side}"]:
121197
if prop in props:
122198
# TODO: support %
123199
props[prop] = self.size_to_pt(
124-
props[prop], em_pt=font_size, conversions=self.MARGIN_RATIOS
200+
props[prop],
201+
em_pt=font_size,
202+
conversions=self.MARGIN_RATIOS,
125203
)
126-
127204
return props
128205

129-
UNIT_RATIOS = {
130-
"rem": ("pt", 12),
131-
"ex": ("em", 0.5),
132-
# 'ch':
133-
"px": ("pt", 0.75),
134-
"pc": ("pt", 12),
135-
"in": ("pt", 72),
136-
"cm": ("in", 1 / 2.54),
137-
"mm": ("in", 1 / 25.4),
138-
"q": ("mm", 0.25),
139-
"!!default": ("em", 0),
140-
}
141-
142-
FONT_SIZE_RATIOS = UNIT_RATIOS.copy()
143-
FONT_SIZE_RATIOS.update(
144-
{
145-
"%": ("em", 0.01),
146-
"xx-small": ("rem", 0.5),
147-
"x-small": ("rem", 0.625),
148-
"small": ("rem", 0.8),
149-
"medium": ("rem", 1),
150-
"large": ("rem", 1.125),
151-
"x-large": ("rem", 1.5),
152-
"xx-large": ("rem", 2),
153-
"smaller": ("em", 1 / 1.2),
154-
"larger": ("em", 1.2),
155-
"!!default": ("em", 1),
156-
}
157-
)
158-
159-
MARGIN_RATIOS = UNIT_RATIOS.copy()
160-
MARGIN_RATIOS.update({"none": ("pt", 0)})
161-
162-
BORDER_WIDTH_RATIOS = UNIT_RATIOS.copy()
163-
BORDER_WIDTH_RATIOS.update(
164-
{
165-
"none": ("pt", 0),
166-
"thick": ("px", 4),
167-
"medium": ("px", 2),
168-
"thin": ("px", 1),
169-
# Default: medium only if solid
170-
}
171-
)
172-
173206
def size_to_pt(self, in_val, em_pt=None, conversions=UNIT_RATIOS):
174207
def _error():
175208
warnings.warn(f"Unhandled size: {repr(in_val)}", CSSWarning)
@@ -222,14 +255,6 @@ def atomize(self, declarations):
222255
for prop, value in expand(prop, value):
223256
yield prop, value
224257

225-
SIDE_SHORTHANDS = {
226-
1: [0, 0, 0, 0],
227-
2: [0, 1, 0, 1],
228-
3: [0, 1, 2, 1],
229-
4: [0, 1, 2, 3],
230-
}
231-
SIDES = ("top", "right", "bottom", "left")
232-
233258
expand_border_color = _side_expander("border-{:s}-color")
234259
expand_border_style = _side_expander("border-{:s}-style")
235260
expand_border_width = _side_expander("border-{:s}-width")

pandas/io/formats/excel.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,13 @@ class CSSToExcelConverter:
6262
# and __call__ make use of instance attributes. We leave them as
6363
# instancemethods so that users can easily experiment with extensions
6464
# without monkey-patching.
65+
inherited: Optional[Dict[str, str]]
6566

6667
def __init__(self, inherited: Optional[str] = None):
6768
if inherited is not None:
68-
inherited = self.compute_css(inherited)
69-
70-
self.inherited = inherited
69+
self.inherited = self.compute_css(inherited)
70+
else:
71+
self.inherited = None
7172

7273
compute_css = CSSResolver()
7374

0 commit comments

Comments
 (0)