diff --git a/CHANGES b/CHANGES index db7d1cf56..495d47199 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,8 @@ Deprecated * ``sphinx.builders.gettext.POHEADER`` * ``sphinx.io.SphinxStandaloneReader.app`` * ``sphinx.io.SphinxStandaloneReader.env`` +* ``sphinx.util.texescape.tex_escape_map`` +* ``sphinx.util.texescape.tex_hl_escape_map_new`` Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 8c201b525..958f4ee4d 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -41,6 +41,16 @@ The following is a list of deprecated interfaces. - 4.0 - ``sphinx.io.SphinxStandaloneReader.setup()`` + * - ``sphinx.util.texescape.tex_escape_map`` + - 2.3 + - 4.0 + - ``sphinx.util.texescape.escape()`` + + * - ``sphinx.util.texescape.tex_hl_escape_map_new`` + - 2.3 + - 4.0 + - ``sphinx.util.texescape.hlescape()`` + * - ``sphinx.domains.math.MathDomain.add_equation()`` - 2.2 - 4.0 diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 9c34b5568..02f147e40 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -440,10 +440,12 @@ def default_latex_use_xindy(config: Config) -> bool: def default_latex_documents(config: Config) -> List[Tuple[str, str, str, str, str]]: """ Better default latex_documents settings. """ + project = texescape.escape(config.project, config.latex_engine) + author = texescape.escape(config.author, config.latex_engine) return [(config.master_doc, make_filename_from_project(config.project) + '.tex', - texescape.escape_abbr(texescape.escape(config.project)), - texescape.escape_abbr(texescape.escape(config.author)), + texescape.escape_abbr(project), + texescape.escape_abbr(author), 'manual')] diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index d2a8a666d..99dc02d3d 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -27,10 +27,9 @@ from sphinx.domains import Domain from sphinx.environment import BuildEnvironment from sphinx.errors import NoUri from sphinx.locale import _, __ -from sphinx.util import logging +from sphinx.util import logging, texescape from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import make_refnode -from sphinx.util.texescape import get_escape_func from sphinx.writers.html import HTMLTranslator from sphinx.writers.latex import LaTeXTranslator @@ -299,11 +298,12 @@ def depart_todo_node(self: HTMLTranslator, node: todo_node) -> None: def latex_visit_todo_node(self: LaTeXTranslator, node: todo_node) -> None: if self.config.todo_include_todos: - escape = get_escape_func(self.config.latex_engine) self.body.append('\n\\begin{sphinxadmonition}{note}{') self.body.append(self.hypertarget_to(node)) + title_node = cast(nodes.title, node[0]) - self.body.append('%s:}' % escape(title_node.astext())) + title = texescape.escape(title_node.astext(), self.config.latex_engine) + self.body.append('%s:}' % title) node.pop(0) else: raise nodes.SkipNode diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 0a6cf8d6e..6804b9de0 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -27,8 +27,7 @@ from sphinx.deprecation import RemovedInSphinx30Warning from sphinx.ext import doctest from sphinx.locale import __ from sphinx.pygments_styles import SphinxStyle, NoneStyle -from sphinx.util import logging -from sphinx.util.texescape import get_hlescape_func, tex_hl_escape_map_new +from sphinx.util import logging, texescape if False: # For type annotation @@ -114,7 +113,7 @@ class PygmentsBridge: # first, escape highlighting characters like Pygments does source = source.translate(escape_hl_chars) # then, escape all characters nonrepresentable in LaTeX - source = source.translate(tex_hl_escape_map_new) + source = texescape.escape(source, self.latex_engine) return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \ source + '\\end{Verbatim}\n' @@ -194,8 +193,7 @@ class PygmentsBridge: if self.dest == 'html': return hlsource else: - escape = get_hlescape_func(self.latex_engine) - return escape(hlsource) + return texescape.hlescape(hlsource, self.latex_engine) def get_stylesheet(self): # type: () -> str diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 3a43db9a5..e6e72d079 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -9,6 +9,7 @@ """ import os +from functools import partial from typing import Dict, List, Union from jinja2.loaders import BaseLoader @@ -69,8 +70,9 @@ class LaTeXRenderer(SphinxRenderer): super().__init__(template_path) # use texescape as escape filter - self.env.filters['e'] = texescape.get_escape_func(latex_engine) - self.env.filters['escape'] = texescape.get_escape_func(latex_engine) + escape = partial(texescape.escape, latex_engine=latex_engine) + self.env.filters['e'] = escape + self.env.filters['escape'] = escape self.env.filters['eabbr'] = texescape.escape_abbr # use JSP/eRuby like tagging instead because curly bracket; the default diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index e3c45adb4..f490c25c1 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -9,7 +9,10 @@ """ import re -from typing import Callable, Dict +from typing import Dict + +from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias + tex_replacements = [ # map TeX special chars @@ -84,47 +87,38 @@ unicode_tex_replacements = [ ('₉', r'\(\sb{\text{9}}\)'), ] -tex_escape_map = {} # type: Dict[int, str] -tex_escape_map_without_unicode = {} # type: Dict[int, str] -tex_replace_map = {} -tex_hl_escape_map_new = {} # type: Dict[int, str] -tex_hl_escape_map_new_without_unicode = {} # type: Dict[int, str] +tex_replace_map = {} # type: Dict[int, str] + +_tex_escape_map = {} # type: Dict[int, str] +_tex_escape_map_without_unicode = {} # type: Dict[int, str] +_tex_hlescape_map = {} # type: Dict[int, str] +_tex_hlescape_map_without_unicode = {} # type: Dict[int, str] -def get_escape_func(latex_engine: str) -> Callable[[str], str]: - """Get escape() function for given latex_engine.""" - if latex_engine in ('lualatex', 'xelatex'): - return escape_for_unicode_latex_engine - else: - return escape +deprecated_alias('sphinx.util.texescape', + { + 'tex_escape_map': _tex_escape_map, + 'tex_hl_escape_map_new': _tex_hlescape_map, + }, + RemovedInSphinx40Warning) -def escape(s: str) -> str: +def escape(s: str, latex_engine: str = None) -> str: """Escape text for LaTeX output.""" - return s.translate(tex_escape_map) - - -def escape_for_unicode_latex_engine(s: str) -> str: - """Escape text for unicode supporting LaTeX engine.""" - return s.translate(tex_escape_map_without_unicode) - - -def get_hlescape_func(latex_engine: str) -> Callable[[str], str]: - """Get hlescape() function for given latex_engine.""" if latex_engine in ('lualatex', 'xelatex'): - return hlescape_for_unicode_latex_engine + # unicode based LaTeX engine + return s.translate(_tex_escape_map_without_unicode) else: - return hlescape + return s.translate(_tex_escape_map) -def hlescape(s: str) -> str: +def hlescape(s: str, latex_engine: str = None) -> str: """Escape text for LaTeX highlighter.""" - return s.translate(tex_hl_escape_map_new) - - -def hlescape_for_unicode_latex_engine(s: str) -> str: - """Escape text for unicode supporting LaTeX engine.""" - return s.translate(tex_hl_escape_map_new_without_unicode) + if latex_engine in ('lualatex', 'xelatex'): + # unicode based LaTeX engine + return s.translate(_tex_hlescape_map_without_unicode) + else: + return s.translate(_tex_hlescape_map) def escape_abbr(text: str) -> str: @@ -134,19 +128,19 @@ def escape_abbr(text: str) -> str: def init() -> None: for a, b in tex_replacements: - tex_escape_map[ord(a)] = b - tex_escape_map_without_unicode[ord(a)] = b + _tex_escape_map[ord(a)] = b + _tex_escape_map_without_unicode[ord(a)] = b tex_replace_map[ord(a)] = '_' for a, b in unicode_tex_replacements: - tex_escape_map[ord(a)] = b + _tex_escape_map[ord(a)] = b tex_replace_map[ord(a)] = '_' for a, b in tex_replacements: if a in '[]{}\\': continue - tex_hl_escape_map_new[ord(a)] = b - tex_hl_escape_map_new_without_unicode[ord(a)] = b + _tex_hlescape_map[ord(a)] = b + _tex_hlescape_map_without_unicode[ord(a)] = b for a, b in unicode_tex_replacements: - tex_hl_escape_map_new[ord(a)] = b + _tex_hlescape_map[ord(a)] = b diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 5facdc40c..5641c08f8 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -28,11 +28,11 @@ from sphinx.deprecation import ( from sphinx.domains.std import StandardDomain from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, _, __ -from sphinx.util import split_into, logging +from sphinx.util import split_into, logging, texescape from sphinx.util.docutils import SphinxTranslator from sphinx.util.nodes import clean_astext, get_prev_node from sphinx.util.template import LaTeXRenderer -from sphinx.util.texescape import get_escape_func, tex_replace_map +from sphinx.util.texescape import tex_replace_map try: from docutils.utils.roman import toRoman @@ -500,9 +500,6 @@ class LaTeXTranslator(SphinxTranslator): self.compact_list = 0 self.first_param = 0 - # escape helper - self.escape = get_escape_func(self.config.latex_engine) - # sort out some elements self.elements = self.builder.context.copy() @@ -736,6 +733,9 @@ class LaTeXTranslator(SphinxTranslator): # type: (str) -> str return '\\autopageref*{%s}' % self.idescape(id) + def escape(self, s: str) -> str: + return texescape.escape(s, self.config.latex_engine) + def idescape(self, id): # type: (str) -> str return '\\detokenize{%s}' % str(id).translate(tex_replace_map).\ diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 0ebcd0b62..38e99f48a 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1414,6 +1414,7 @@ def test_default_latex_documents(): 'project': 'STASI™ Documentation', 'author': "Wolfgang Schäuble & G'Beckstein."}) config.init_values() + config.add('latex_engine', None, True, None) expected = [('index', 'stasi.tex', 'STASI™ Documentation', r"Wolfgang Schäuble \& G'Beckstein.\@{}", 'manual')] assert default_latex_documents(config) == expected