Skip to content

Commit fe0d8db

Browse files
retkowskiTomAugspurger
authored andcommitted
BUG: Correct Escaping for LaTeX, #20859 (#20860)
1 parent 8ddc0fd commit fe0d8db

File tree

3 files changed

+26
-7
lines changed

3 files changed

+26
-7
lines changed

doc/source/whatsnew/v0.23.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,7 @@ I/O
11641164
- Bug in :func:`DataFrame.to_latex()` where a non-string index-level name would result in an ``AttributeError`` (:issue:`19981`)
11651165
- Bug in :func:`DataFrame.to_latex()` where the combination of an index name and the `index_names=False` option would result in incorrect output (:issue:`18326`)
11661166
- Bug in :func:`DataFrame.to_latex()` where a ``MultiIndex`` with an empty string as its name would result in incorrect output (:issue:`18669`)
1167+
- Bug in :func:`DataFrame.to_latex()` where missing space characters caused wrong escaping and produced non-valid latex in some cases (:issue:`20859`)
11671168
- Bug in :func:`read_json` where large numeric values were causing an ``OverflowError`` (:issue:`18842`)
11681169
- Bug in :func:`DataFrame.to_parquet` where an exception was raised if the write destination is S3 (:issue:`19134`)
11691170
- :class:`Interval` now supported in :func:`DataFrame.to_excel` for all Excel file types (:issue:`19242`)

pandas/io/formats/latex.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,13 @@ def pad_empties(x):
134134
buf.write('\\endlastfoot\n')
135135
if self.fmt.kwds.get('escape', True):
136136
# escape backslashes first
137-
crow = [(x.replace('\\', '\\textbackslash').replace('_', '\\_')
137+
crow = [(x.replace('\\', '\\textbackslash ')
138+
.replace('_', '\\_')
138139
.replace('%', '\\%').replace('$', '\\$')
139140
.replace('#', '\\#').replace('{', '\\{')
140-
.replace('}', '\\}').replace('~', '\\textasciitilde')
141-
.replace('^', '\\textasciicircum').replace('&', '\\&')
141+
.replace('}', '\\}').replace('~', '\\textasciitilde ')
142+
.replace('^', '\\textasciicircum ')
143+
.replace('&', '\\&')
142144
if (x and x != '{}') else '{}') for x in row]
143145
else:
144146
crow = [x if x else '{}' for x in row]

pandas/tests/io/formats/test_to_latex.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def test_to_latex_escape(self):
369369

370370
escaped_expected = r'''\begin{tabular}{lll}
371371
\toprule
372-
{} & co\$e\textasciicircumx\$ & co\textasciicircuml1 \\
372+
{} & co\$e\textasciicircum x\$ & co\textasciicircum l1 \\
373373
\midrule
374374
a & a & a \\
375375
b & b & b \\
@@ -380,6 +380,22 @@ def test_to_latex_escape(self):
380380
assert unescaped_result == unescaped_expected
381381
assert escaped_result == escaped_expected
382382

383+
def test_to_latex_special_escape(self):
384+
df = DataFrame([r"a\b\c", r"^a^b^c", r"~a~b~c"])
385+
386+
escaped_result = df.to_latex()
387+
escaped_expected = r"""\begin{tabular}{ll}
388+
\toprule
389+
{} & 0 \\
390+
\midrule
391+
0 & a\textbackslash b\textbackslash c \\
392+
1 & \textasciicircum a\textasciicircum b\textasciicircum c \\
393+
2 & \textasciitilde a\textasciitilde b\textasciitilde c \\
394+
\bottomrule
395+
\end{tabular}
396+
"""
397+
assert escaped_result == escaped_expected
398+
383399
def test_to_latex_longtable(self, frame):
384400
frame.to_latex(longtable=True)
385401

@@ -447,9 +463,9 @@ def test_to_latex_escape_special_chars(self):
447463
4 & \_ \\
448464
5 & \{ \\
449465
6 & \} \\
450-
7 & \textasciitilde \\
451-
8 & \textasciicircum \\
452-
9 & \textbackslash \\
466+
7 & \textasciitilde \\
467+
8 & \textasciicircum \\
468+
9 & \textbackslash \\
453469
\bottomrule
454470
\end{tabular}
455471
"""

0 commit comments

Comments
 (0)