Fix #7610: incorrectly renders consecutive backslashes

This commit is contained in:
Takeshi KOMIYA 2020-05-02 00:09:55 +09:00
parent 6ce265dc81
commit 71b653719d
3 changed files with 31 additions and 7 deletions

View File

@ -89,6 +89,7 @@ Bugs fixed
* #7536: sphinx-autogen: crashes when template uses i18n feature * #7536: sphinx-autogen: crashes when template uses i18n feature
* #2785: html: Bad alignment of equation links * #2785: html: Bad alignment of equation links
* #7581: napoleon: bad parsing of inline code in attribute docstrings * #7581: napoleon: bad parsing of inline code in attribute docstrings
* #7610: incorrectly renders consecutive backslashes for docutils-0.16
Testing Testing
-------- --------

View File

@ -23,6 +23,7 @@ from sphinx import addnodes
from sphinx.config import Config from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import docutils
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.docutils import new_document from sphinx.util.docutils import new_document
from sphinx.util.i18n import format_date 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]: def get_tokens(self, txtnodes: List[Text]) -> Generator[Tuple[str, str], None, None]:
# A generator that yields ``(texttype, nodetext)`` tuples for a list # A generator that yields ``(texttype, nodetext)`` tuples for a list
# of "Text" nodes (interface to ``smartquotes.educate_tokens()``). # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
texttype = {True: 'literal', # "literal" text is not changed:
False: 'plain'}
for txtnode in txtnodes: for txtnode in txtnodes:
notsmartquotable = not is_smartquotable(txtnode) if is_smartquotable(txtnode):
yield (texttype[notsmartquotable], txtnode.astext()) 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): class DoctreeReadEvent(SphinxTransform):

View File

@ -13,7 +13,6 @@ import re
import pytest import pytest
from docutils import frontend, utils, nodes from docutils import frontend, utils, nodes
from docutils.parsers.rst import Parser as RstParser from docutils.parsers.rst import Parser as RstParser
from docutils.transforms.universal import SmartQuotes
from sphinx import addnodes from sphinx import addnodes
from sphinx.builders.html.transforms import KeyboardTransform 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.builders.latex.theming import ThemeFactory
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.testing.util import Struct, assert_node 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 import texescape
from sphinx.util.docutils import sphinx_domains from sphinx.util.docutils import sphinx_domains
from sphinx.writers.html import HTMLWriter, HTMLTranslator from sphinx.writers.html import HTMLWriter, HTMLTranslator
@ -67,7 +68,7 @@ def parse(new_document):
document = new_document() document = new_document()
parser = RstParser() parser = RstParser()
parser.parse(rst, document) parser.parse(rst, document)
SmartQuotes(document, startnode=None).apply() SphinxSmartQuotes(document, startnode=None).apply()
for msg in document.traverse(nodes.system_message): for msg in document.traverse(nodes.system_message):
if msg['level'] == 1: if msg['level'] == 1:
msg.replace_self([]) msg.replace_self([])
@ -349,6 +350,21 @@ def test_inline(get_verifier, type, rst, html_expected, latex_expected):
verifier(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'<p>4 backslashes \\</p>',
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.sphinx(confoverrides={'latex_engine': 'xelatex'})
@pytest.mark.parametrize('type,rst,html_expected,latex_expected', [ @pytest.mark.parametrize('type,rst,html_expected,latex_expected', [
( (