@@ -116,8 +116,8 @@ class CSSToExcelConverter:
116116 focusing on font styling, backgrounds, borders and alignment.
117117
118118 Operates by first computing CSS styles in a fairly generic
119- way (see :meth:`compute_css`) then determining Excel style
120- properties from CSS properties (see :meth:`build_xlstyle`).
119+ way (see :meth: `compute_css`) then determining Excel style
120+ properties from CSS properties (see :meth: `build_xlstyle`).
121121
122122 Parameters
123123 ----------
@@ -587,14 +587,15 @@ def __init__(
587587
588588 def _format_value (self , val ):
589589 if is_scalar (val ) and missing .isna (val ):
590- val = self .na_rep
590+ return self .na_rep
591591 elif is_float (val ):
592592 if missing .isposinf_scalar (val ):
593- val = self .inf_rep
593+ return self .inf_rep
594594 elif missing .isneginf_scalar (val ):
595- val = f"-{ self .inf_rep } "
595+ return f"-{ self .inf_rep } "
596596 elif self .float_format is not None :
597- val = float (self .float_format % val )
597+ return float (self .float_format % val )
598+
598599 if getattr (val , "tzinfo" , None ) is not None :
599600 raise ValueError (
600601 "Excel does not support datetimes with "
@@ -616,15 +617,25 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
616617
617618 columns = self .columns
618619 merge_columns = self .merge_cells in {True , "columns" }
619- level_strs = columns ._format_multi (sparsify = merge_columns , include_names = False )
620+ NBSP = "\u00a0 "
621+
622+ fixed_levels = []
623+ for lvl in range (columns .nlevels ):
624+ vals = columns .get_level_values (lvl )
625+ fixed_levels .append (vals .fillna (NBSP ))
626+ fixed_columns = MultiIndex .from_arrays (fixed_levels , names = columns .names )
627+
628+ level_strs = fixed_columns ._format_multi (
629+ sparsify = merge_columns , include_names = False
630+ )
620631 level_lengths = get_level_lengths (level_strs )
621632 coloffset = 0
622633 lnum = 0
623634
624635 if self .index and isinstance (self .df .index , MultiIndex ):
625636 coloffset = self .df .index .nlevels - 1
626637
627- for lnum , name in enumerate (columns .names ):
638+ for lnum , name in enumerate (fixed_columns .names ):
628639 yield ExcelCell (
629640 row = lnum ,
630641 col = coloffset ,
@@ -633,7 +644,7 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
633644 )
634645
635646 for lnum , (spans , levels , level_codes ) in enumerate (
636- zip (level_lengths , columns .levels , columns .codes , strict = True )
647+ zip (level_lengths , fixed_columns .levels , fixed_columns .codes , strict = True )
637648 ):
638649 values = levels .take (level_codes )
639650 for i , span_val in spans .items ():
@@ -657,7 +668,6 @@ def _format_header_mi(self) -> Iterable[ExcelCell]:
657668 def _format_header_regular (self ) -> Iterable [ExcelCell ]:
658669 if self ._has_aliases or self .header :
659670 coloffset = 0
660-
661671 if self .index :
662672 coloffset = 1
663673 if isinstance (self .df .index , MultiIndex ):
@@ -673,7 +683,10 @@ def _format_header_regular(self) -> Iterable[ExcelCell]:
673683 )
674684 colnames = self .header
675685
676- for colindex , colname in enumerate (colnames ):
686+ NBSP = "\u00a0 "
687+ output_colnames = colnames .fillna (NBSP )
688+
689+ for colindex , colname in enumerate (output_colnames ):
677690 yield CssExcelCell (
678691 row = self .rowcounter ,
679692 col = colindex + coloffset ,
@@ -687,15 +700,14 @@ def _format_header_regular(self) -> Iterable[ExcelCell]:
687700
688701 def _format_header (self ) -> Iterable [ExcelCell ]:
689702 gen : Iterable [ExcelCell ]
690-
691703 if isinstance (self .columns , MultiIndex ):
692704 gen = self ._format_header_mi ()
693705 else :
694706 gen = self ._format_header_regular ()
695707
696708 gen2 : Iterable [ExcelCell ] = ()
697709
698- if self .df .index .names :
710+ if self .df .index .names and self . header is not False :
699711 row = [x if x is not None else "" for x in self .df .index .names ] + [
700712 ""
701713 ] * len (self .columns )
@@ -762,35 +774,25 @@ def _format_regular_rows(self) -> Iterable[ExcelCell]:
762774 def _format_hierarchical_rows (self ) -> Iterable [ExcelCell ]:
763775 if self ._has_aliases or self .header :
764776 self .rowcounter += 1
765-
766777 gcolidx = 0
767-
768778 if self .index :
769779 index_labels = self .df .index .names
770- # check for aliases
771780 if self .index_label and isinstance (
772781 self .index_label , (list , tuple , np .ndarray , Index )
773782 ):
774783 index_labels = self .index_label
775-
776- # MultiIndex columns require an extra row
777- # with index names (blank if None) for
778- # unambiguous round-trip, Issue #11328
779784 if isinstance (self .columns , MultiIndex ):
780785 self .rowcounter += 1
781-
782- # if index labels are not empty go ahead and dump
783786 if com .any_not_none (* index_labels ) and self .header is not False :
784787 for cidx , name in enumerate (index_labels ):
785788 yield ExcelCell (self .rowcounter - 1 , cidx , name , None )
786789
787790 if self .merge_cells and self .merge_cells != "columns" :
788- # Format hierarchical rows as merged cells.
791+ # Cette partie (pour merge_cells=True) est correcte
789792 level_strs = self .df .index ._format_multi (
790793 sparsify = True , include_names = False
791794 )
792795 level_lengths = get_level_lengths (level_strs )
793-
794796 for spans , levels , level_codes in zip (
795797 level_lengths ,
796798 self .df .index .levels ,
@@ -802,10 +804,8 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
802804 allow_fill = levels ._can_hold_na ,
803805 fill_value = levels ._na_value ,
804806 )
805- # GH#60099
806- if isinstance (values [0 ], Period ):
807+ if values .size > 0 and isinstance (values [0 ], Period ):
807808 values = values .to_timestamp ()
808-
809809 for i , span_val in spans .items ():
810810 mergestart , mergeend = None , None
811811 if span_val > 1 :
@@ -824,15 +824,11 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
824824 mergeend = mergeend ,
825825 )
826826 gcolidx += 1
827-
828827 else :
829- # Format hierarchical rows with non-merged values.
830828 for indexcolvals in zip (* self .df .index , strict = True ):
831829 for idx , indexcolval in enumerate (indexcolvals ):
832- # GH#60099
833830 if isinstance (indexcolval , Period ):
834831 indexcolval = indexcolval .to_timestamp ()
835-
836832 yield CssExcelCell (
837833 row = self .rowcounter + idx ,
838834 col = gcolidx ,
@@ -844,7 +840,6 @@ def _format_hierarchical_rows(self) -> Iterable[ExcelCell]:
844840 css_converter = self .style_converter ,
845841 )
846842 gcolidx += 1
847-
848843 yield from self ._generate_body (gcolidx )
849844
850845 @property
@@ -901,9 +896,7 @@ def write(
901896 write engine to use if writer is a path - you can also set this
902897 via the options ``io.excel.xlsx.writer``,
903898 or ``io.excel.xlsm.writer``.
904-
905899 {storage_options}
906-
907900 engine_kwargs: dict, optional
908901 Arbitrary keyword arguments passed to excel engine.
909902 """
0 commit comments