Description
Pandas version checks
-
I have checked that this issue has not already been reported.
-
I have confirmed this bug exists on the latest version of pandas.
-
I have confirmed this bug exists on the main branch of pandas.
Reproducible Example
import pandas as pd
df = pd.DataFrame({"A": [1, 0, -1]})
css = 'text-align: left; number-format: "0;[Red](0);-;@"; ;'
try:
(
df
.style
# Replace zero with dash, negative with red
.applymap(lambda x: css)
.to_excel("test.xlsx")
)
except Exception as e:
print(e)
Issue Description
Styler does not accept CSS value string types enclosed in double quotes. In particular, it crashes if the string contains semi-colons. This means full number-format
strings cannot be passed to Styler
for exporting to Excel. Excel number format is of the form: <positive>;<negative>;<zero>;<text>
. The semi-colon dividers here will conflict with CSS semi-colon dividers unless they can be properly encapsulated in a string.
The problem is rooted in the naive string split in maybe_convert_css_to_tuples
and CSSResolver
. A possible fix using the TextFileReader
parser is shown in the Expected Behavior section.
Expected Behavior
In the above example, the function should export the following sheet:
Here is a set of changes that can produce the desired behavior using pandas parsing libraries:
### In style_render.py
def maybe_convert_css_to_tuples(style):
from io import StringIO
from pandas.io.parsers.readers import TextFileReader
if style and isinstance(style, str):
# Use "C" parser because of line terminator
s = TextFileReader(StringIO(style), engine="c", delimiter=":", lineterminator=";", skipinitialspace=True, index=False, header=None).read()
try:
return [
(row[0].strip(), row[1].strip())
for _, row in s.iterrows()
if row[0] != ""
]
except IndexError:
raise ValueError(
"Styles supplied as string must follow CSS rule formats, "
f"for example 'attr: val;'. '{style}' was given."
)
return style
### In css.py
class CSSResolver():
# ...
def parse(self, declarations_str: str):
from io import StringIO
from pandas.io.parsers.c_parser_wrapper import CParserWrapper
from pandas.io.parsers.readers import TextFileReader
if (declarations_str):
s = TextFileReader(StringIO(declarations_str), engine="c", delimiter=":", lineterminator=";", skipinitialspace=True, index=False, header=None).read()
try:
for _,row in s.iterrows():
prop, val = row[0], row[1]
prop = prop.strip().lower()
# TODO: don't lowercase case sensitive parts of values (strings)
val = val.strip().lower()
yield prop, val
except Exception as e:
print(e)
### In io/formats/excel.py
class CssExcelCell(ExcelCell):
def __init__(
self,
row: int,
col: int,
val,
style: dict | None,
css_styles: dict[tuple[int, int], list[tuple[str, Any]]] | None,
css_row: int,
css_col: int,
css_converter: Callable | None,
**kwargs,
):
if css_styles and css_converter:
# Not exhaustively tested. Might be better to add quotes conditionally based on type of `v`
css = ";".join(a + ":" + "\"" + str(v) + "\"" for (a, v) in css_styles[css_row, css_col])
style = css_converter(css)
return super().__init__(row=row, col=col, val=val, style=style, **kwargs)
Installed Versions
INSTALLED VERSIONS
commit : 06d2301
python : 3.10.1.final.0
python-bits : 64
OS : Windows
OS-release : 10
...
pandas : 1.4.1
numpy : 1.22.0
pytz : 2021.3
dateutil : 2.8.2
pip : 22.0.3
setuptools : 60.2.0
Cython : None
pytest : 7.0.1
hypothesis : None
sphinx : None
blosc : None
feather : None
xlsxwriter : 3.0.2
lxml.etree : None
html5lib : None
pymysql : None
psycopg2 : None
jinja2 : 3.0.3
IPython : 8.0.0
pandas_datareader: None
bs4 : None
bottleneck : 1.3.2
fastparquet : None
fsspec : None
gcsfs : None
matplotlib : 3.5.1
numba : None
numexpr : 2.8.0
odfpy : None
openpyxl : 3.0.9
pandas_gbq : None
pyarrow : None
pyreadstat : None
pyxlsb : None
s3fs : None
scipy : 1.7.3
sqlalchemy : None
tables : 3.6.1
tabulate : None
xarray : None
xlrd : 2.0.1
xlwt : None
zstandard : None