Skip to content

Commit

Permalink
feat: CSS inlining in as_raw_html() (#557)
Browse files Browse the repository at this point in the history
* Add the inline_css= option to as_raw_html()

* Add snapshot test for CSS-inlined table

* Add snap for `inline_css=True, make_page=True`

* Move import statement outside of method body

* Add test for all_important= arg in as_raw_html()

* Change default of inline_css= from True to False
  • Loading branch information
rich-iannone authored Dec 11, 2024
1 parent ceef5c5 commit b32b55a
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 8 deletions.
101 changes: 93 additions & 8 deletions great_tables/_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
from pathlib import Path
from typing import TYPE_CHECKING, Literal

from css_inline import inline, inline_fragment
from typing_extensions import TypeAlias

from ._helpers import random_id
from ._scss import compile_scss
from ._utils import _try_import
from ._utils_render_latex import _render_as_latex

Expand Down Expand Up @@ -126,19 +129,29 @@ def show(

def as_raw_html(
self: GT,
inline_css: bool = False,
make_page: bool = False,
all_important: bool = False,
) -> str:
"""
Get the HTML content of a GT object.
Get the HTML content from a GT object as a string. This function is useful for obtaining the
HTML content of a GT object for use in other contexts.
Get the HTML content from a GT object as a string. By default, the generated HTML will have
inlined styles, where CSS styles (that were previously contained in CSS rule sets external to
the `<table>` element are included as style attributes in the HTML table's tags. This option is
preferable when using the output HTML table in an emailing context.
Parameters
----------
gt
A GT object.
inline_css
An option to supply styles to table elements as inlined CSS styles. This is useful when
including the table HTML as part of an HTML email message body, since inlined styles are
largely supported in email clients over using CSS in a `<style>` block.
make_page
An option to wrap the table in a complete HTML page. This is useful when you want to display
the table in a web browser.
Returns
-------
Expand All @@ -147,24 +160,96 @@ def as_raw_html(
Examples:
------
Let's use the `row` column of `exibble` dataset to create a table. With the `as_raw_html()`
method, we're able to output the HTML content.
Let's use a subset of the `gtcars` dataset to create a new table.
```{python}
from great_tables import GT, exibble
from great_tables import GT, md, style, loc
from great_tables.data import gtcars
import polars as pl
GT(exibble[["row"]]).as_raw_html()
gtcars_mini = (
pl.from_pandas(gtcars)
.select(["mfr", "model", "msrp"])
.head(5)
)
gt_tbl = (
GT(gtcars_mini)
.tab_header(
title=md("Data listing from **gtcars**"),
subtitle=md("gtcars is an R dataset")
)
.tab_style(
style=style.fill(color="LightCyan"),
locations=loc.body(columns="mfr")
)
.fmt_currency(columns="msrp")
.tab_options(
heading_background_color="Azure",
table_body_hlines_color="Lavender",
table_body_hlines_width="2px"
)
.opt_horizontal_padding(scale=2)
)
gt_tbl
```
Now we can return the table as an HTML string using the `as_raw_html()` method.
```{python}
gt_tbl.as_raw_html()
```
The HTML string contains the HTML for the table. It has only the table so it's not a complete
HTML document but rather an HTML fragment. While this useful for embedding a table in an
existing HTML document, you could also use the `make_page=True` argument to get a complete HTML
page with the table contained within.
```{python}
gt_tbl.as_raw_html(make_page=True)
```
Should you want to include all of the CSS styles as inline styles, you can use `inline_css=True`
to get an HTML string with all CSS inlined into the HTML tags.
```{python}
gt_tbl.as_raw_html(inline_css=True)
```
"""
built_table = self._build_data(context="html")

html_table = built_table._render_as_html(
if not inline_css:
html_table = built_table._render_as_html(
make_page=make_page,
all_important=all_important,
)

return html_table

table_html = built_table._render_as_html(
make_page=make_page,
all_important=all_important,
)

return html_table
if make_page:
inlined = inline(html=table_html)

else:
# Obtain the `table_id` value from the Options (might be set, might be None)
table_id = self._options.table_id.value

if table_id is None:
id = random_id()
else:
id = table_id

# Compile the SCSS as CSS
table_css = str(compile_scss(self, id=id, compress=False, all_important=all_important))

inlined = inline_fragment(html=table_html, css=table_css)

return inlined


def as_latex(self: GT, use_longtable: bool = False, tbl_pos: str | None = None) -> str:
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ classifiers = [
]
dependencies = [
"commonmark>=0.9.1",
"css-inline>=0.14.1",
"faicons>=0.2.2",
"htmltools>=0.4.1",
"importlib-metadata",
Expand Down
66 changes: 66 additions & 0 deletions tests/__snapshots__/test_export.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,72 @@

'''
# ---
# name: test_html_string_generated_inline_css
'''
<div id="test_table_small" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">

<table class="gt_table" data-quarto-bootstrap="false" data-quarto-disable-processing="false" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;display: table;border-collapse: collapse;line-height: normal;margin-left: auto;margin-right: auto;color: #333333;font-size: 16px;font-weight: normal;font-style: normal;background-color: #FFFFFF;width: auto;border-top-style: solid;border-top-width: 2px;border-top-color: #A8A8A8;border-right-style: none;border-right-width: 2px;border-right-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #A8A8A8;border-left-style: none;border-left-width: 2px;border-left-color: #D3D3D3;">
<thead style="border-style: none;">

<tr class="gt_col_headings" style="border-style: none;background-color: transparent;border-top-style: solid;border-top-width: 2px;border-top-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;">
<th class="gt_col_heading gt_columns_bottom_border gt_right" id="num" rowspan="1" colspan="1" scope="col" style="border-style: none;color: #333333;background-color: #FFFFFF;font-size: 100%;font-weight: normal;text-transform: inherit;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: bottom;padding-top: 5px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">num</th>
<th class="gt_col_heading gt_columns_bottom_border gt_left" id="char" rowspan="1" colspan="1" scope="col" style="border-style: none;color: #333333;background-color: #FFFFFF;font-size: 100%;font-weight: normal;text-transform: inherit;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: bottom;padding-top: 5px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;overflow-x: hidden;text-align: left;">char</th>
</tr>
</thead>
<tbody class="gt_table_body" style="border-style: none;border-top-style: solid;border-top-width: 2px;border-top-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #D3D3D3;">
<tr style="border-style: none;background-color: transparent;">
<td class="gt_row gt_right" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">0.11</td>
<td class="gt_row gt_left" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: left;">apricot</td>
</tr>
<tr style="border-style: none;background-color: transparent;">
<td class="gt_row gt_right" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">2.22</td>
<td class="gt_row gt_left" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: left;">banana</td>
</tr>
</tbody>


</table>

</div>
'''
# ---
# name: test_html_string_generated_inline_css_make_page
'''
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
</head>
<body>
<div id="test_table_small" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">

<table class="gt_table" data-quarto-bootstrap="false" data-quarto-disable-processing="false" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;display: table;border-collapse: collapse;line-height: normal;margin-left: auto;margin-right: auto;color: #333333;font-size: 16px;font-weight: normal;font-style: normal;background-color: #FFFFFF;width: auto;border-top-style: solid;border-top-width: 2px;border-top-color: #A8A8A8;border-right-style: none;border-right-width: 2px;border-right-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #A8A8A8;border-left-style: none;border-left-width: 2px;border-left-color: #D3D3D3;">
<thead style="border-style: none;">

<tr class="gt_col_headings" style="border-style: none;background-color: transparent;border-top-style: solid;border-top-width: 2px;border-top-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;">
<th class="gt_col_heading gt_columns_bottom_border gt_right" id="num" rowspan="1" colspan="1" scope="col" style="border-style: none;color: #333333;background-color: #FFFFFF;font-size: 100%;font-weight: normal;text-transform: inherit;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: bottom;padding-top: 5px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">num</th>
<th class="gt_col_heading gt_columns_bottom_border gt_left" id="char" rowspan="1" colspan="1" scope="col" style="border-style: none;color: #333333;background-color: #FFFFFF;font-size: 100%;font-weight: normal;text-transform: inherit;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: bottom;padding-top: 5px;padding-bottom: 5px;padding-left: 5px;padding-right: 5px;overflow-x: hidden;text-align: left;">char</th>
</tr>
</thead>
<tbody class="gt_table_body" style="border-style: none;border-top-style: solid;border-top-width: 2px;border-top-color: #D3D3D3;border-bottom-style: solid;border-bottom-width: 2px;border-bottom-color: #D3D3D3;">
<tr style="border-style: none;background-color: transparent;">
<td class="gt_row gt_right" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">0.11</td>
<td class="gt_row gt_left" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: left;">apricot</td>
</tr>
<tr style="border-style: none;background-color: transparent;">
<td class="gt_row gt_right" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: right;font-variant-numeric: tabular-nums;">2.22</td>
<td class="gt_row gt_left" style="border-style: none;padding-top: 8px;padding-bottom: 8px;padding-left: 5px;padding-right: 5px;margin: 10px;border-top-style: solid;border-top-width: 1px;border-top-color: #D3D3D3;border-left-style: none;border-left-width: 1px;border-left-color: #D3D3D3;border-right-style: none;border-right-width: 1px;border-right-color: #D3D3D3;vertical-align: middle;overflow-x: hidden;text-align: left;">banana</td>
</tr>
</tbody>


</table>

</div>



</body></html>
'''
# ---
# name: test_snap_as_latex
'''
\begingroup
Expand Down
23 changes: 23 additions & 0 deletions tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,33 @@ def gt_tbl():
return gt_tbl


@pytest.fixture
def gt_tbl_small():
gt_tbl_small = GT(
exibble[["num", "char"]].head(2),
id="test_table_small",
).fmt_number(columns="num")

return gt_tbl_small


def test_html_string_generated(gt_tbl: GT, snapshot: str):
assert snapshot == gt_tbl.as_raw_html()


def test_html_string_generated_inline_css(gt_tbl_small: GT, snapshot: str):
assert snapshot == gt_tbl_small.as_raw_html(inline_css=True)


def test_html_string_generated_inline_css_make_page(gt_tbl_small: GT, snapshot: str):
assert snapshot == gt_tbl_small.as_raw_html(inline_css=True, make_page=True)


def test_html_string_generated_all_important(gt_tbl_small: GT):
assert "!important;" in gt_tbl_small.as_raw_html(inline_css=False, all_important=True)
assert "!important;" in gt_tbl_small.as_raw_html(inline_css=True, all_important=True)


@pytest.mark.skipif(sys.platform == "win32", reason="chrome might not be installed.")
@pytest.mark.extra
def test_save_image_file(gt_tbl: GT, tmp_path):
Expand Down

0 comments on commit b32b55a

Please sign in to comment.