diff --git a/bigframes/display/html.py b/bigframes/display/html.py
index 3f1667eb9c..912f1d7e3a 100644
--- a/bigframes/display/html.py
+++ b/bigframes/display/html.py
@@ -48,60 +48,62 @@ def render_html(
orderable_columns: list[str] | None = None,
) -> str:
"""Render a pandas DataFrame to HTML with specific styling."""
- classes = "dataframe table table-striped table-hover"
- table_html = [f'
']
- precision = options.display.precision
orderable_columns = orderable_columns or []
+ classes = "dataframe table table-striped table-hover"
+ table_html_parts = [f'']
+ table_html_parts.append(_render_table_header(dataframe, orderable_columns))
+ table_html_parts.append(_render_table_body(dataframe))
+ table_html_parts.append("
")
+ return "".join(table_html_parts)
- # Render table head
- table_html.append(" ")
- table_html.append(' ')
+
+def _render_table_header(dataframe: pd.DataFrame, orderable_columns: list[str]) -> str:
+ """Render the header of the HTML table."""
+ header_parts = [" ", " "]
for col in dataframe.columns:
th_classes = []
if col in orderable_columns:
th_classes.append("sortable")
class_str = f'class="{" ".join(th_classes)}"' if th_classes else ""
- header_div = (
- ''
- f"{html.escape(str(col))}"
- "
"
- )
- table_html.append(
- f' {header_div} | '
+ header_parts.append(
+ f' | "
)
- table_html.append("
")
- table_html.append(" ")
+ header_parts.extend(["
", " "])
+ return "\n".join(header_parts)
+
+
+def _render_table_body(dataframe: pd.DataFrame) -> str:
+ """Render the body of the HTML table."""
+ body_parts = [" "]
+ precision = options.display.precision
- # Render table body
- table_html.append(" ")
for i in range(len(dataframe)):
- table_html.append(" ")
+ body_parts.append("
")
row = dataframe.iloc[i]
for col_name, value in row.items():
dtype = dataframe.dtypes.loc[col_name] # type: ignore
align = "right" if _is_dtype_numeric(dtype) else "left"
- table_html.append(
- ' | '.format(align)
- )
# TODO(b/438181139): Consider semi-exploding ARRAY/STRUCT columns
# into multiple rows/columns like the BQ UI does.
if pandas.api.types.is_scalar(value) and pd.isna(value):
- table_html.append(' <NA>')
+ body_parts.append(
+ f' | '
+ '<NA> | '
+ )
else:
if isinstance(value, float):
- formatted_value = f"{value:.{precision}f}"
- table_html.append(f" {html.escape(formatted_value)}")
+ cell_content = f"{value:.{precision}f}"
else:
- table_html.append(f" {html.escape(str(value))}")
- table_html.append(" ")
- table_html.append("
")
- table_html.append(" ")
- table_html.append("
")
-
- return "\n".join(table_html)
+ cell_content = str(value)
+ body_parts.append(
+ f' '
+ f"{html.escape(cell_content)} | "
+ )
+ body_parts.append(" ")
+ body_parts.append(" ")
+ return "\n".join(body_parts)
def _obj_ref_rt_to_html(obj_ref_rt: str) -> str:
diff --git a/tests/unit/display/test_html.py b/tests/unit/display/test_html.py
index fcf1455362..0762a2fd8d 100644
--- a/tests/unit/display/test_html.py
+++ b/tests/unit/display/test_html.py
@@ -130,9 +130,8 @@ def test_render_html_alignment_and_precision(
df = pd.DataFrame(data)
html = bf_html.render_html(dataframe=df, table_id="test-table")
- for _, align in expected_alignments.items():
- assert 'th style="text-align: left;"' in html
- assert f' |