Skip to content

Commit dfd5d66

Browse files
authored
ENH: add math mode to formatter escape="latex-math" (#50398)
* ENH: add escape math mode with escape=latex-math * ENH: add test for escape=latex-math * change uuid string and revert the example for latex * add one line to whatsnew
1 parent 4ab1c76 commit dfd5d66

File tree

4 files changed

+68
-5
lines changed

4 files changed

+68
-5
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ Other enhancements
281281
- Added new argument ``engine`` to :func:`read_json` to support parsing JSON with pyarrow by specifying ``engine="pyarrow"`` (:issue:`48893`)
282282
- Added support for SQLAlchemy 2.0 (:issue:`40686`)
283283
- :class:`Index` set operations :meth:`Index.union`, :meth:`Index.intersection`, :meth:`Index.difference`, and :meth:`Index.symmetric_difference` now support ``sort=True``, which will always return a sorted result, unlike the default ``sort=None`` which does not sort in some cases (:issue:`25151`)
284+
- Added new escape mode "latex-math" to avoid escaping "$" in formatter (:issue:`50040`)
284285

285286
.. ---------------------------------------------------------------------------
286287
.. _whatsnew_200.notable_bug_fixes:

pandas/core/config_init.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ def register_converter_cb(key) -> None:
873873
"format.escape",
874874
None,
875875
styler_escape,
876-
validator=is_one_of_factory([None, "html", "latex"]),
876+
validator=is_one_of_factory([None, "html", "latex", "latex-math"]),
877877
)
878878

879879
cf.register_option(

pandas/io/formats/style_render.py

+53-3
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,8 @@ def format(
985985
Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``,
986986
``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with
987987
LaTeX-safe sequences.
988+
Use 'latex-math' to replace the characters the same way as in 'latex' mode,
989+
except for math substrings, which start and end with ``$``.
988990
Escaping is done before ``formatter``.
989991
990992
.. versionadded:: 1.3.0
@@ -1101,18 +1103,30 @@ def format(
11011103
<td .. >NA</td>
11021104
...
11031105
1104-
Using a ``formatter`` with LaTeX ``escape``.
1106+
Using a ``formatter`` with ``escape`` in 'latex' mode.
11051107
11061108
>>> df = pd.DataFrame([["123"], ["~ ^"], ["$%#"]])
11071109
>>> df.style.format("\\textbf{{{}}}", escape="latex").to_latex()
11081110
... # doctest: +SKIP
11091111
\begin{tabular}{ll}
1110-
{} & {0} \\
1112+
& 0 \\
11111113
0 & \textbf{123} \\
11121114
1 & \textbf{\textasciitilde \space \textasciicircum } \\
11131115
2 & \textbf{\$\%\#} \\
11141116
\end{tabular}
11151117
1118+
Using ``escape`` in 'latex-math' mode.
1119+
1120+
>>> df = pd.DataFrame([[r"$\sum_{i=1}^{10} a_i$ a~b $\alpha \
1121+
... = \frac{\beta}{\zeta^2}$"], ["%#^ $ \$x^2 $"]])
1122+
>>> df.style.format(escape="latex-math").to_latex()
1123+
... # doctest: +SKIP
1124+
\begin{tabular}{ll}
1125+
& 0 \\
1126+
0 & $\sum_{i=1}^{10} a_i$ a\textasciitilde b $\alpha = \frac{\beta}{\zeta^2}$ \\
1127+
1 & \%\#\textasciicircum \space $ \$x^2 $ \\
1128+
\end{tabular}
1129+
11161130
Pandas defines a `number-format` pseudo CSS attribute instead of the `.format`
11171131
method to create `to_excel` permissible formatting. Note that semi-colons are
11181132
CSS protected characters but used as separators in Excel's format string.
@@ -1739,9 +1753,12 @@ def _str_escape(x, escape):
17391753
return escape_html(x)
17401754
elif escape == "latex":
17411755
return _escape_latex(x)
1756+
elif escape == "latex-math":
1757+
return _escape_latex_math(x)
17421758
else:
17431759
raise ValueError(
1744-
f"`escape` only permitted in {{'html', 'latex'}}, got {escape}"
1760+
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
1761+
got {escape}"
17451762
)
17461763
return x
17471764

@@ -2340,3 +2357,36 @@ def _escape_latex(s):
23402357
.replace("^", "\\textasciicircum ")
23412358
.replace("ab2§=§8yz", "\\textbackslash ")
23422359
)
2360+
2361+
2362+
def _escape_latex_math(s):
2363+
r"""
2364+
All characters between two characters ``$`` are preserved.
2365+
2366+
The substrings in LaTeX math mode, which start with the character ``$``
2367+
and end with ``$``, are preserved without escaping. Otherwise
2368+
regular LaTeX escaping applies. See ``_escape_latex()``.
2369+
2370+
Parameters
2371+
----------
2372+
s : str
2373+
Input to be escaped
2374+
2375+
Return
2376+
------
2377+
str :
2378+
Escaped string
2379+
"""
2380+
s = s.replace(r"\$", r"rt8§=§7wz")
2381+
pattern = re.compile(r"\$.*?\$")
2382+
pos = 0
2383+
ps = pattern.search(s, pos)
2384+
res = []
2385+
while ps:
2386+
res.append(_escape_latex(s[pos : ps.span()[0]]))
2387+
res.append(ps.group())
2388+
pos = ps.span()[1]
2389+
ps = pattern.search(s, pos)
2390+
2391+
res.append(_escape_latex(s[pos : len(s)]))
2392+
return "".join(res).replace(r"rt8§=§7wz", r"\$")

pandas/tests/io/formats/style/test_format.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ def test_format_escape_html(escape, exp):
192192
assert styler._translate(True, True)["head"][0][1]["display_value"] == f"&{exp}&"
193193

194194

195+
def test_format_escape_latex_math():
196+
chars = r"$\frac{1}{2} \$ x^2$ ~%#^"
197+
df = DataFrame([[chars]])
198+
199+
expected = r"$\frac{1}{2} \$ x^2$ \textasciitilde \%\#\textasciicircum "
200+
s = df.style.format("{0}", escape="latex-math")
201+
assert expected == s._translate(True, True)["body"][0][1]["display_value"]
202+
203+
195204
def test_format_escape_na_rep():
196205
# tests the na_rep is not escaped
197206
df = DataFrame([['<>&"', None]])
@@ -359,7 +368,7 @@ def test_format_decimal(formatter, thousands, precision, func, col):
359368

360369

361370
def test_str_escape_error():
362-
msg = "`escape` only permitted in {'html', 'latex'}, got "
371+
msg = "`escape` only permitted in {'html', 'latex', 'latex-math'}, got "
363372
with pytest.raises(ValueError, match=msg):
364373
_str_escape("text", "bad_escape")
365374

@@ -403,6 +412,9 @@ def test_format_options():
403412
with option_context("styler.format.escape", "latex"):
404413
ctx_with_op = df.style._translate(True, True)
405414
assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde "
415+
with option_context("styler.format.escape", "latex-math"):
416+
ctx_with_op = df.style._translate(True, True)
417+
assert ctx_with_op["body"][1][3]["display_value"] == "\\&\\textasciitilde "
406418

407419
# test option: formatter
408420
with option_context("styler.format.formatter", {"int": "{:,.2f}"}):

0 commit comments

Comments
 (0)