From 954304514806490ea62cf5a708ca1662becc103e Mon Sep 17 00:00:00 2001 From: tehunter Date: Sun, 9 Jan 2022 14:50:04 -0500 Subject: [PATCH 01/20] BUG/ENH: Improve border CSS parsing (GH42276) --- pandas/io/formats/css.py | 37 ++++++++++ pandas/tests/io/formats/test_css.py | 104 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 956951a6f2f3d..1d81db6619f71 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -26,6 +26,35 @@ def expand(self, prop, value: str): return expand +def _border_expander(side: str = ""): + if (side != ""): + side = f"-{side}" + def expand(self, prop, value:str): + tokens = value.split() + if (len(tokens) == 0 or len(tokens) > 3): + raise ValueError("Too many tokens provided to border (expected 1-3)") + + # 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: + token_key = "color" + if token in self.BORDER_STYLES: + token_key = "style" + + for ratio in self.BORDER_WIDTH_RATIOS: + if ratio in token: + token_key = "width" + break + + # TODO: Warn user if item entered more than once (e.g. "border: red green") + border_declarations[f"border{side}-{token_key}"] = token + + # Per CSS, "border" will reset previous "border-*" definitions + yield from self.atomize(border_declarations.items()) + + return expand + + class CSSResolver: """ @@ -76,6 +105,8 @@ class CSSResolver: } ) + BORDER_STYLES = ["none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"] + SIDE_SHORTHANDS = { 1: [0, 0, 0, 0], 2: [0, 1, 0, 1], @@ -255,6 +286,12 @@ def atomize(self, declarations): for prop, value in expand(prop, value): yield prop, value + expand_border = _border_expander() + expand_border_top = _border_expander("top") + expand_border_right = _border_expander("right") + expand_border_bottom = _border_expander("bottom") + expand_border_left = _border_expander("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") diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index 8465d116805c7..ca8f5f36b1a11 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -122,6 +122,110 @@ def test_css_side_shorthands(shorthand, expansions): with tm.assert_produces_warning(CSSWarning): assert_resolves(f"{shorthand}: 1pt 1pt 1pt 1pt 1pt", {}) +@pytest.mark.parametrize( + "shorthand,expansions", + [ + ( + "border-top", + [[ + "border-top-color", + "border-top-style", + "border-top-width" + ]], + ), + ( + "border-right", + [[ + "border-right-color", + "border-right-style", + "border-right-width" + ]], + ), + ( + "border-bottom", + [[ + "border-bottom-color", + "border-bottom-style", + "border-bottom-width" + ]], + ), + ( + "border-left", + [[ + "border-left-color", + "border-left-style", + "border-left-width" + ]], + ), + ( + "border", + [ + [ + "border-top-color", + "border-top-style", + "border-top-width" + ], + [ + "border-right-color", + "border-right-style", + "border-right-width" + ], + [ + "border-bottom-color", + "border-bottom-style", + "border-bottom-width" + ], + [ + "border-left-color", + "border-left-style", + "border-left-width" + ], + ] + ), + ] +) +def test_css_border_shorthands(shorthand, expansions): + for side_expansions in expansions: + color, style, width = side_expansions + + assert_resolves( + f"{shorthand}: 1pt red solid", + {color: "red", style: "solid", width: "1pt"} + ) + + assert_resolves( + f"{shorthand}: red solid", + {color: "red", style: "solid", width: "1.500000pt"} + ) + + # Do not test color=black because it's not CSS conforming + assert_resolves( + f"{shorthand}: 1pt solid", + {style: "solid", width: "1pt"} + ) + + assert_resolves( + f"{shorthand}: 1pt red", + {color: "red", style: "none", width: "1pt"} + ) + + assert_resolves( + f"{shorthand}: red", + {color: "red", style: "none", width: "1.500000pt"} + ) + + # Do not test color=black because it's not CSS conforming + assert_resolves( + f"{shorthand}: 1pt", + {style: "none", width: "1pt"} + ) + + # Do not test color=black because it's not CSS conforming + assert_resolves( + f"{shorthand}: solid", + {style: "solid", width: "1.500000pt"} + ) + @pytest.mark.parametrize( "style,inherited,equiv", From d07189344136900197e06cb6912ed00b2381e6a8 Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 10 Jan 2022 11:52:27 -0500 Subject: [PATCH 02/20] Fix issue where width 'pt' is not recognized and fix border shorthand tests --- pandas/io/formats/css.py | 1 + pandas/tests/io/formats/test_css.py | 154 +++++++++++++--------------- 2 files changed, 71 insertions(+), 84 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 1d81db6619f71..adaae1b90b059 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -62,6 +62,7 @@ class CSSResolver: """ UNIT_RATIOS = { + "pt": ("pt", 1), "rem": ("pt", 12), "ex": ("em", 0.5), # 'ch': diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index ca8f5f36b1a11..60f5527204de1 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -123,108 +123,94 @@ def test_css_side_shorthands(shorthand, expansions): assert_resolves(f"{shorthand}: 1pt 1pt 1pt 1pt 1pt", {}) @pytest.mark.parametrize( - "shorthand,expansions", + "shorthand,sides", [ ( "border-top", - [[ - "border-top-color", - "border-top-style", - "border-top-width" - ]], + ["top"] ), ( "border-right", - [[ - "border-right-color", - "border-right-style", - "border-right-width" - ]], + ["right"] ), ( "border-bottom", - [[ - "border-bottom-color", - "border-bottom-style", - "border-bottom-width" - ]], + ["bottom"], ), ( "border-left", - [[ - "border-left-color", - "border-left-style", - "border-left-width" - ]], + ["left"] ), ( "border", - [ - [ - "border-top-color", - "border-top-style", - "border-top-width" - ], - [ - "border-right-color", - "border-right-style", - "border-right-width" - ], - [ - "border-bottom-color", - "border-bottom-style", - "border-bottom-width" - ], - [ - "border-left-color", - "border-left-style", - "border-left-width" - ], - ] + ["top", "right", "bottom", "left"] ), ] ) -def test_css_border_shorthands(shorthand, expansions): - for side_expansions in expansions: - color, style, width = side_expansions - - assert_resolves( - f"{shorthand}: 1pt red solid", - {color: "red", style: "solid", width: "1pt"} - ) - - assert_resolves( - f"{shorthand}: red solid", - {color: "red", style: "solid", width: "1.500000pt"} - ) - - # Do not test color=black because it's not CSS conforming - assert_resolves( - f"{shorthand}: 1pt solid", - {style: "solid", width: "1pt"} - ) - - assert_resolves( - f"{shorthand}: 1pt red", - {color: "red", style: "none", width: "1pt"} - ) - - assert_resolves( - f"{shorthand}: red", - {color: "red", style: "none", width: "1.500000pt"} - ) - - # Do not test color=black because it's not CSS conforming - assert_resolves( - f"{shorthand}: 1pt", - {style: "none", width: "1pt"} - ) - - # Do not test color=black because it's not CSS conforming - assert_resolves( - f"{shorthand}: solid", - {style: "solid", width: "1.500000pt"} - ) +def test_css_border_shorthands(shorthand, sides): + def create_border_dict(sides, color=None, style=None, width=None): + resolved = {} + for side in sides: + if (color): + resolved[f"border-{side}-color"] = color + if (style): + resolved[f"border-{side}-style"] = style + if (width): + resolved[f"border-{side}-width"] = width + return resolved + + assert_resolves( + f"{shorthand}: 1pt red solid", + create_border_dict(sides, "red", "solid", "1pt") + ) + + assert_resolves( + f"{shorthand}: red 1pt solid", + create_border_dict(sides, "red", "solid", "1pt") + ) + + assert_resolves( + f"{shorthand}: red solid 1pt", + create_border_dict(sides, "red", "solid", "1pt") + ) + + assert_resolves( + f"{shorthand}: solid 1pt red", + create_border_dict(sides, "red", "solid", "1pt") + ) + + assert_resolves( + f"{shorthand}: red solid", + create_border_dict(sides, "red", "solid", "1.500000pt") + ) + + # Note: color=black is not CSS conforming (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) + assert_resolves( + f"{shorthand}: 1pt solid", + create_border_dict(sides, "black", "solid", "1pt") + ) + + assert_resolves( + f"{shorthand}: 1pt red", + create_border_dict(sides, "red", "none", "1pt") + ) + + assert_resolves( + f"{shorthand}: red", + create_border_dict(sides, "red", "none", "1.500000pt") + ) + + # Note: color=black is not CSS conforming + assert_resolves( + f"{shorthand}: 1pt", + create_border_dict(sides, "black", "none", "1pt") + ) + + # Note: color=black is not CSS conforming + assert_resolves( + f"{shorthand}: solid", + create_border_dict(sides, "black", "solid", "1.500000pt") + ) @pytest.mark.parametrize( From fb31af36c400ae89ecbaa0a65cf5ea47b24b024f Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 10 Jan 2022 12:42:44 -0500 Subject: [PATCH 03/20] BUG: Xlsxwriter crashes on to_excel with only 'border-color' (GH30008/GH42276) --- pandas/io/formats/excel.py | 15 +++++++++++---- pandas/tests/io/formats/test_to_excel.py | 4 ++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 1f1ca434a22c0..5eaee231cd224 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -235,13 +235,14 @@ def build_border( "style": self._border_style( props.get(f"border-{side}-style"), props.get(f"border-{side}-width"), + props.get(f"border-{side}-color") ), "color": self.color_to_excel(props.get(f"border-{side}-color")), } for side in ["top", "right", "bottom", "left"] } - def _border_style(self, style: str | None, width: str | None): + def _border_style(self, style: str | None, width: str | None, color: str | None): # convert styles and widths to openxml, one of: # 'dashDot' # 'dashDotDot' @@ -256,14 +257,20 @@ def _border_style(self, style: str | None, width: str | None): # 'slantDashDot' # 'thick' # 'thin' - if width is None and style is None: + if width is None and style is None and color is None: + # Return None will remove "border" from style dictionary return None + + if width is None and style is None: + # Return "none" will keep "border" in style dictionary + return "none" + if style == "none" or style == "hidden": - return None + return "none" width_name = self._get_width_name(width) if width_name is None: - return None + return "none" if style in (None, "groove", "ridge", "inset", "outset", "solid"): # not handled diff --git a/pandas/tests/io/formats/test_to_excel.py b/pandas/tests/io/formats/test_to_excel.py index 968ad63eaceef..f34fe204f03aa 100644 --- a/pandas/tests/io/formats/test_to_excel.py +++ b/pandas/tests/io/formats/test_to_excel.py @@ -187,6 +187,10 @@ "border-top-style: solid; border-top-color: #06c", {"border": {"top": {"style": "medium", "color": "0066CC"}}}, ), + ( + "border-top-color: blue", + {"border": {"top": {"color": "0000FF", "style": "none"}}}, + ), # ALIGNMENT # - horizontal ("text-align: center", {"alignment": {"horizontal": "center"}}), From 9827ad9e7ada84ada6870f5b09c2a885bcc89abd Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 10 Jan 2022 13:24:43 -0500 Subject: [PATCH 04/20] DOC: Update documentation to resolve GH42267 --- doc/source/user_guide/style.ipynb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/style.ipynb b/doc/source/user_guide/style.ipynb index f94f86b4eea58..fb50b40019e00 100644 --- a/doc/source/user_guide/style.ipynb +++ b/doc/source/user_guide/style.ipynb @@ -1577,6 +1577,9 @@ "Some support (*since version 0.20.0*) is available for exporting styled `DataFrames` to Excel worksheets using the `OpenPyXL` or `XlsxWriter` engines. CSS2.2 properties handled include:\n", "\n", "- `background-color`\n", + "- `border-style` properties\n", + "- `border-width` properties\n", + "- `border-color` properties\n", "- `color`\n", "- `font-family`\n", "- `font-style`\n", @@ -1587,7 +1590,7 @@ "- `white-space: nowrap`\n", "\n", "\n", - "- Currently broken: `border-style`, `border-width`, `border-color` and their {`top`, `right`, `bottom`, `left` variants}\n", + "- Shorthand and side-specific border properties are supported (e.g. `border-style` and `border-left-style`) as well as the `border` shorthands for all sides (`border: 1px solid green`) or specified sides (`border-left: 1px solid green`). Using a `border` shorthand will override any border properties set before it (See [CSS Working Group](https://drafts.csswg.org/css-backgrounds/#border-shorthands) for more details)\n", "\n", "\n", "- Only CSS2 named colors and hex colors of the form `#rgb` or `#rrggbb` are currently supported.\n", From aaac09a683bc144fc17111d131d21e1c74e46cfe Mon Sep 17 00:00:00 2001 From: tehunter Date: Tue, 11 Jan 2022 08:34:11 -0500 Subject: [PATCH 05/20] CLN: Comply with PEP8 checks --- pandas/io/formats/css.py | 22 +++++++++++++++------- pandas/tests/io/formats/test_css.py | 6 ++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index adaae1b90b059..01e472160cf27 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -26,16 +26,22 @@ def expand(self, prop, value: str): return expand + def _border_expander(side: str = ""): if (side != ""): side = f"-{side}" - def expand(self, prop, value:str): + + def expand(self, prop, value: str): tokens = value.split() if (len(tokens) == 0 or len(tokens) > 3): - raise ValueError("Too many tokens provided to border (expected 1-3)") - + raise ValueError(f'Too many tokens provided to "{prop}" (expected 1-3)') + # 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"} + border_declarations = { + f"border{side}-color": "black", + f"border{side}-style": "none", + f"border{side}-width": "medium" + } for token in tokens: token_key = "color" if token in self.BORDER_STYLES: @@ -51,10 +57,9 @@ def expand(self, prop, value:str): # Per CSS, "border" will reset previous "border-*" definitions yield from self.atomize(border_declarations.items()) - + return expand - class CSSResolver: """ @@ -106,7 +111,10 @@ class CSSResolver: } ) - BORDER_STYLES = ["none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"] + BORDER_STYLES = [ + "none", "hidden", "dotted", "dashed", "solid", + "double", "groove", "ridge", "inset", "outset" + ] SIDE_SHORTHANDS = { 1: [0, 0, 0, 0], diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index 60f5527204de1..bb287c1039aa2 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -122,6 +122,7 @@ def test_css_side_shorthands(shorthand, expansions): with tm.assert_produces_warning(CSSWarning): assert_resolves(f"{shorthand}: 1pt 1pt 1pt 1pt 1pt", {}) + @pytest.mark.parametrize( "shorthand,sides", [ @@ -141,7 +142,7 @@ def test_css_side_shorthands(shorthand, expansions): "border-left", ["left"] ), - ( + ( "border", ["top", "right", "bottom", "left"] ), @@ -184,7 +185,8 @@ def create_border_dict(sides, color=None, style=None, width=None): create_border_dict(sides, "red", "solid", "1.500000pt") ) - # Note: color=black is not CSS conforming (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) + # Note: color=black is not CSS conforming + # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) assert_resolves( f"{shorthand}: 1pt solid", create_border_dict(sides, "black", "solid", "1pt") From f6af38a2af7073985a05a4121b0ae2dbcb2e884e Mon Sep 17 00:00:00 2001 From: tehunter Date: Tue, 11 Jan 2022 08:39:43 -0500 Subject: [PATCH 06/20] CLN: Remove whitespace --- pandas/io/formats/css.py | 8 ++++---- pandas/tests/io/formats/test_css.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 01e472160cf27..3803bcac63244 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -38,8 +38,8 @@ def expand(self, prop, value: str): # 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}-color": "black", + f"border{side}-style": "none", f"border{side}-width": "medium" } for token in tokens: @@ -112,7 +112,7 @@ class CSSResolver: ) BORDER_STYLES = [ - "none", "hidden", "dotted", "dashed", "solid", + "none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset" ] @@ -300,7 +300,7 @@ def atomize(self, declarations): expand_border_right = _border_expander("right") expand_border_bottom = _border_expander("bottom") expand_border_left = _border_expander("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") diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index bb287c1039aa2..620e13aef5e54 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -185,7 +185,7 @@ def create_border_dict(sides, color=None, style=None, width=None): create_border_dict(sides, "red", "solid", "1.500000pt") ) - # Note: color=black is not CSS conforming + # Note: color=black is not CSS conforming # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) assert_resolves( f"{shorthand}: 1pt solid", From 9af28698d7c0642b40eac5b5337a33aa61e8c26f Mon Sep 17 00:00:00 2001 From: tehunter Date: Tue, 11 Jan 2022 14:28:42 +0000 Subject: [PATCH 07/20] Fixes from pre-commit [automated commit] --- pandas/io/formats/css.py | 18 ++++++--- pandas/io/formats/excel.py | 2 +- pandas/tests/io/formats/test_css.py | 57 +++++++++-------------------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 3803bcac63244..a7551a07620c2 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -28,19 +28,19 @@ def expand(self, prop, value: str): def _border_expander(side: str = ""): - if (side != ""): + if side != "": side = f"-{side}" def expand(self, prop, value: str): tokens = value.split() - if (len(tokens) == 0 or len(tokens) > 3): + if len(tokens) == 0 or len(tokens) > 3: raise ValueError(f'Too many tokens provided to "{prop}" (expected 1-3)') # 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" + f"border{side}-width": "medium", } for token in tokens: token_key = "color" @@ -112,8 +112,16 @@ class CSSResolver: ) BORDER_STYLES = [ - "none", "hidden", "dotted", "dashed", "solid", - "double", "groove", "ridge", "inset", "outset" + "none", + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", ] SIDE_SHORTHANDS = { diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 82a58d1bb8adb..9b3beceba351d 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -235,7 +235,7 @@ def build_border( "style": self._border_style( props.get(f"border-{side}-style"), props.get(f"border-{side}-width"), - props.get(f"border-{side}-color") + props.get(f"border-{side}-color"), ), "color": self.color_to_excel(props.get(f"border-{side}-color")), } diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index 620e13aef5e54..df0641d466947 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -126,92 +126,71 @@ def test_css_side_shorthands(shorthand, expansions): @pytest.mark.parametrize( "shorthand,sides", [ - ( - "border-top", - ["top"] - ), - ( - "border-right", - ["right"] - ), + ("border-top", ["top"]), + ("border-right", ["right"]), ( "border-bottom", ["bottom"], ), - ( - "border-left", - ["left"] - ), - ( - "border", - ["top", "right", "bottom", "left"] - ), - ] + ("border-left", ["left"]), + ("border", ["top", "right", "bottom", "left"]), + ], ) def test_css_border_shorthands(shorthand, sides): def create_border_dict(sides, color=None, style=None, width=None): resolved = {} for side in sides: - if (color): + if color: resolved[f"border-{side}-color"] = color - if (style): + if style: resolved[f"border-{side}-style"] = style - if (width): + if width: resolved[f"border-{side}-width"] = width return resolved assert_resolves( - f"{shorthand}: 1pt red solid", - create_border_dict(sides, "red", "solid", "1pt") + f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt") ) assert_resolves( - f"{shorthand}: red 1pt solid", - create_border_dict(sides, "red", "solid", "1pt") + f"{shorthand}: red 1pt solid", create_border_dict(sides, "red", "solid", "1pt") ) assert_resolves( - f"{shorthand}: red solid 1pt", - create_border_dict(sides, "red", "solid", "1pt") + f"{shorthand}: red solid 1pt", create_border_dict(sides, "red", "solid", "1pt") ) assert_resolves( - f"{shorthand}: solid 1pt red", - create_border_dict(sides, "red", "solid", "1pt") + f"{shorthand}: solid 1pt red", create_border_dict(sides, "red", "solid", "1pt") ) assert_resolves( f"{shorthand}: red solid", - create_border_dict(sides, "red", "solid", "1.500000pt") + create_border_dict(sides, "red", "solid", "1.500000pt"), ) # Note: color=black is not CSS conforming # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) assert_resolves( - f"{shorthand}: 1pt solid", - create_border_dict(sides, "black", "solid", "1pt") + f"{shorthand}: 1pt solid", create_border_dict(sides, "black", "solid", "1pt") ) assert_resolves( - f"{shorthand}: 1pt red", - create_border_dict(sides, "red", "none", "1pt") + f"{shorthand}: 1pt red", create_border_dict(sides, "red", "none", "1pt") ) assert_resolves( - f"{shorthand}: red", - create_border_dict(sides, "red", "none", "1.500000pt") + f"{shorthand}: red", create_border_dict(sides, "red", "none", "1.500000pt") ) # Note: color=black is not CSS conforming assert_resolves( - f"{shorthand}: 1pt", - create_border_dict(sides, "black", "none", "1pt") + f"{shorthand}: 1pt", create_border_dict(sides, "black", "none", "1pt") ) # Note: color=black is not CSS conforming assert_resolves( - f"{shorthand}: solid", - create_border_dict(sides, "black", "solid", "1.500000pt") + f"{shorthand}: solid", create_border_dict(sides, "black", "solid", "1.500000pt") ) From 13c3fa668d9fe98f7e63af6a3c9e47e7b046c642 Mon Sep 17 00:00:00 2001 From: tehunter Date: Tue, 11 Jan 2022 10:15:27 -0500 Subject: [PATCH 08/20] BUG: Fixed issue with invalid colors generating borders --- pandas/io/formats/excel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 82a58d1bb8adb..9cc19c338a337 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -235,7 +235,7 @@ def build_border( "style": self._border_style( props.get(f"border-{side}-style"), props.get(f"border-{side}-width"), - props.get(f"border-{side}-color") + self.color_to_excel(props.get(f"border-{side}-color")), ), "color": self.color_to_excel(props.get(f"border-{side}-color")), } From 18a6a410b63e14a7a6e678110d552f4a6ad9cf0b Mon Sep 17 00:00:00 2001 From: tehunter Date: Tue, 11 Jan 2022 16:30:33 +0000 Subject: [PATCH 09/20] Fixes from pre-commit [automated commit] --- pandas/tests/io/formats/test_to_excel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_to_excel.py b/pandas/tests/io/formats/test_to_excel.py index 9be21aae71f84..08ba05e7ab9ce 100644 --- a/pandas/tests/io/formats/test_to_excel.py +++ b/pandas/tests/io/formats/test_to_excel.py @@ -292,7 +292,8 @@ def test_css_to_excel_good_colors(input_color, output_color): expected["font"] = {"color": output_color} expected["border"] = { - k: {"color": output_color, "style": "none"} for k in ("top", "right", "bottom", "left") + k: {"color": output_color, "style": "none"} + for k in ("top", "right", "bottom", "left") } with tm.assert_produces_warning(None): From 905a6ca78c86a091558453a7930101bf3aed65a8 Mon Sep 17 00:00:00 2001 From: tehunter Date: Wed, 12 Jan 2022 11:14:07 -0500 Subject: [PATCH 10/20] Cleaned code and improved test (GH45312) Addressing PR change requests --- pandas/io/formats/css.py | 12 ++--- pandas/tests/io/excel/test_style.py | 44 +++++++++++++++-- pandas/tests/io/formats/test_css.py | 73 ++++++++++------------------- 3 files changed, 73 insertions(+), 56 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index a7551a07620c2..a50b43acc21d5 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -34,7 +34,7 @@ def _border_expander(side: str = ""): def expand(self, prop, value: str): tokens = value.split() if len(tokens) == 0 or len(tokens) > 3: - raise ValueError(f'Too many tokens provided to "{prop}" (expected 1-3)') + warnings.warn(f'Too many tokens provided to "{prop}" (expected 1-3)', CSSWarning) # TODO: Can we use current color as initial value to comply with CSS standards? border_declarations = { @@ -46,11 +46,10 @@ def expand(self, prop, value: str): token_key = "color" if token in self.BORDER_STYLES: token_key = "style" - - for ratio in self.BORDER_WIDTH_RATIOS: - if ratio in token: - token_key = "width" - break + elif any([ratio in token for ratio in self.BORDER_WIDTH_RATIOS]): + token_key = "width" + else: + token_key = "color" # TODO: Warn user if item entered more than once (e.g. "border: red green") border_declarations[f"border{side}-{token_key}"] = token @@ -68,6 +67,7 @@ class CSSResolver: UNIT_RATIOS = { "pt": ("pt", 1), + "em": ("em", 1), "rem": ("pt", 12), "ex": ("em", 0.5), # 'ch': diff --git a/pandas/tests/io/excel/test_style.py b/pandas/tests/io/excel/test_style.py index 8a142aebd719d..e57d90c6e5e75 100644 --- a/pandas/tests/io/excel/test_style.py +++ b/pandas/tests/io/excel/test_style.py @@ -68,6 +68,44 @@ def test_styler_to_excel_unstyled(engine): ["alignment", "vertical"], {"xlsxwriter": None, "openpyxl": "bottom"}, # xlsxwriter Fails ), + # Border widths + ("border-left: 2pt solid red", ["border", "left", "style"], "medium"), + ("border-left: 1pt dotted red", ["border", "left", "style"], "dotted"), + ("border-left: 2pt dotted red", ["border", "left", "style"], "mediumDashDotDot"), + ("border-left: 1pt dashed red", ["border", "left", "style"], "dashed"), + ("border-left: 2pt dashed red", ["border", "left", "style"], "mediumDashed"), + ("border-left: 1pt solid red", ["border", "left", "style"], "thin"), + ("border-left: 3pt solid red", ["border", "left", "style"], "thick"), + # Border expansion + ( + "border-left: 2pt solid #111222", + ["border", "left", "color", "rgb"], + {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + ), + ("border: 1pt solid red", ["border", "top", "style"], "thin"), + ( + "border: 1pt solid #111222", + ["border", "top", "color", "rgb"], + {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + ), + ("border: 1pt solid red", ["border", "right", "style"], "thin"), + ( + "border: 1pt solid #111222", + ["border", "right", "color", "rgb"], + {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + ), + ("border: 1pt solid red", ["border", "bottom", "style"], "thin"), + ( + "border: 1pt solid #111222", + ["border", "bottom", "color", "rgb"], + {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + ), + ("border: 1pt solid red", ["border", "left", "style"], "thin"), + ( + "border: 1pt solid #111222", + ["border", "left", "color", "rgb"], + {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + ), ] @@ -93,7 +131,7 @@ def test_styler_to_excel_basic(engine, css, attrs, expected): # test styled cell has expected styles u_cell, s_cell = wb["dataframe"].cell(2, 2), wb["styled"].cell(2, 2) for attr in attrs: - u_cell, s_cell = getattr(u_cell, attr), getattr(s_cell, attr) + u_cell, s_cell = getattr(u_cell, attr, None), getattr(s_cell, attr) if isinstance(expected, dict): assert u_cell is None or u_cell != expected[engine] @@ -134,8 +172,8 @@ def test_styler_to_excel_basic_indexes(engine, css, attrs, expected): ui_cell, si_cell = wb["null_styled"].cell(2, 1), wb["styled"].cell(2, 1) uc_cell, sc_cell = wb["null_styled"].cell(1, 2), wb["styled"].cell(1, 2) for attr in attrs: - ui_cell, si_cell = getattr(ui_cell, attr), getattr(si_cell, attr) - uc_cell, sc_cell = getattr(uc_cell, attr), getattr(sc_cell, attr) + ui_cell, si_cell = getattr(ui_cell, attr, None), getattr(si_cell, attr) + uc_cell, sc_cell = getattr(uc_cell, attr, None), getattr(sc_cell, attr) if isinstance(expected, dict): assert ui_cell is None or ui_cell != expected[engine] diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index df0641d466947..fa7bff4a001e9 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -57,6 +57,8 @@ def test_css_parse_normalisation(name, norm, abnorm): ("font-size: 1unknownunit", "font-size: 1em"), ("font-size: 10", "font-size: 1em"), ("font-size: 10 pt", "font-size: 1em"), + # Too many args + ("border-top: 1pt solid red green", "border-top: 1pt solid green"), ], ) def test_css_parse_invalid(invalid_css, remainder): @@ -128,15 +130,33 @@ def test_css_side_shorthands(shorthand, expansions): [ ("border-top", ["top"]), ("border-right", ["right"]), - ( - "border-bottom", - ["bottom"], - ), + ("border-bottom", ["bottom"]), ("border-left", ["left"]), ("border", ["top", "right", "bottom", "left"]), ], ) -def test_css_border_shorthands(shorthand, sides): +@pytest.mark.parametrize( + "prop, expected", + [ + ("1pt red solid", ("red", "solid", "1pt")), + ("red 1pt solid", ("red", "solid", "1pt")), + ("red solid 1pt", ("red", "solid", "1pt")), + ("solid 1pt red", ("red", "solid", "1pt")), + ("red solid", ("red", "solid", "1.500000pt")), + # Note: color=black is not CSS conforming + # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) + ("1pt solid", ("black", "solid", "1pt")), + ("1pt red", ("red", "none", "1pt")), + ("red", ("red", "none", "1.500000pt")), + ("1pt", ("black", "none", "1pt")), + ("solid", ("black", "solid", "1.500000pt")), + # Sizes + ("1em", ("black", "none", "12pt")), + ], +) +def test_css_border_shorthands(shorthand, sides, prop, expected): + color, style, width = expected + def create_border_dict(sides, color=None, style=None, width=None): resolved = {} for side in sides: @@ -149,48 +169,7 @@ def create_border_dict(sides, color=None, style=None, width=None): return resolved assert_resolves( - f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt") - ) - - assert_resolves( - f"{shorthand}: red 1pt solid", create_border_dict(sides, "red", "solid", "1pt") - ) - - assert_resolves( - f"{shorthand}: red solid 1pt", create_border_dict(sides, "red", "solid", "1pt") - ) - - assert_resolves( - f"{shorthand}: solid 1pt red", create_border_dict(sides, "red", "solid", "1pt") - ) - - assert_resolves( - f"{shorthand}: red solid", - create_border_dict(sides, "red", "solid", "1.500000pt"), - ) - - # Note: color=black is not CSS conforming - # (See https://drafts.csswg.org/css-backgrounds/#border-shorthands) - assert_resolves( - f"{shorthand}: 1pt solid", create_border_dict(sides, "black", "solid", "1pt") - ) - - assert_resolves( - f"{shorthand}: 1pt red", create_border_dict(sides, "red", "none", "1pt") - ) - - assert_resolves( - f"{shorthand}: red", create_border_dict(sides, "red", "none", "1.500000pt") - ) - - # Note: color=black is not CSS conforming - assert_resolves( - f"{shorthand}: 1pt", create_border_dict(sides, "black", "none", "1pt") - ) - - # Note: color=black is not CSS conforming - assert_resolves( - f"{shorthand}: solid", create_border_dict(sides, "black", "solid", "1.500000pt") + f"{shorthand}: {prop}", create_border_dict(sides, color, style, width) ) From 7e6f5f1174ab372d0295545f13bd1654cac6fc64 Mon Sep 17 00:00:00 2001 From: tehunter Date: Wed, 12 Jan 2022 16:29:30 +0000 Subject: [PATCH 11/20] Fixes from pre-commit [automated commit] --- pandas/io/formats/css.py | 4 +++- pandas/tests/io/excel/test_style.py | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index a50b43acc21d5..f7daf851cd918 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -34,7 +34,9 @@ def _border_expander(side: str = ""): def expand(self, prop, value: str): tokens = value.split() if len(tokens) == 0 or len(tokens) > 3: - warnings.warn(f'Too many tokens provided to "{prop}" (expected 1-3)', CSSWarning) + warnings.warn( + f'Too many tokens provided to "{prop}" (expected 1-3)', CSSWarning + ) # TODO: Can we use current color as initial value to comply with CSS standards? border_declarations = { diff --git a/pandas/tests/io/excel/test_style.py b/pandas/tests/io/excel/test_style.py index e57d90c6e5e75..a34177e3d64a1 100644 --- a/pandas/tests/io/excel/test_style.py +++ b/pandas/tests/io/excel/test_style.py @@ -80,31 +80,31 @@ def test_styler_to_excel_unstyled(engine): ( "border-left: 2pt solid #111222", ["border", "left", "color", "rgb"], - {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + {"xlsxwriter": "FF111222", "openpyxl": "00111222"}, ), ("border: 1pt solid red", ["border", "top", "style"], "thin"), ( "border: 1pt solid #111222", ["border", "top", "color", "rgb"], - {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + {"xlsxwriter": "FF111222", "openpyxl": "00111222"}, ), ("border: 1pt solid red", ["border", "right", "style"], "thin"), ( "border: 1pt solid #111222", ["border", "right", "color", "rgb"], - {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + {"xlsxwriter": "FF111222", "openpyxl": "00111222"}, ), ("border: 1pt solid red", ["border", "bottom", "style"], "thin"), ( "border: 1pt solid #111222", ["border", "bottom", "color", "rgb"], - {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + {"xlsxwriter": "FF111222", "openpyxl": "00111222"}, ), ("border: 1pt solid red", ["border", "left", "style"], "thin"), ( "border: 1pt solid #111222", ["border", "left", "color", "rgb"], - {"xlsxwriter": "FF111222", "openpyxl": "00111222"} + {"xlsxwriter": "FF111222", "openpyxl": "00111222"}, ), ] From 337b2f4c0e375cc8d0aee1f935818b421d5d6abb Mon Sep 17 00:00:00 2001 From: tehunter Date: Thu, 13 Jan 2022 20:25:29 -0500 Subject: [PATCH 12/20] Clean border_expander Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- pandas/io/formats/css.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index f7daf851cd918..70c7e0ac3d1d5 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -45,16 +45,13 @@ def expand(self, prop, value: str): f"border{side}-width": "medium", } for token in tokens: - token_key = "color" if token in self.BORDER_STYLES: - token_key = "style" + border_declarations[f"border{side}-style"] = token elif any([ratio in token for ratio in self.BORDER_WIDTH_RATIOS]): - token_key = "width" + border_declarations[f"border{side}-width"] = token else: - token_key = "color" - + border_declarations[f"border{side}-color"] = token # TODO: Warn user if item entered more than once (e.g. "border: red green") - border_declarations[f"border{side}-{token_key}"] = token # Per CSS, "border" will reset previous "border-*" definitions yield from self.atomize(border_declarations.items()) From cd281985401eb0424b1d4c1a6bc9460581323899 Mon Sep 17 00:00:00 2001 From: tehunter Date: Thu, 13 Jan 2022 21:37:16 -0500 Subject: [PATCH 13/20] Eliminate redundant test expansions --- pandas/tests/io/formats/test_css.py | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index fa7bff4a001e9..6b1432a9ea406 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -135,6 +135,23 @@ def test_css_side_shorthands(shorthand, expansions): ("border", ["top", "right", "bottom", "left"]), ], ) +def test_css_border_shorthands(shorthand, sides): + + def create_border_dict(sides, color=None, style=None, width=None): + resolved = {} + for side in sides: + if color: + resolved[f"border-{side}-color"] = color + if style: + resolved[f"border-{side}-style"] = style + if width: + resolved[f"border-{side}-width"] = width + return resolved + + assert_resolves( + f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt") + ) + @pytest.mark.parametrize( "prop, expected", [ @@ -154,22 +171,16 @@ def test_css_side_shorthands(shorthand, expansions): ("1em", ("black", "none", "12pt")), ], ) -def test_css_border_shorthands(shorthand, sides, prop, expected): +def test_css_border_shorthands(prop, expected): color, style, width = expected - def create_border_dict(sides, color=None, style=None, width=None): - resolved = {} - for side in sides: - if color: - resolved[f"border-{side}-color"] = color - if style: - resolved[f"border-{side}-style"] = style - if width: - resolved[f"border-{side}-width"] = width - return resolved - assert_resolves( - f"{shorthand}: {prop}", create_border_dict(sides, color, style, width) + f"border-left: {prop}", + { + "border-left-color": color, + "border-left-style": style, + "border-left-width": width, + } ) From 7caca168e46d49ff3cba2db6be5954cdc85f50b8 Mon Sep 17 00:00:00 2001 From: tehunter Date: Fri, 14 Jan 2022 02:45:43 +0000 Subject: [PATCH 14/20] Fixes from pre-commit [automated commit] --- pandas/tests/io/formats/test_css.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index 6b1432a9ea406..cfe7a161e3b3f 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -136,7 +136,6 @@ def test_css_side_shorthands(shorthand, expansions): ], ) def test_css_border_shorthands(shorthand, sides): - def create_border_dict(sides, color=None, style=None, width=None): resolved = {} for side in sides: @@ -152,6 +151,7 @@ def create_border_dict(sides, color=None, style=None, width=None): f"{shorthand}: 1pt red solid", create_border_dict(sides, "red", "solid", "1pt") ) + @pytest.mark.parametrize( "prop, expected", [ @@ -175,12 +175,12 @@ def test_css_border_shorthands(prop, expected): color, style, width = expected assert_resolves( - f"border-left: {prop}", + f"border-left: {prop}", { - "border-left-color": color, + "border-left-color": color, "border-left-style": style, "border-left-width": width, - } + }, ) From 50f93a1c2557d526280e97ba2c144c985d5e2f60 Mon Sep 17 00:00:00 2001 From: tehunter Date: Sun, 16 Jan 2022 15:42:54 -0500 Subject: [PATCH 15/20] Updated docstrings and what's new entry --- doc/source/whatsnew/v1.5.0.rst | 7 ++-- pandas/io/formats/css.py | 61 ++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index ecf31c93c5fa9..5743398dd984d 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -20,6 +20,7 @@ Styler ^^^^^^ - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) + - Added the ability to render `border` and `border-{side}` CSS properties in Excel (:issue:`42276`) .. _whatsnew_150.enhancements.enhancement2: @@ -43,8 +44,10 @@ These are bug fixes that might have notable behavior changes. .. _whatsnew_150.notable_bug_fixes.notable_bug_fix1: -notable_bug_fix1 -^^^^^^^^^^^^^^^^ +Styler +^^^^^^ + +- Fixed bug in :class:`CSSToExcelConverter` leading to ``TypeError`` when border color provided without border style for ``xlsxwriter`` engine (:issue:`42276`) .. _whatsnew_150.notable_bug_fixes.notable_bug_fix2: diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 70c7e0ac3d1d5..e5031af4adab7 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -4,6 +4,7 @@ from __future__ import annotations import re +from typing import Callable import warnings @@ -13,8 +14,36 @@ class CSSWarning(UserWarning): """ -def _side_expander(prop_fmt: str): +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 + + Notes + ----- + Description of [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) syntax + """ def expand(self, prop, value: str): + """ + 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)] @@ -27,11 +56,37 @@ def expand(self, prop, value: str): return expand -def _border_expander(side: str = ""): +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): + def expand(self, prop, value: str) -> tuple[str, str]: + """ + 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( From 4785dc488aca132bfacc4ed5a2c5256cd1c1234e Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 17 Jan 2022 16:28:55 -0500 Subject: [PATCH 16/20] Update doc/source/whatsnew/v1.5.0.rst Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- doc/source/whatsnew/v1.5.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index b797544b37d6f..0fe7e16c9881e 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -20,7 +20,7 @@ Styler ^^^^^^ - New method :meth:`.Styler.to_string` for alternative customisable output methods (:issue:`44502`) - - Added the ability to render `border` and `border-{side}` CSS properties in Excel (:issue:`42276`) + - Added the ability to render ``border`` and ``border-{side}`` CSS properties in Excel (:issue:`42276`) .. _whatsnew_150.enhancements.enhancement2: From 93cfb922d27d6f1d59951711cd1e6f56602b43bd Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 17 Jan 2022 16:29:08 -0500 Subject: [PATCH 17/20] Update pandas/tests/io/formats/test_css.py Co-authored-by: JHM Darbyshire <24256554+attack68@users.noreply.github.com> --- pandas/tests/io/formats/test_css.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/formats/test_css.py b/pandas/tests/io/formats/test_css.py index cfe7a161e3b3f..c93694481ef53 100644 --- a/pandas/tests/io/formats/test_css.py +++ b/pandas/tests/io/formats/test_css.py @@ -135,7 +135,7 @@ def test_css_side_shorthands(shorthand, expansions): ("border", ["top", "right", "bottom", "left"]), ], ) -def test_css_border_shorthands(shorthand, sides): +def test_css_border_shorthand_sides(shorthand, sides): def create_border_dict(sides, color=None, style=None, width=None): resolved = {} for side in sides: From dec6920e7c0e25feec09a62a77f0a18b49cfbb1f Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 17 Jan 2022 16:32:43 -0500 Subject: [PATCH 18/20] Resolved doc-build check --- pandas/io/formats/css.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index e5031af4adab7..0e85c37c7ebc2 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -2,6 +2,7 @@ Utilities for interpreting CSS from Stylers for formatting non-HTML outputs. """ from __future__ import annotations +from email.generator import Generator import re from typing import Callable @@ -31,7 +32,7 @@ def _side_expander(prop_fmt: str) -> Callable: ----- Description of [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) syntax """ - def expand(self, prop, value: str): + def expand(self, prop, value: str) -> Generator: """ Expand shorthand property into side-specific property (top, right, bottom, left) @@ -72,7 +73,7 @@ def _border_expander(side: str = "") -> Callable: if side != "": side = f"-{side}" - def expand(self, prop, value: str) -> tuple[str, str]: + def expand(self, prop, value: str) -> Generator: """ Expand border into color, style, and width tuples From 17dc457e994d686fb203e853be55044e938389e7 Mon Sep 17 00:00:00 2001 From: tehunter Date: Mon, 17 Jan 2022 21:51:00 +0000 Subject: [PATCH 19/20] Fixes from pre-commit [automated commit] --- pandas/io/formats/css.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 0e85c37c7ebc2..13dcb7d5d175e 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -2,8 +2,8 @@ Utilities for interpreting CSS from Stylers for formatting non-HTML outputs. """ from __future__ import annotations -from email.generator import Generator +from email.generator import Generator import re from typing import Callable import warnings @@ -32,6 +32,7 @@ def _side_expander(prop_fmt: str) -> Callable: ----- Description of [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) syntax """ + def expand(self, prop, value: str) -> Generator: """ Expand shorthand property into side-specific property (top, right, bottom, left) From fa15e0a046756cf22349bd4c99597ae0a96bb08b Mon Sep 17 00:00:00 2001 From: tehunter Date: Thu, 10 Feb 2022 08:27:15 -0500 Subject: [PATCH 20/20] Pre-commit cleanup --- pandas/io/formats/css.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pandas/io/formats/css.py b/pandas/io/formats/css.py index 13dcb7d5d175e..5335887785881 100644 --- a/pandas/io/formats/css.py +++ b/pandas/io/formats/css.py @@ -3,9 +3,11 @@ """ from __future__ import annotations -from email.generator import Generator import re -from typing import Callable +from typing import ( + Callable, + Generator, +) import warnings @@ -27,13 +29,9 @@ def _side_expander(prop_fmt: str) -> Callable: Returns ------- function: Return to call when a 'border(-{side}): {value}' string is encountered - - Notes - ----- - Description of [shorthand](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) syntax """ - def expand(self, prop, value: str) -> Generator: + def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]: """ Expand shorthand property into side-specific property (top, right, bottom, left) @@ -74,7 +72,7 @@ def _border_expander(side: str = "") -> Callable: if side != "": side = f"-{side}" - def expand(self, prop, value: str) -> Generator: + def expand(self, prop, value: str) -> Generator[tuple[str, str], None, None]: """ Expand border into color, style, and width tuples @@ -348,7 +346,7 @@ def _error(): size_fmt = f"{val:f}pt" return size_fmt - def atomize(self, declarations): + def atomize(self, declarations) -> Generator[tuple[str, str], None, None]: for prop, value in declarations: attr = "expand_" + prop.replace("-", "_") try: