Skip to content

BUG: style.map() not compatible with CSS string "url(data:..." #59623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
3 tasks done
invalidarg opened this issue Aug 27, 2024 · 7 comments
Open
3 tasks done

BUG: style.map() not compatible with CSS string "url(data:..." #59623

invalidarg opened this issue Aug 27, 2024 · 7 comments
Labels
Bug Styler conditional formatting using DataFrame.style

Comments

@invalidarg
Copy link
Contributor

invalidarg commented Aug 27, 2024

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
print(pd.__version__) # 2.2.2

# Creating toy data
data = {
    "country": [ "Canada",  "Denmark"],
    "number": [ 200, 400]
}

def flag_background(country):
    if country == "Denmark":
        return """background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 640 480"><path fill="%23c8102e" d="M0 0h640.1v480H0z"/><path fill="%23fff" d="M205.7 0h68.6v480h-68.6z"/><path fill="%23fff" d="M0 205.7h640.1v68.6H0z"/></svg>');"""
    elif country == "Canada":
        return "background-color: red"
        

df = pd.DataFrame(data)
print(
    (
    df
    .style
    .map(flag_background)
    ).to_html()
)

### css in output is broken:
# #T_b3ead_row1_col0 {
#   background-image: url('data;
# }


#### But set_table_styles() works!

print(
    df
    .style
    .set_table_styles([
          {
              'selector': '.col0', 
                'props': 
                    [('background-image', '''url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 640 480"><path fill="%23c8102e" d="M0 0h640.1v480H0z"/><path fill="%23fff" d="M205.7 0h68.6v480h-68.6z"/><path fill="%23fff" d="M0 205.7h640.1v68.6H0z"/></svg>')'''), ('background-size', 'contain'),('background-repeat', 'no-repeat'),('background-position', 'center')]
         }
    ])
    .to_html()
)

### css in output is OK:
# #T_106ed .col0 {
#   background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 640 480"><path fill="%23c8102e" d="M0 0h640.1v480H0z"/><path fill="%23fff" d="M205.7 0h68.6v480h-68.6z"/><path fill="%23fff" d="M0 205.7h640.1v68.6H0z"/></svg>');
#   background-size: contain;
#   background-repeat: no-repeat;
#   background-position: center;
# }

Issue Description

I am tring to add SVG flags to each country but styler breaks css values with url(data:...

The CSS string returned by the function in style.map must be

property : value ; property2 : value ;

But there is valid CSS that does not follow this pattern.
e.g. this is valid CSS:
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 640 480"><path fill="%23c8102e" d="M0 0h640.1v480H0z"/><path fill="%23fff" d="M205.7 0h68.6v480h-68.6z"/><path fill="%23fff" d="M0 205.7h640.1v68.6H0z"/></svg>');

The problem is that pandas finds two consecutive colons : is will replace the second with semicolon ; and then truncate. I.e. the resulting HTML will be

background-image: url('data;

Expected Behavior

Let me input any valid CSS string. Remove validation / truncation since it is not compatible with valid CSS strings.

Installed Versions

/databricks/python/lib/python3.10/site-packages/_distutils_hack/init.py:33: UserWarning: Setuptools is replacing distutils.
warnings.warn("Setuptools is replacing distutils.")

INSTALLED VERSIONS

commit : d9cdd2e
python : 3.10.12.final.0
python-bits : 64
OS : Linux
OS-release : 5.15.0-1067-azure
Version : #76~20.04.1-Ubuntu SMP Thu Jun 13 18:00:23 UTC 2024
machine : x86_64
processor : x86_64
byteorder : little
LC_ALL : None
LANG : C.UTF-8
LOCALE : en_US.UTF-8

pandas : 2.2.2
numpy : 1.23.5
pytz : 2022.7
dateutil : 2.8.2
setuptools : 65.6.3
pip : 22.3.1
Cython : 0.29.32
pytest : None
hypothesis : None
sphinx : None
blosc : None
feather : None
xlsxwriter : None
lxml.etree : 4.9.1
html5lib : None
pymysql : None
psycopg2 : None
jinja2 : 3.1.2
IPython : 8.14.0
pandas_datareader : None
adbc-driver-postgresql: None
adbc-driver-sqlite : None
bs4 : None
bottleneck : None
dataframe-api-compat : None
fastparquet : None
fsspec : None
gcsfs : None
matplotlib : 3.7.0
numba : None
numexpr : None
odfpy : None
openpyxl : None
pandas_gbq : None
pyarrow : None
pyreadstat : None
python-calamine : None
pyxlsb : None
s3fs : None
scipy : 1.10.0
sqlalchemy : None
tables : None
tabulate : None
xarray : None
xlrd : None
zstandard : None
tzdata : 2024.1
qtpy : None
pyqt5 : None

@invalidarg invalidarg added Bug Needs Triage Issue that has not been reviewed by a pandas team member labels Aug 27, 2024
@invalidarg invalidarg changed the title BUG: styler.map not compatable with "url(data:..." BUG: styler.map not compatable with CSS string "url(data:..." Aug 28, 2024
@invalidarg invalidarg changed the title BUG: styler.map not compatable with CSS string "url(data:..." BUG: style.map not compatible with CSS string "url(data:..." Aug 28, 2024
@invalidarg invalidarg changed the title BUG: style.map not compatible with CSS string "url(data:..." BUG: style.map() not compatible with CSS string "url(data:..." Aug 28, 2024
@Lollitor
Copy link

Hello, this is one of my first times contributing. I noticed that it was still necessary to check if the bug existed on the main branch, so I ran the code that reproduces the bug on the main branch.

While using .map() I get the following

<style type="text/css">
#T_7aa42_row0_col0 {
  background-color: red;
}
#T_7aa42_row1_col0 {
  background-image: url('data;
}
</style>

However, I get the correct output when using set_table_styles() as reported. Therefore, I can confirm that the bug also exists on the main branch.

\pandas> git branch --show-current
main

I hope this is somehow helpful. I see if I manage to do more!

Installed Versions:

commit : ef3368a
python : 3.12.4
python-bits : 64
OS : Windows
OS-release : 11
Version : 10.0.22631
machine : AMD64
processor : AMD64 Family 25 Model 117 Stepping 2, AuthenticAMD
byteorder : little
LC_ALL : None
LANG : en_US.UTF-8
LOCALE : Italian_Italy.1252

pandas : 0+untagged.35428.gef3368a
numpy : 1.26.4
dateutil : 2.9.0.post0
pip : 24.2
Cython : 3.0.11
sphinx : 8.0.2
IPython : 8.26.0
adbc-driver-postgresql: None
adbc-driver-sqlite : None
bs4 : 4.12.3
blosc : None
bottleneck : 1.4.0
fastparquet : 2024.5.0
fsspec : 2024.6.1
html5lib : 1.1
hypothesis : 6.111.2
gcsfs : 2024.6.1
jinja2 : 3.1.4
lxml.etree : 5.3.0
matplotlib : 3.9.2
numba : 0.60.0
numexpr : 2.10.1
odfpy : None
openpyxl : 3.1.5
psycopg2 : 2.9.9
pymysql : 1.4.6
pyarrow : 17.0.0
pyreadstat : 1.2.7
pytest : 8.3.2
python-calamine : None
pytz : 2024.1
pyxlsb : 1.0.10
s3fs : 2024.6.1
scipy : 1.14.1
sqlalchemy : 2.0.32
tables : 3.10.1
tabulate : 0.9.0
xarray : 2024.7.0
xlrd : 2.0.1
xlsxwriter : 3.2.0
zstandard : 0.23.0
tzdata : 2024.1
qtpy : None
pyqt5 : None

@attack68 attack68 added Styler conditional formatting using DataFrame.style and removed Needs Triage Issue that has not been reviewed by a pandas team member labels Aug 31, 2024
@attack68
Copy link
Contributor

Possible reason to revive: #48869

@attack68
Copy link
Contributor

This behaviour is due to the function maybe_convert_css_to_tuples. Note the discussion that shared link.

@invalidarg
Copy link
Contributor Author

invalidarg commented Sep 2, 2024

Thanks attack68 for pointing to the correct direction. A patch to maybe_convert_css_to_tuples would help until (or instead) full fledged CSS parsing is in place?

Basically taking all remaining elements of the x.split(":")-list instead of only the second.

def maybe_convert_css_to_tuples(style: str) -> str:
    """
    Convert css-string to sequence of tuples format if needed.
    'color:red; border:1px solid black;' -> [('color', 'red'),
                                             ('border','1px solid red')]
    """
    if isinstance(style, str):
        s = style.split(";")
        try:
            return [
                (x.split(":")[0].strip(), ":".join(x.split(":")[1:]).strip()) # updated to take [1:] elements
                for x in s
                if ":".join(x.split(":")[1:]).strip() != "" # updated to take [1:] elements
            ]
        except IndexError as err:
            raise ValueError(
                "Styles supplied as string must follow CSS rule formats, "
                f"for example 'attr: val;'. '{style}' was given."
            ) from err
    return style

maybe_convert_css_to_tuples("""background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 640 480"><path fill="%23c8102e" d="M0 0h640.1v480H0z"/><path fill="%23fff" d="M205.7 0h68.6v480h-68.6z"/><path fill="%23fff" d="M0 205.7h640.1v68.6H0z"/></svg>');""")

This seems to work for examples I have tried.

@attack68
Copy link
Contributor

attack68 commented Sep 5, 2024

I think this is a good patch.
A PR is appreciated. Then we can check all existing tests and ensure compliance.

@invalidarg
Copy link
Contributor Author

invalidarg commented Sep 9, 2024

The patch still won't fix cases with semi-colons in the css value, e.g.

def maybe_convert_css_to_tuples(style: str) -> str:
    """
    Convert css-string to sequence of tuples format if needed.
    'color:red; border:1px solid black;' -> [('color', 'red'),
                                                ('border','1px solid red')]
    """
    if isinstance(style, str):
        s = style.split(";")
        try:
            return [
                (x.split(":")[0].strip(), ":".join(x.split(":")[1:]).strip())
                for x in s
                if ":".join(x.split(":")[1:]).strip() != ""
            ]
        except IndexError as err:
            raise ValueError(
                "Styles supplied as string must follow CSS rule formats, "
                f"for example 'attr: val;'. '{style}' was given."
            ) from err
    return style

css_str = 'background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAQAAACR313BAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfoBg8UEDHh089+AAABAUlEQVQY043POyiFAQDF8d93ySNi+HRTkrgmMimRnZJkVFaPMstCSpmIPEoGIpsMNslwM1yKIhkoeXYzWA0K12P4cFdnO+ffOXUCWRVoViWQlvIaRTk/KN+EFRXyhDpNyzjO9kJHFpX++UqHJrN42zjKjFnVLzQs7klNBNucypGQNqfbipQzzBuJ8KYB7JgFgWtL6LdMDE2S4totgC9fDsAnMYEKjxLe3INaCSeocxWNPwvV+1CCmC3v8hR5UB2NX2hy6dyGQXtSAj22rbmL2kOSYkKjZrSg17yu7Otc+9YV//lCfRp/ERkdptzYl1aiWoNdST8vf1WqVbmMW6de/E/fgBNApPnOcOEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjQtMDYtMTVUMjA6MTY6NDgrMDA6MDCqRcMDAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDI0LTA2LTE1VDIwOjE2OjQ4KzAwOjAw2xh7vwAAAABJRU5ErkJggg==")'
tuple_list = maybe_convert_css_to_tuples(css_str)
print("tuple_list=",tuple_list)

The resulting tuple is truncated at the ;:
tuple_list= [('background-image', 'url("data:image/png')]

Something like #48869 would be needed to fix that.

@attack68
Copy link
Contributor

attack68 commented Sep 9, 2024

Good point, and this may well be the issue to revive it.

mroeschke added a commit that referenced this issue Oct 4, 2024
* second item in tuple is no longer truncated at first colon

#59623

* added testcase for maybe_convert_css_to_tuples

#59623

* maybe_convert_css_to_tuples() raises on strings without ":"

* fixed implicit str concatination

* Fixed raise on empty string

* Update test_style.py

* attr:; -> ("attr","")

Same behavior as before patch

* add test for "attr:;", ie empty value

* str concatenation in the test broke mypy

* revert explicit str concat

* Invalidarg patch black (#1)

* black test_style

* Update style_render.py

---------

Co-authored-by: Matthew Roeschke <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Styler conditional formatting using DataFrame.style
Projects
None yet
Development

No branches or pull requests

3 participants