diff --git a/doc/source/_static/style/format_excel_css.png b/doc/source/_static/style/format_excel_css.png new file mode 100644 index 0000000000000..0bd4662c3f2d0 Binary files /dev/null and b/doc/source/_static/style/format_excel_css.png differ diff --git a/doc/source/whatsnew/v1.4.2.rst b/doc/source/whatsnew/v1.4.2.rst index 9f4d248ca7042..badda6a73d1c8 100644 --- a/doc/source/whatsnew/v1.4.2.rst +++ b/doc/source/whatsnew/v1.4.2.rst @@ -16,6 +16,7 @@ Fixed regressions ~~~~~~~~~~~~~~~~~ - Fixed regression in :meth:`DataFrame.drop` and :meth:`Series.drop` when :class:`Index` had extension dtype and duplicates (:issue:`45860`) - Fixed memory performance regression in :meth:`Series.fillna` when called on a :class:`DataFrame` column with ``inplace=True`` (:issue:`46149`) +- Provided an alternative solution for passing custom Excel formats in :meth:`.Styler.to_excel`, which was a regression based on stricter CSS validation. Examples available in the documentation for :meth:`.Styler.format` (:issue:`46152`) - .. --------------------------------------------------------------------------- diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index 5371957365eae..98a87bfed7da9 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -316,7 +316,9 @@ def build_fill(self, props: Mapping[str, str]): return {"fgColor": self.color_to_excel(fill_color), "patternType": "solid"} def build_number_format(self, props: Mapping[str, str]) -> dict[str, str | None]: - return {"format_code": props.get("number-format")} + fc = props.get("number-format") + fc = fc.replace("§", ";") if isinstance(fc, str) else fc + return {"format_code": fc} def build_font( self, props: Mapping[str, str] diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 0b3500cb34f87..9c62dd4072d3f 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -992,6 +992,10 @@ def format( ------- self : Styler + See Also + -------- + Styler.format_index: Format the text display value of index labels. + Notes ----- This method assigns a formatting function, ``formatter``, to each cell in the @@ -1027,6 +1031,12 @@ def format( - ``styler.format.thousands``: default None. - ``styler.format.escape``: default None. + .. warning:: + `Styler.format` is ignored when using the output format `Styler.to_excel`, + since Excel and Python have inherrently different formatting structures. + However, it is possible to use the `number-format` pseudo CSS attribute + to force Excel permissible formatting. See examples. + Examples -------- Using ``na_rep`` and ``precision`` with the default ``formatter`` @@ -1094,6 +1104,19 @@ def format( 1 & \textbf{\textasciitilde \space \textasciicircum } \\ 2 & \textbf{\$\%\#} \\ \end{tabular} + + Pandas defines a `number-format` pseudo CSS attribute instead of the `.format` + method to create `to_excel` permissible formatting. Note that semi-colons are + CSS protected characters but used as separators in Excel's format string. + Replace semi-colons with the section separator character (ASCII-245) when + defining the formatting here. + + >>> df = pd.DataFrame({"A": [1, 0, -1]}) + >>> pseudo_css = "number-format: 0§[Red](0)§-§@;" + >>> df.style.applymap(lambda v: css).to_excel("formatted_file.xlsx") + ... # doctest: +SKIP + + .. figure:: ../../_static/style/format_excel_css.png """ if all( ( @@ -1185,6 +1208,10 @@ def format_index( ------- self : Styler + See Also + -------- + Styler.format: Format the text display value of data cells. + Notes ----- This method assigns a formatting function, ``formatter``, to each level label @@ -1211,6 +1238,13 @@ def format_index( When using a ``formatter`` string the dtypes must be compatible, otherwise a `ValueError` will be raised. + .. warning:: + `Styler.format_index` is ignored when using the output format + `Styler.to_excel`, since Excel and Python have inherrently different + formatting structures. + However, it is possible to use the `number-format` pseudo CSS attribute + to force Excel permissible formatting. See documentation for `Styler.format`. + Examples -------- Using ``na_rep`` and ``precision`` with the default ``formatter`` diff --git a/pandas/tests/io/formats/test_to_excel.py b/pandas/tests/io/formats/test_to_excel.py index 08ba05e7ab9ce..b95a5b4365f43 100644 --- a/pandas/tests/io/formats/test_to_excel.py +++ b/pandas/tests/io/formats/test_to_excel.py @@ -210,6 +210,10 @@ ("white-space: normal", {"alignment": {"wrap_text": True}}), # NUMBER FORMAT ("number-format: 0%", {"number_format": {"format_code": "0%"}}), + ( + "number-format: 0§[Red](0)§-§@;", + {"number_format": {"format_code": "0;[red](0);-;@"}}, # GH 46152 + ), ], ) def test_css_to_excel(css, expected):