From ba72f260b8153f64531e5f26f75d2936421500af Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Sat, 11 Mar 2023 11:22:45 +0100 Subject: [PATCH 1/7] ENH: add math mode with parentheses --- pandas/io/formats/style_render.py | 71 +++++++++++++++----- pandas/tests/io/formats/style/test_format.py | 19 ++++-- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 317ce93cf3da6..5c51a685a6687 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1117,7 +1117,8 @@ def format( 2 & \textbf{\$\%\#} \\ \end{tabular} - Using ``escape`` in 'latex-math' mode. + Applying ``escape`` in 'latex-math' mode. In the example below + we enter math mode using the charackter ``$``. >>> df = pd.DataFrame([[r"$\sum_{i=1}^{10} a_i$ a~b $\alpha \ ... = \frac{\beta}{\zeta^2}$"], ["%#^ $ \$x^2 $"]]) @@ -1129,6 +1130,20 @@ def format( 1 & \%\#\textasciicircum \space $ \$x^2 $ \\ \end{tabular} + We can use the charackter ``\(`` to enter math mode and the charackter ``\)`` + to close math mode. + + >>> df = pd.DataFrame([[r"\(\sum_{i=1}^{10} a_i\) a~b \(\alpha \ + ... = \frac{\beta}{\zeta^2}\)"], ["%#^ \( \$x^2 \)"]]) + >>> df.style.format(escape="latex-math").to_latex() + ... # doctest: +SKIP + \begin{tabular}{ll} + & 0 \\ + 0 & \(\sum_{i=1}^{10} a_i\) a\textasciitilde b \(\alpha + = \frac{\beta}{\zeta^2}\) \\ + 1 & \%\#\textasciicircum \space \( \$x^2 \) \\ + \end{tabular} + Pandas defines a `number-format` pseudo CSS attribute instead of the `.format` method to create `to_excel` permissible formatting. Note that semi-colons are CSS protected characters but used as separators in Excel's format string. @@ -2357,17 +2372,20 @@ def _escape_latex(s): .replace("~", "\\textasciitilde ") .replace("^ ", "^\\space ") # since \textasciicircum gobbles spaces .replace("^", "\\textasciicircum ") + .replace("ab2§=§8yz(", "\\( ") + .replace("ab2§=§8yz)", "\\) ") .replace("ab2§=§8yz", "\\textbackslash ") ) def _escape_latex_math(s): r""" - All characters between two characters ``$`` are preserved. + All characters in LaTeX math mode are preserved. - The substrings in LaTeX math mode, which start with the character ``$`` - and end with ``$``, are preserved without escaping. Otherwise - regular LaTeX escaping applies. See ``_escape_latex()``. + The substrings in LaTeX math mode, which either are surrounded + by two characters ``$`` or start with the character ``\(`` and end with ``\)``, + are preserved without escaping. Otherwise regular LaTeX escaping applies. + See ``_escape_latex()``. Parameters ---------- @@ -2379,16 +2397,37 @@ def _escape_latex_math(s): str : Escaped string """ - s = s.replace(r"\$", r"rt8§=§7wz") - pattern = re.compile(r"\$.*?\$") - pos = 0 - ps = pattern.search(s, pos) - res = [] - while ps: - res.append(_escape_latex(s[pos : ps.span()[0]])) - res.append(ps.group()) - pos = ps.span()[1] + + def _math_mode_with_dollar(s): + s = s.replace(r"\$", r"rt8§=§7wz") + pattern = re.compile(r"\$.*?\$") + pos = 0 ps = pattern.search(s, pos) + res = [] + while ps: + res.append(_escape_latex(s[pos : ps.span()[0]])) + res.append(ps.group()) + pos = ps.span()[1] + ps = pattern.search(s, pos) + + res.append(_escape_latex(s[pos : len(s)])) + return "".join(res).replace(r"rt8§=§7wz", r"\$") + + def _math_mode_with_parentheses(s): + s = s.replace(r"\(", r"LEFT§=§6yzLEFT").replace(r"\)", r"RIGHTab5§=§RIGHT") + res = [] + for item in re.split(r"LEFT§=§6yz|ab5§=§RIGHT", s): + if item.startswith("LEFT") and item.endswith("RIGHT"): + res.append(item.replace("LEFT", r"\(").replace("RIGHT", r"\)")) + else: + res.append( + _escape_latex(item).replace("LEFT", r"\(").replace("RIGHT", r"\)") + ) + return "".join(res) - res.append(_escape_latex(s[pos : len(s)])) - return "".join(res).replace(r"rt8§=§7wz", r"\$") + if s.replace(r"\$", "ab").find(r"$") > -1: + return _math_mode_with_dollar(s) + elif s.find(r"\(") > -1: + return _math_mode_with_parentheses(s) + else: + return _escape_latex(s) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 0dec614970467..824469a0ddb39 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -192,13 +192,22 @@ def test_format_escape_html(escape, exp): assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{exp}&" -def test_format_escape_latex_math(): - chars = r"$\frac{1}{2} \$ x^2$ ~%#^" - df = DataFrame([[chars]]) +@pytest.mark.parametrize( + "chars, expected", + [ + (r"$\frac{1}{2} \$ x^2$ ", r"$\frac{1}{2} \$ x^2$ "), + (r"\(\frac{1}{2} \$ x^2\) ", r"\(\frac{1}{2} \$ x^2\) "), + (r"\)", r"\) "), + ], +) +def test_format_escape_latex_math(chars, expected): + df = DataFrame([["".join([chars, "~%#^"])]]) - expected = r"$\frac{1}{2} \$ x^2$ \textasciitilde \%\#\textasciicircum " s = df.style.format("{0}", escape="latex-math") - assert expected == s._translate(True, True)["body"][0][1]["display_value"] + assert ( + "".join([expected, r"\textasciitilde \%\#\textasciicircum "]) + == s._translate(True, True)["body"][0][1]["display_value"] + ) def test_format_escape_na_rep(): From 2320fb3f3690d00601d936c68aa14ef16b1e7582 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Tue, 14 Mar 2023 23:09:35 +0100 Subject: [PATCH 2/7] ENH: add math mode with parentheses II --- pandas/io/formats/style_render.py | 40 +++++++++++------ pandas/tests/io/formats/style/test_format.py | 45 +++++++++++++++----- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 5c51a685a6687..b6274811b9c9e 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1118,7 +1118,7 @@ def format( \end{tabular} Applying ``escape`` in 'latex-math' mode. In the example below - we enter math mode using the charackter ``$``. + we enter math mode using the character ``$``. >>> df = pd.DataFrame([[r"$\sum_{i=1}^{10} a_i$ a~b $\alpha \ ... = \frac{\beta}{\zeta^2}$"], ["%#^ $ \$x^2 $"]]) @@ -1130,7 +1130,7 @@ def format( 1 & \%\#\textasciicircum \space $ \$x^2 $ \\ \end{tabular} - We can use the charackter ``\(`` to enter math mode and the charackter ``\)`` + We can use the character ``\(`` to enter math mode and the character ``\)`` to close math mode. >>> df = pd.DataFrame([[r"\(\sum_{i=1}^{10} a_i\) a~b \(\alpha \ @@ -2359,7 +2359,8 @@ def _escape_latex(s): Escaped string """ return ( - s.replace("\\", "ab2§=§8yz") # rare string for final conversion: avoid \\ clash + s.replace("\\ ", "ab2§=§8yz") + .replace("\\", "ab2§=§8yz") # rare string for final conversion: avoid \\ clash .replace("ab2§=§8yz ", "ab2§=§8yz\\space ") # since \backslash gobbles spaces .replace("&", "\\&") .replace("%", "\\%") @@ -2372,8 +2373,6 @@ def _escape_latex(s): .replace("~", "\\textasciitilde ") .replace("^ ", "^\\space ") # since \textasciicircum gobbles spaces .replace("^", "\\textasciicircum ") - .replace("ab2§=§8yz(", "\\( ") - .replace("ab2§=§8yz)", "\\) ") .replace("ab2§=§8yz", "\\textbackslash ") ) @@ -2385,7 +2384,6 @@ def _escape_latex_math(s): The substrings in LaTeX math mode, which either are surrounded by two characters ``$`` or start with the character ``\(`` and end with ``\)``, are preserved without escaping. Otherwise regular LaTeX escaping applies. - See ``_escape_latex()``. Parameters ---------- @@ -2419,15 +2417,33 @@ def _math_mode_with_parentheses(s): for item in re.split(r"LEFT§=§6yz|ab5§=§RIGHT", s): if item.startswith("LEFT") and item.endswith("RIGHT"): res.append(item.replace("LEFT", r"\(").replace("RIGHT", r"\)")) - else: + elif "LEFT" in item and "RIGHT" in item: res.append( _escape_latex(item).replace("LEFT", r"\(").replace("RIGHT", r"\)") ) + else: + res.append( + _escape_latex(item) + .replace("LEFT", r"\textbackslash (") + .replace("RIGHT", r"\textbackslash )") + ) return "".join(res) - if s.replace(r"\$", "ab").find(r"$") > -1: - return _math_mode_with_dollar(s) - elif s.find(r"\(") > -1: - return _math_mode_with_parentheses(s) + s = s.replace(r"\$", r"rt8§=§7wz") + pattern_d = re.compile(r"\$.*?\$") + pattern_p = re.compile(r"\\(.*?\\)") + pos_d = 0 + pos_p = 0 + ps_d = pattern_d.search(s, pos_d) + ps_p = pattern_p.search(s, pos_p) + mode = [] + if ps_d: + mode.append(ps_d.span()[0]) + if ps_p: + mode.append(ps_p.span()[0]) + if len(mode) == 0: + return _escape_latex(s.replace(r"\$", r"rt8§=§7wz")) + if s[min(mode)] == r"$": + return _math_mode_with_dollar(s.replace(r"\$", r"rt8§=§7wz")) else: - return _escape_latex(s) + return _math_mode_with_parentheses(s.replace(r"\$", r"rt8§=§7wz")) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 824469a0ddb39..0c5024f425978 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -167,7 +167,7 @@ def test_format_clear(styler, func, attr, kwargs): "latex", '<>\\&"\\%\\$\\#\\_\\{\\}\\textasciitilde \\textasciicircum ' "\\textbackslash \\textasciitilde \\space \\textasciicircum \\space " - "\\textbackslash \\space ", + "\\textbackslash ", ), ], ) @@ -195,19 +195,44 @@ def test_format_escape_html(escape, exp): @pytest.mark.parametrize( "chars, expected", [ - (r"$\frac{1}{2} \$ x^2$ ", r"$\frac{1}{2} \$ x^2$ "), - (r"\(\frac{1}{2} \$ x^2\) ", r"\(\frac{1}{2} \$ x^2\) "), - (r"\)", r"\) "), + ( + r"$ \$&%#_{}~^\ $ &%#_{}~^\ $", + "".join( + [ + r"$ \$&%#_{}~^\ $ ", + r"\&\%\#\_\{\}\textasciitilde \textasciicircum \textbackslash \$", + ] + ), + ), + ( + r"\( &%#_{}~^\ \) &%#_{}~^\ \(", + "".join( + [ + r"\( &%#_{}~^\ \) ", + r"\&\%\#\_\{\}\textasciitilde \textasciicircum ", + r"\textbackslash \textbackslash (", + ] + ), + ), + ( + r"$ \frac{1}{2} $ \( \frac{1}{2} \)", + "".join( + [ + r"$ \frac{1}{2} $", + r" \textbackslash ( \textbackslash frac\{1\}\{2\} \textbackslash )", + ] + ), + ), ], ) def test_format_escape_latex_math(chars, expected): - df = DataFrame([["".join([chars, "~%#^"])]]) - + # GH 51903 + # latex-math escape works for each DataFrame cell separately. + # If we have a combination of dollar signs and brackets, + # the sign which occurs first would apply. + df = DataFrame([[chars]]) s = df.style.format("{0}", escape="latex-math") - assert ( - "".join([expected, r"\textasciitilde \%\#\textasciicircum "]) - == s._translate(True, True)["body"][0][1]["display_value"] - ) + assert s._translate(True, True)["body"][0][1]["display_value"] == expected def test_format_escape_na_rep(): From 29e357c8eb420006315255d4126731a12616b112 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Sun, 19 Mar 2023 23:55:08 +0100 Subject: [PATCH 3/7] ENH: add math mode with parentheses III --- pandas/io/formats/style_render.py | 119 ++++++++++++------- pandas/tests/io/formats/style/test_format.py | 7 +- 2 files changed, 82 insertions(+), 44 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index b6274811b9c9e..1bf0f837046bd 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -988,8 +988,9 @@ def format( ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with LaTeX-safe sequences. Use 'latex-math' to replace the characters the same way as in 'latex' mode, - except for math substrings, which start and end with ``$``. - Escaping is done before ``formatter``. + except for math substrings, which either are surrounded + by two characters ``$`` or start with the character ``\(`` and + end with ``\)``. Escaping is done before ``formatter``. .. versionadded:: 1.3.0 @@ -2359,8 +2360,7 @@ def _escape_latex(s): Escaped string """ return ( - s.replace("\\ ", "ab2§=§8yz") - .replace("\\", "ab2§=§8yz") # rare string for final conversion: avoid \\ clash + s.replace("\\", "ab2§=§8yz") # rare string for final conversion: avoid \\ clash .replace("ab2§=§8yz ", "ab2§=§8yz\\space ") # since \backslash gobbles spaces .replace("&", "\\&") .replace("%", "\\%") @@ -2377,6 +2377,75 @@ def _escape_latex(s): ) +def _math_mode_with_dollar(s): + r""" + All characters in LaTeX math mode are preserved. + + The substrings in LaTeX math mode, which start with + the character ``$`` and end with ``$``, are preserved + without escaping. Otherwise regular LaTeX escaping applies. + + Parameters + ---------- + s : str + Input to be escaped + + Return + ------ + str : + Escaped string + """ + s = s.replace(r"\$", r"rt8§=§7wz") + pattern = re.compile(r"\$.*?\$") + pos = 0 + ps = pattern.search(s, pos) + res = [] + while ps: + res.append(_escape_latex(s[pos : ps.span()[0]])) + res.append(ps.group()) + pos = ps.span()[1] + ps = pattern.search(s, pos) + + res.append(_escape_latex(s[pos : len(s)])) + return "".join(res).replace(r"rt8§=§7wz", r"\$") + + +def _math_mode_with_parentheses(s): + r""" + All characters in LaTeX math mode are preserved. + + The substrings in LaTeX math mode, which start with + the character ``\(`` and end with ``\)``, are preserved + without escaping. Otherwise regular LaTeX escaping applies. + + Parameters + ---------- + s : str + Input to be escaped + + Return + ------ + str : + Escaped string + """ + s = s.replace(r"\(", r"LEFT§=§6yzLEFT").replace(r"\)", r"RIGHTab5§=§RIGHT") + res = [] + for item in re.split(r"LEFT§=§6yz|ab5§=§RIGHT", s): + if item.startswith("LEFT") and item.endswith("RIGHT"): + res.append(item.replace("LEFT", r"\(").replace("RIGHT", r"\)")) + elif "LEFT" in item and "RIGHT" in item: + res.append( + _escape_latex(item).replace("LEFT", r"\(").replace("RIGHT", r"\)") + ) + else: + res.append( + _escape_latex(item) + .replace("LEFT", r"\textbackslash (") + .replace("RIGHT", r"\textbackslash )") + ) + return "".join(res) + + def _escape_latex_math(s): r""" All characters in LaTeX math mode are preserved. @@ -2395,43 +2464,9 @@ def _escape_latex_math(s): str : Escaped string """ - - def _math_mode_with_dollar(s): - s = s.replace(r"\$", r"rt8§=§7wz") - pattern = re.compile(r"\$.*?\$") - pos = 0 - ps = pattern.search(s, pos) - res = [] - while ps: - res.append(_escape_latex(s[pos : ps.span()[0]])) - res.append(ps.group()) - pos = ps.span()[1] - ps = pattern.search(s, pos) - - res.append(_escape_latex(s[pos : len(s)])) - return "".join(res).replace(r"rt8§=§7wz", r"\$") - - def _math_mode_with_parentheses(s): - s = s.replace(r"\(", r"LEFT§=§6yzLEFT").replace(r"\)", r"RIGHTab5§=§RIGHT") - res = [] - for item in re.split(r"LEFT§=§6yz|ab5§=§RIGHT", s): - if item.startswith("LEFT") and item.endswith("RIGHT"): - res.append(item.replace("LEFT", r"\(").replace("RIGHT", r"\)")) - elif "LEFT" in item and "RIGHT" in item: - res.append( - _escape_latex(item).replace("LEFT", r"\(").replace("RIGHT", r"\)") - ) - else: - res.append( - _escape_latex(item) - .replace("LEFT", r"\textbackslash (") - .replace("RIGHT", r"\textbackslash )") - ) - return "".join(res) - s = s.replace(r"\$", r"rt8§=§7wz") pattern_d = re.compile(r"\$.*?\$") - pattern_p = re.compile(r"\\(.*?\\)") + pattern_p = re.compile(r"\(.*?\)") pos_d = 0 pos_p = 0 ps_d = pattern_d.search(s, pos_d) @@ -2443,7 +2478,9 @@ def _math_mode_with_parentheses(s): mode.append(ps_p.span()[0]) if len(mode) == 0: return _escape_latex(s.replace(r"\$", r"rt8§=§7wz")) - if s[min(mode)] == r"$": + if s[mode[0]] == r"$": return _math_mode_with_dollar(s.replace(r"\$", r"rt8§=§7wz")) - else: + if s[mode[0] - 1 : mode[0] + 1] == r"\(": return _math_mode_with_parentheses(s.replace(r"\$", r"rt8§=§7wz")) + else: + return _escape_latex(s.replace(r"\$", r"rt8§=§7wz")) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 0c5024f425978..98ecec15b0b9b 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -167,7 +167,7 @@ def test_format_clear(styler, func, attr, kwargs): "latex", '<>\\&"\\%\\$\\#\\_\\{\\}\\textasciitilde \\textasciicircum ' "\\textbackslash \\textasciitilde \\space \\textasciicircum \\space " - "\\textbackslash ", + "\\textbackslash \\space ", ), ], ) @@ -200,7 +200,8 @@ def test_format_escape_html(escape, exp): "".join( [ r"$ \$&%#_{}~^\ $ ", - r"\&\%\#\_\{\}\textasciitilde \textasciicircum \textbackslash \$", + r"\&\%\#\_\{\}\textasciitilde \textasciicircum ", + r"\textbackslash \space \$", ] ), ), @@ -210,7 +211,7 @@ def test_format_escape_html(escape, exp): [ r"\( &%#_{}~^\ \) ", r"\&\%\#\_\{\}\textasciitilde \textasciicircum ", - r"\textbackslash \textbackslash (", + r"\textbackslash \space \textbackslash (", ] ), ), From 9be5c22caa05bec8082662479cc21c6150cab11c Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Mon, 20 Mar 2023 16:59:08 +0100 Subject: [PATCH 4/7] ENH: add an example to latex-math mode and a line to whatsnew --- doc/source/whatsnew/v2.1.0.rst | 1 + pandas/io/formats/style_render.py | 13 +++++++++++++ pandas/tests/io/formats/style/test_format.py | 5 ++--- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 3f898ca23bd6f..04579427139f8 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -35,6 +35,7 @@ Other enhancements - Let :meth:`DataFrame.to_feather` accept a non-default :class:`Index` and non-string column names (:issue:`51787`) - :class:`api.extensions.ExtensionArray` now has a :meth:`~api.extensions.ExtensionArray.map` method (:issue:`51809`) - Improve error message when having incompatible columns using :meth:`DataFrame.merge` (:issue:`51861`) +- Added to the escape mode "latex-math" preserving without escaping all characters between "\(" and "\)" in formatter (:issue:`51903`) .. --------------------------------------------------------------------------- .. _whatsnew_210.notable_bug_fixes: diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 1bf0f837046bd..28b15f28c7e3d 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1145,6 +1145,19 @@ def format( 1 & \%\#\textasciicircum \space \( \$x^2 \) \\ \end{tabular} + If we have in one DataFrame cell a combination of both shorthands + for math formulas, the shorthand with the sign ``$`` will be applied. + + >>> df = pd.DataFrame([[r"\( x^2 \) $x^2$"], \ + ... [r"$\frac{\beta}{\zeta}$ \(\frac{\beta}{\zeta}\)"]]) + >>> df.style.format(escape="latex-math").to_latex() + \begin{tabular}{ll} + & 0 \\ + 0 & \textbackslash ( x\textasciicircum 2 \textbackslash ) $x^2$ \\ + 1 & $\frac{\beta}{\zeta}$ \textbackslash (\textbackslash + frac\{\textbackslash beta\}\{\textbackslash zeta\}\textbackslash ) \\ + \end{tabular} + Pandas defines a `number-format` pseudo CSS attribute instead of the `.format` method to create `to_excel` permissible formatting. Note that semi-colons are CSS protected characters but used as separators in Excel's format string. diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index 98ecec15b0b9b..b60fa1f9f1429 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -228,9 +228,8 @@ def test_format_escape_html(escape, exp): ) def test_format_escape_latex_math(chars, expected): # GH 51903 - # latex-math escape works for each DataFrame cell separately. - # If we have a combination of dollar signs and brackets, - # the sign which occurs first would apply. + # latex-math escape works for each DataFrame cell separately. If we have + # a combination of dollar signs and brackets, the dollar sign would apply. df = DataFrame([[chars]]) s = df.style.format("{0}", escape="latex-math") assert s._translate(True, True)["body"][0][1]["display_value"] == expected From 25e6b1f841f71ff1f9208ab72192540e19df4712 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Tue, 21 Mar 2023 11:54:44 +0100 Subject: [PATCH 5/7] ENH: update docs for Styler: add description latex-math mode to escape --- pandas/io/formats/style.py | 6 +++++- pandas/io/formats/style_render.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 573e2b72e81f9..26405aac7d1c0 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -175,7 +175,11 @@ class Styler(StylerRenderer): in cell display string with HTML-safe sequences. Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``, ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with - LaTeX-safe sequences. If not given uses ``pandas.options.styler.format.escape``. + LaTeX-safe sequences. Use 'latex-math' to replace the characters + the same way as in 'latex' mode, except for math substrings, + which either are surrounded by two characters ``$`` or start with + the character ``\(`` and end with ``\)``. + If not given uses ``pandas.options.styler.format.escape``. .. versionadded:: 1.3.0 formatter : str, callable, dict, optional diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 28b15f28c7e3d..889cd4b96af02 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -1151,6 +1151,7 @@ def format( >>> df = pd.DataFrame([[r"\( x^2 \) $x^2$"], \ ... [r"$\frac{\beta}{\zeta}$ \(\frac{\beta}{\zeta}\)"]]) >>> df.style.format(escape="latex-math").to_latex() + ... # doctest: +SKIP \begin{tabular}{ll} & 0 \\ 0 & \textbackslash ( x\textasciicircum 2 \textbackslash ) $x^2$ \\ From ed27582a92f222e1be2cdcb40e9d2d487567968d Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Tue, 21 Mar 2023 21:57:21 +0100 Subject: [PATCH 6/7] improve code style --- pandas/io/formats/style_render.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 889cd4b96af02..1f82fdee0f2d6 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2479,12 +2479,8 @@ def _escape_latex_math(s): Escaped string """ s = s.replace(r"\$", r"rt8§=§7wz") - pattern_d = re.compile(r"\$.*?\$") - pattern_p = re.compile(r"\(.*?\)") - pos_d = 0 - pos_p = 0 - ps_d = pattern_d.search(s, pos_d) - ps_p = pattern_p.search(s, pos_p) + ps_d = re.compile(r"\$.*?\$").search(s, 0) + ps_p = re.compile(r"\(.*?\)").search(s, 0) mode = [] if ps_d: mode.append(ps_d.span()[0]) From 3daf4d58f9e14a5b476fcd4e3721ccf25ab31470 Mon Sep 17 00:00:00 2001 From: Natalia Mokeeva Date: Fri, 24 Mar 2023 23:47:51 +0100 Subject: [PATCH 7/7] add an example to test and correct _escape_latex_math --- pandas/io/formats/style_render.py | 8 ++++---- pandas/tests/io/formats/style/test_format.py | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py index 1f82fdee0f2d6..3a3b784c9d4d7 100644 --- a/pandas/io/formats/style_render.py +++ b/pandas/io/formats/style_render.py @@ -2487,10 +2487,10 @@ def _escape_latex_math(s): if ps_p: mode.append(ps_p.span()[0]) if len(mode) == 0: - return _escape_latex(s.replace(r"\$", r"rt8§=§7wz")) + return _escape_latex(s.replace(r"rt8§=§7wz", r"\$")) if s[mode[0]] == r"$": - return _math_mode_with_dollar(s.replace(r"\$", r"rt8§=§7wz")) + return _math_mode_with_dollar(s.replace(r"rt8§=§7wz", r"\$")) if s[mode[0] - 1 : mode[0] + 1] == r"\(": - return _math_mode_with_parentheses(s.replace(r"\$", r"rt8§=§7wz")) + return _math_mode_with_parentheses(s.replace(r"rt8§=§7wz", r"\$")) else: - return _escape_latex(s.replace(r"\$", r"rt8§=§7wz")) + return _escape_latex(s.replace(r"rt8§=§7wz", r"\$")) diff --git a/pandas/tests/io/formats/style/test_format.py b/pandas/tests/io/formats/style/test_format.py index b60fa1f9f1429..c6e981c684044 100644 --- a/pandas/tests/io/formats/style/test_format.py +++ b/pandas/tests/io/formats/style/test_format.py @@ -215,6 +215,10 @@ def test_format_escape_html(escape, exp): ] ), ), + ( + r"$\&%#_{}^\$", + r"\$\textbackslash \&\%\#\_\{\}\textasciicircum \textbackslash \$", + ), ( r"$ \frac{1}{2} $ \( \frac{1}{2} \)", "".join(