diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index ecfe3de10c829..a806334e5bed1 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -71,7 +71,11 @@ class StylerRenderer: Base class to process rendering a Styler with a specified jinja2 template. """ - loader = jinja2.PackageLoader("pandas", "io/formats/templates") + import os + + loader = jinja2.FileSystemLoader( + os.path.join(os.path.dirname(__file__), "templates") + ) env = jinja2.Environment(loader=loader, trim_blocks=True) template_html = env.get_template("html.tpl") template_html_table = env.get_template("html_table.tpl") @@ -834,10 +838,7 @@ def _generate_body_row( data_element = _element( "td", - ( - f"{self.css['data']} {self.css['row']}{r} " - f"{self.css['col']}{c}{cls}" - ), + (f"{self.css['data']} {self.css['row']}{r} {self.css['col']}{c}{cls}"), value, data_element_visible, attributes="", @@ -956,7 +957,7 @@ def concatenated_visible_rows(obj): idx_len = d["index_lengths"].get((lvl, r), None) if idx_len is not None: # i.e. not a sparsified entry d["clines"][rn + idx_len].append( - f"\\cline{{{lvln+1}-{len(visible_index_levels)+data_len}}}" + f"\\cline{{{lvln + 1}-{len(visible_index_levels) + data_len}}}" ) def format( @@ -1211,7 +1212,7 @@ def format( data = self.data.loc[subset] if not isinstance(formatter, dict): - formatter = {col: formatter for col in data.columns} + formatter = dict.fromkeys(data.columns, formatter) cis = self.columns.get_indexer_for(data.columns) ris = self.index.get_indexer_for(data.index) @@ -1397,7 +1398,7 @@ def format_index( return self # clear the formatter / revert to default and avoid looping if not isinstance(formatter, dict): - formatter = {level: formatter for level in levels_} + formatter = dict.fromkeys(levels_, formatter) else: formatter = { obj._get_level_number(level): formatter_ @@ -1540,7 +1541,7 @@ def relabel_index( >>> df = pd.DataFrame({"samples": np.random.rand(10)}) >>> styler = df.loc[np.random.randint(0, 10, 3)].style - >>> styler.relabel_index([f"sample{i+1} ({{}})" for i in range(3)]) + >>> styler.relabel_index([f"sample{i + 1} ({{}})" for i in range(3)]) ... # doctest: +SKIP samples sample1 (5) 0.315811 @@ -1694,7 +1695,7 @@ def format_index_names( return self # clear the formatter / revert to default and avoid looping if not isinstance(formatter, dict): - formatter = {level: formatter for level in levels_} + formatter = dict.fromkeys(levels_, formatter) else: formatter = { obj._get_level_number(level): formatter_ @@ -2384,21 +2385,28 @@ def _parse_latex_cell_styles( """ if convert_css: latex_styles = _parse_latex_css_conversion(latex_styles) - for command, options in latex_styles[::-1]: # in reverse for most recent style - formatter = { - "--wrap": f"{{\\{command}--to_parse {display_value}}}", - "--nowrap": f"\\{command}--to_parse {display_value}", - "--lwrap": f"{{\\{command}--to_parse}} {display_value}", - "--rwrap": f"\\{command}--to_parse{{{display_value}}}", - "--dwrap": f"{{\\{command}--to_parse}}{{{display_value}}}", - } - display_value = f"\\{command}{options} {display_value}" + for command, options in reversed(latex_styles): + options_str = options if isinstance(options, str) else str(options) + found_arg = None for arg in ["--nowrap", "--wrap", "--lwrap", "--rwrap", "--dwrap"]: - if arg in str(options): - display_value = formatter[arg].replace( - "--to_parse", _parse_latex_options_strip(value=options, arg=arg) - ) + if arg in options_str: + found_arg = arg break # only ever one purposeful entry + + if found_arg is None: + display_value = f"\\{command}{options} {display_value}" + else: + parsed_opt = _parse_latex_options_strip(value=options, arg=found_arg) + if found_arg == "--wrap": + display_value = f"{{\\{command}{parsed_opt} {display_value}}}" + elif found_arg == "--nowrap": + display_value = f"\\{command}{parsed_opt} {display_value}" + elif found_arg == "--lwrap": + display_value = f"{{\\{command}{parsed_opt}}} {display_value}" + elif found_arg == "--rwrap": + display_value = f"\\{command}{parsed_opt}{{{display_value}}}" + elif found_arg == "--dwrap": + display_value = f"{{\\{command}{parsed_opt}}}{{{display_value}}}" return display_value @@ -2409,7 +2417,7 @@ def _parse_latex_header_span( wrap: bool = False, convert_css: bool = False, ) -> str: - r""" + """ Refactor the cell `display_value` if a 'colspan' or 'rowspan' attribute is present. 'rowspan' and 'colspan' do not occur simultaneously. If they are detected then @@ -2431,25 +2439,30 @@ def _parse_latex_header_span( display_val = _parse_latex_cell_styles( cell["cellstyle"], cell["display_value"], convert_css ) - if "attributes" in cell: - attrs = cell["attributes"] - if 'colspan="' in attrs: - colspan = attrs[attrs.find('colspan="') + 9 :] # len('colspan="') = 9 - colspan = int(colspan[: colspan.find('"')]) - if "naive-l" == multicol_align: - out = f"{{{display_val}}}" if wrap else f"{display_val}" + attrs = cell.get("attributes") + if attrs: + # Use find only once and cache results + col_idx = attrs.find('colspan="') + if col_idx != -1: + start = col_idx + 9 + end = attrs.find('"', start) + colspan = int(attrs[start:end]) + if multicol_align == "naive-l": + out = f"{{{display_val}}}" if wrap else display_val blanks = " & {}" if wrap else " &" return out + blanks * (colspan - 1) - elif "naive-r" == multicol_align: - out = f"{{{display_val}}}" if wrap else f"{display_val}" + elif multicol_align == "naive-r": + out = f"{{{display_val}}}" if wrap else display_val blanks = "{} & " if wrap else "& " return blanks * (colspan - 1) + out return f"\\multicolumn{{{colspan}}}{{{multicol_align}}}{{{display_val}}}" - elif 'rowspan="' in attrs: + row_idx = attrs.find('rowspan="') + if row_idx != -1: if multirow_align == "naive": return display_val - rowspan = attrs[attrs.find('rowspan="') + 9 :] - rowspan = int(rowspan[: rowspan.find('"')]) + start = row_idx + 9 + end = attrs.find('"', start) + rowspan = int(attrs[start:end]) return f"\\multirow[{multirow_align}]{{{rowspan}}}{{*}}{{{display_val}}}" if wrap: return f"{{{display_val}}}" @@ -2503,7 +2516,7 @@ def color(value, user_arg, command, comm_arg): if value[0] == "#" and len(value) == 7: # color is hex code return command, f"[HTML]{{{value[1:].upper()}}}{arg}" if value[0] == "#" and len(value) == 4: # color is short hex code - val = f"{value[1].upper()*2}{value[2].upper()*2}{value[3].upper()*2}" + val = f"{value[1].upper() * 2}{value[2].upper() * 2}{value[3].upper() * 2}" return command, f"[HTML]{{{val}}}{arg}" elif value[:3] == "rgb": # color is rgb or rgba r = re.findall("(?<=\\()[0-9\\s%]+(?=,)", value)[0].strip()