diff --git a/mistletoe/latex_renderer.py b/mistletoe/latex_renderer.py index ec76d76..3230392 100644 --- a/mistletoe/latex_renderer.py +++ b/mistletoe/latex_renderer.py @@ -2,6 +2,7 @@ LaTeX renderer for mistletoe. """ +import re import string from itertools import chain from urllib.parse import quote @@ -78,11 +79,20 @@ def render_escape_sequence(self, token): return self.render_inner(token) def render_raw_text(self, token, escape=True): - return (token.content.replace('$', '\\$').replace('#', '\\#') - .replace('{', '\\{').replace('}', '\\}') - .replace('&', '\\&').replace('_', '\\_') - .replace('%', '\\%') - ) if escape else token.content + """Escape all latex special characters $#&%_{}^~\\ within `token.content`. + """ + if not escape: + return token.content + + if not hasattr(self, 'raw_escape_chars'): + self.raw_escape_chars = re.compile('([$#&%_{}])') + + content = token.content.replace('\\', '\\textbackslash') + content = self.raw_escape_chars.sub(r'\\\1', content) + # The \text* commands gobble up whitespace behind them -> {} to prevent that. + return content.replace('~', '\\textasciitilde{}') \ + .replace('^', '\\textasciicircum{}') \ + .replace('\\textbackslash', '\\textbackslash{}') def render_heading(self, token): inner = self.render_inner(token) diff --git a/test/test_latex_renderer.py b/test/test_latex_renderer.py index 9fd0c80..b23445d 100644 --- a/test/test_latex_renderer.py +++ b/test/test_latex_renderer.py @@ -71,10 +71,20 @@ def test_math(self): self._test_token('Math', expected, children=False, content='$ 1 + 2 = 3 $') - def test_raw_text(self): - expected = '\\$\\&\\#\\{\\}' - self._test_token('RawText', expected, - children=False, content='$&#{}') + @parameterized.expand([ + ('$', '\\$'), + ('&', '\\&'), + ('#', '\\#'), + ('%', '\\%'), + ('_', '\\_'), + ('{', '\\{'), + ('}', '\\}'), + ('~', '\\textasciitilde{}'), + ('^', '\\textasciicircum{}'), + ('\\', '\\textbackslash{}'), + ]) + def test_raw_text(self, target, expected): + self._test_token('RawText', expected, children=False, content=target) def test_heading(self): expected = '\n\\section{inner}\n'