From 71b653719d2212795545230fe34426c1f78aa993 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 2 May 2020 00:09:55 +0900 Subject: [PATCH] Fix #7610: incorrectly renders consecutive backslashes --- CHANGES | 1 + sphinx/transforms/__init__.py | 17 ++++++++++++----- tests/test_markup.py | 20 ++++++++++++++++++-- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index d426fa48c..2ba127035 100644 --- a/CHANGES +++ b/CHANGES @@ -89,6 +89,7 @@ Bugs fixed * #7536: sphinx-autogen: crashes when template uses i18n feature * #2785: html: Bad alignment of equation links * #7581: napoleon: bad parsing of inline code in attribute docstrings +* #7610: incorrectly renders consecutive backslashes for docutils-0.16 Testing -------- diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index a00f04fdf..1605c95b9 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -23,6 +23,7 @@ from sphinx import addnodes from sphinx.config import Config from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.locale import _, __ +from sphinx.util import docutils from sphinx.util import logging from sphinx.util.docutils import new_document from sphinx.util.i18n import format_date @@ -360,12 +361,18 @@ class SphinxSmartQuotes(SmartQuotes, SphinxTransform): def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, None]: # A generator that yields ``(texttype, nodetext)`` tuples for a list # of "Text" nodes (interface to ``smartquotes.educate_tokens()``). - - texttype = {True: 'literal', # "literal" text is not changed: - False: 'plain'} for txtnode in txtnodes: - notsmartquotable = not is_smartquotable(txtnode) - yield (texttype[notsmartquotable], txtnode.astext()) + if is_smartquotable(txtnode): + if docutils.__version_info__ >= (0, 16): + # SmartQuotes uses backslash escapes instead of null-escapes + text = re.sub(r'(?<=\x00)([-\\\'".`])', r'\\\1', str(txtnode)) + else: + text = txtnode.astext() + + yield ('plain', text) + else: + # skip smart quotes + yield ('literal', txtnode.astext()) class DoctreeReadEvent(SphinxTransform): diff --git a/tests/test_markup.py b/tests/test_markup.py index 1b41dc69a..5fb835605 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -13,7 +13,6 @@ import re import pytest from docutils import frontend, utils, nodes from docutils.parsers.rst import Parser as RstParser -from docutils.transforms.universal import SmartQuotes from sphinx import addnodes from sphinx.builders.html.transforms import KeyboardTransform @@ -21,6 +20,8 @@ from sphinx.builders.latex import LaTeXBuilder from sphinx.builders.latex.theming import ThemeFactory from sphinx.roles import XRefRole from sphinx.testing.util import Struct, assert_node +from sphinx.transforms import SphinxSmartQuotes +from sphinx.util import docutils from sphinx.util import texescape from sphinx.util.docutils import sphinx_domains from sphinx.writers.html import HTMLWriter, HTMLTranslator @@ -67,7 +68,7 @@ def parse(new_document): document = new_document() parser = RstParser() parser.parse(rst, document) - SmartQuotes(document, startnode=None).apply() + SphinxSmartQuotes(document, startnode=None).apply() for msg in document.traverse(nodes.system_message): if msg['level'] == 1: msg.replace_self([]) @@ -349,6 +350,21 @@ def test_inline(get_verifier, type, rst, html_expected, latex_expected): verifier(rst, html_expected, latex_expected) +@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [ + ( + 'verify', + r'4 backslashes \\\\', + r'

4 backslashes \\

', + None, + ), +]) +@pytest.mark.skipif(docutils.__version_info__ < (0, 16), + reason='docutils-0.16 or above is required') +def test_inline_docutils16(get_verifier, type, rst, html_expected, latex_expected): + verifier = get_verifier(type) + verifier(rst, html_expected, latex_expected) + + @pytest.mark.sphinx(confoverrides={'latex_engine': 'xelatex'}) @pytest.mark.parametrize('type,rst,html_expected,latex_expected', [ (