From a9f7d9b38789c0a855beb977ac6488c44e1bc6c1 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 00:20:56 +0700 Subject: [PATCH 1/7] REF: extract methods in CSSResolver --- pandas/io/formats/css.py | 43 +++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 2e9ee192a1182..9347a3de82cee 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -76,6 +76,11 @@ 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, inherited): # 1. resolve inherited, initial for prop, val in inherited.items(): if prop not in props: @@ -92,38 +97,48 @@ def __call__(self, declarations_str, inherited=None): del props[prop] else: props[prop] = val + return props + def _update_font_size(self, props, inherited): # 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) -> 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): + assert font_size_string.endswith("pt") + return float(font_size_string.rstrip("pt")) + def _update_other_units(self, props): + 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 = { From d853068894f93f3118c209f29848d746624c3e56 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 00:35:22 +0700 Subject: [PATCH 2/7] REF: simplify logic in _update_initial --- pandas/io/formats/css.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 9347a3de82cee..4638705fbbbdf 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -3,7 +3,7 @@ """ import re -from typing import Optional +from typing import Dict, Mapping, Optional import warnings @@ -34,7 +34,11 @@ class CSSResolver: A callable for parsing and resolving CSS to atomic properties. """ - def __call__(self, declarations_str, inherited=None): + def __call__( + self, + declarations_str: str, + inherited: Optional[Mapping[str, str]] = None, + ): """ The given declarations to atomic properties. @@ -80,7 +84,11 @@ def __call__(self, declarations_str, inherited=None): props = self._update_font_size(props, inherited) return self._update_other_units(props) - def _update_initial(self, props, inherited): + def _update_initial( + self, + props: Dict[str, Optional[str]], + inherited: Mapping[str, str], + ) -> Dict[str, Optional[str]]: # 1. resolve inherited, initial for prop, val in inherited.items(): if prop not in props: @@ -89,17 +97,19 @@ def _update_initial(self, props, inherited): for prop, val in list(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] else: props[prop] = val return props - def _update_font_size(self, props, inherited): + def _update_font_size( + self, + props: Dict[str, Optional[str]], + inherited: Mapping[str, str], + ) -> Dict[str, Optional[str]]: # 2. resolve relative font size if props.get("font-size"): props["font-size"] = self.size_to_pt( From 9aa71ffb37a57689f5e3d45acb5430285f2b4dcb Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 01:01:11 +0700 Subject: [PATCH 3/7] TYP: fix typing between css.py and excel.py --- pandas/io/formats/css.py | 27 ++++++++++++++------------- pandas/io/formats/excel.py | 7 ++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 4638705fbbbdf..5a965998cbde2 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -3,7 +3,7 @@ """ import re -from typing import Dict, Mapping, Optional +from typing import Dict, Optional import warnings @@ -37,8 +37,8 @@ class CSSResolver: def __call__( self, declarations_str: str, - inherited: Optional[Mapping[str, str]] = None, - ): + inherited: Optional[Dict[str, str]] = None, + ) -> Dict[str, str]: """ The given declarations to atomic properties. @@ -79,6 +79,7 @@ def __call__( props = dict(self.atomize(self.parse(declarations_str))) if inherited is None: inherited = {} + assert inherited is not None props = self._update_initial(props, inherited) props = self._update_font_size(props, inherited) @@ -86,15 +87,15 @@ def __call__( def _update_initial( self, - props: Dict[str, Optional[str]], - inherited: Mapping[str, str], - ) -> Dict[str, Optional[str]]: + 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()): + for prop, val in props.copy().items(): if val == "inherit": val = inherited.get(prop, "initial") @@ -107,9 +108,9 @@ def _update_initial( def _update_font_size( self, - props: Dict[str, Optional[str]], - inherited: Mapping[str, str], - ) -> Dict[str, Optional[str]]: + 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( @@ -119,17 +120,17 @@ def _update_font_size( ) return props - def _get_font_size(self, props) -> Optional[float]: + 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): + 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): + 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: diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 0140804e8c7b5..472ecc8ae17ab 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -64,10 +64,11 @@ class CSSToExcelConverter: # without monkey-patching. def __init__(self, inherited: Optional[str] = None): + self.inherited: Optional[Dict[str, str]] 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() From 101513529222d1779950123b64da2a02b919e6c5 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 01:03:41 +0700 Subject: [PATCH 4/7] CLN: remove pass in CSSWarning --- pandas/io/formats/css.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 5a965998cbde2..930131d11afe8 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -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): From 6223022f941d5d47575db56b81759f79612bfe82 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 01:05:49 +0700 Subject: [PATCH 5/7] CLN: move class attributes on top --- pandas/io/formats/css.py | 105 ++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 930131d11afe8..78af9c010f51e 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -32,6 +32,59 @@ class CSSResolver: A callable for parsing and resolving CSS to atomic properties. """ + 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, @@ -150,50 +203,6 @@ def _update_other_units(self, props: Dict[str, str]) -> Dict[str, str]: ) 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) @@ -246,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") From 1b783a6e1eb00c5de2ec769787ec4d55471cce08 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 22:29:45 +0700 Subject: [PATCH 6/7] CLN: remove assert, move typing into class level --- pandas/io/formats/css.py | 1 - pandas/io/formats/excel.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 78af9c010f51e..7d24efc8f3f5d 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -130,7 +130,6 @@ def __call__( props = dict(self.atomize(self.parse(declarations_str))) if inherited is None: inherited = {} - assert inherited is not None props = self._update_initial(props, inherited) props = self._update_font_size(props, inherited) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 472ecc8ae17ab..2fccb4f3e9258 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -62,9 +62,9 @@ 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): - self.inherited: Optional[Dict[str, str]] if inherited is not None: self.inherited = self.compute_css(inherited) else: From 5b70d3ef954e5e25757afefe5515b7e9ca5355b5 Mon Sep 17 00:00:00 2001 From: Maxim Ivanov Date: Thu, 24 Sep 2020 22:39:09 +0700 Subject: [PATCH 7/7] CLN: iterate over original dict, update copy --- pandas/io/formats/css.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 7d24efc8f3f5d..8abe13db370ca 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -145,16 +145,17 @@ def _update_initial( if prop not in props: props[prop] = val - for prop, val in props.copy().items(): + 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 props[prop] + del new_props[prop] else: - props[prop] = val - return props + new_props[prop] = val + return new_props def _update_font_size( self,