@@ -56,18 +56,53 @@ def _flatten_nested_data(
5656 return dataframe .copy (), {}, [], set ()
5757
5858 result_df = _try_parse_json_strings (dataframe )
59- initial_columns = list (result_df .columns )
6059
61- array_row_groups : dict [str , list [int ]] = {}
62- nested_originated_columns : set [str ] = set ()
60+ (
61+ struct_columns ,
62+ array_columns ,
63+ array_of_struct_columns ,
64+ clear_on_continuation_cols ,
65+ nested_originated_columns ,
66+ ) = _classify_columns (result_df )
67+
68+ result_df , array_columns = _flatten_array_of_struct_columns (
69+ result_df , array_of_struct_columns , array_columns , nested_originated_columns
70+ )
6371
64- # First, identify all STRUCT and ARRAY columns
72+ result_df , clear_on_continuation_cols = _flatten_struct_columns (
73+ result_df , struct_columns , clear_on_continuation_cols , nested_originated_columns
74+ )
75+
76+ # Now handle ARRAY columns (including the newly created ones from ARRAY of STRUCT)
77+ if not array_columns :
78+ return (
79+ result_df ,
80+ {},
81+ clear_on_continuation_cols ,
82+ nested_originated_columns ,
83+ )
84+
85+ result_df , array_row_groups = _explode_array_columns (result_df , array_columns )
86+ return (
87+ result_df ,
88+ array_row_groups ,
89+ clear_on_continuation_cols ,
90+ nested_originated_columns ,
91+ )
92+
93+
94+ def _classify_columns (
95+ dataframe : pd .DataFrame ,
96+ ) -> tuple [list [str ], list [str ], list [str ], list [str ], set [str ]]:
97+ """Identify all STRUCT and ARRAY columns."""
98+ initial_columns = list (dataframe .columns )
6599 struct_columns : list [str ] = []
66100 array_columns : list [str ] = []
67101 array_of_struct_columns : list [str ] = []
68102 clear_on_continuation_cols : list [str ] = []
103+ nested_originated_columns : set [str ] = set ()
69104
70- for col_name_raw , col_data in result_df .items ():
105+ for col_name_raw , col_data in dataframe .items ():
71106 col_name = str (col_name_raw )
72107 dtype = col_data .dtype
73108 if isinstance (dtype , pd .ArrowDtype ):
@@ -86,28 +121,10 @@ def _flatten_nested_data(
86121 clear_on_continuation_cols .append (col_name )
87122 elif col_name in initial_columns :
88123 clear_on_continuation_cols .append (col_name )
89-
90- result_df , array_columns = _flatten_array_of_struct_columns (
91- result_df , array_of_struct_columns , array_columns , nested_originated_columns
92- )
93-
94- result_df , clear_on_continuation_cols = _flatten_struct_columns (
95- result_df , struct_columns , clear_on_continuation_cols , nested_originated_columns
96- )
97-
98- # Now handle ARRAY columns (including the newly created ones from ARRAY of STRUCT)
99- if not array_columns :
100- return (
101- result_df ,
102- array_row_groups ,
103- clear_on_continuation_cols ,
104- nested_originated_columns ,
105- )
106-
107- result_df , array_row_groups = _explode_array_columns (result_df , array_columns )
108124 return (
109- result_df ,
110- array_row_groups ,
125+ struct_columns ,
126+ array_columns ,
127+ array_of_struct_columns ,
111128 clear_on_continuation_cols ,
112129 nested_originated_columns ,
113130 )
@@ -292,25 +309,43 @@ def render_html(
292309 ) = _flatten_nested_data (dataframe )
293310
294311 classes = "dataframe table table-striped table-hover"
295- table_html_parts = []
296- precision = options .display .precision
312+ table_html_parts = [f'<table border="1" class="{ classes } " id="{ table_id } ">\n ' ]
313+ table_html_parts .append (_render_table_header (flattened_df ))
314+ table_html_parts .append (
315+ _render_table_body (
316+ flattened_df ,
317+ array_row_groups ,
318+ clear_on_continuation ,
319+ nested_originated_columns ,
320+ )
321+ )
322+ table_html_parts .append ("</table>" )
323+ return "" .join (table_html_parts )
297324
298- table_html_parts .append (f'<table border="1" class="{ classes } " id="{ table_id } ">\n ' )
299325
300- # Render table head
301- table_html_parts . append ( " <thead> \n " )
302- table_html_parts . append ( " <tr>\n " )
303- for col in flattened_df .columns :
304- table_html_parts .append (
326+ def _render_table_header ( dataframe : pd . DataFrame ) -> str :
327+ """Render the header of the HTML table."""
328+ header_parts = [ " <thead> \n " , " <tr>\n "]
329+ for col in dataframe .columns :
330+ header_parts .append (
305331 f' <th><div class="bf-header-content">'
306332 f"{ html .escape (str (col ))} </div></th>\n "
307333 )
308- table_html_parts . append ( " </tr>\n " )
309- table_html_parts . append ( " </thead> \n " )
334+ header_parts . extend ([ " </tr>\n " , " </thead> \n " ] )
335+ return "" . join ( header_parts )
310336
311- # Render table body
312- table_html_parts .append (" <tbody>\n " )
313- for i in range (len (flattened_df )):
337+
338+ def _render_table_body (
339+ dataframe : pd .DataFrame ,
340+ array_row_groups : dict [str , list [int ]],
341+ clear_on_continuation : list [str ],
342+ nested_originated_columns : set [str ],
343+ ) -> str :
344+ """Render the body of the HTML table."""
345+ body_parts = [" <tbody>\n " ]
346+ precision = options .display .precision
347+
348+ for i in range (len (dataframe )):
314349 row_class = ""
315350 orig_row_idx = None
316351 is_continuation = False
@@ -322,21 +357,20 @@ def render_html(
322357 break
323358
324359 if row_class :
325- table_html_parts .append (
360+ body_parts .append (
326361 f' <tr class="{ row_class } " data-orig-row="{ orig_row_idx } ">\n '
327362 )
328363 else :
329- table_html_parts .append (" <tr>\n " )
364+ body_parts .append (" <tr>\n " )
330365
331- row = flattened_df .iloc [i ]
366+ row = dataframe .iloc [i ]
332367 for col_name , value in row .items ():
333368 col_name_str = str (col_name )
334369 if is_continuation and col_name_str in clear_on_continuation :
335- table_html_parts .append (" <td></td>\n " )
370+ body_parts .append (" <td></td>\n " )
336371 continue
337- dtype = flattened_df .dtypes .loc [col_name ] # type: ignore
372+ dtype = dataframe .dtypes .loc [col_name ] # type: ignore
338373
339- # Check if column originated from an array
340374 if col_name_str in nested_originated_columns :
341375 align = "left"
342376 else :
@@ -345,19 +379,17 @@ def render_html(
345379 cell_content = ""
346380 if pandas .api .types .is_scalar (value ) and pd .isna (value ):
347381 cell_content = ""
348- align = "left" # Force left alignment for empty cells (NA)
382+ align = "left"
349383 elif isinstance (value , float ):
350384 cell_content = f"{ value :.{precision }f} "
351385 else :
352386 cell_content = str (value )
353387
354388 align_class = f"cell-align-{ align } "
355- table_html_parts .append (
389+ body_parts .append (
356390 f' <td class="{ align_class } ">'
357391 f"{ html .escape (cell_content )} </td>\n "
358392 )
359- table_html_parts .append (" </tr>\n " )
360- table_html_parts .append (" </tbody>\n " )
361- table_html_parts .append ("</table>" )
362-
363- return "" .join (table_html_parts )
393+ body_parts .append (" </tr>\n " )
394+ body_parts .append (" </tbody>\n " )
395+ return "" .join (body_parts )
0 commit comments