mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #4982 from tk0miya/4980_improve_label_handling_for_latex
Fix #4980: latex: Explicit labels on code blocks are duplicated
This commit is contained in:
commit
e87872db6f
1
CHANGES
1
CHANGES
@ -37,6 +37,7 @@ Bugs fixed
|
|||||||
* #4956: autodoc: Failed to extract document from a subclass of the class on
|
* #4956: autodoc: Failed to extract document from a subclass of the class on
|
||||||
mocked module
|
mocked module
|
||||||
* #4973: latex: glossary directive adds whitespace to each item
|
* #4973: latex: glossary directive adds whitespace to each item
|
||||||
|
* #4980: latex: Explicit labels on code blocks are duplicated
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
--------
|
--------
|
||||||
|
@ -49,6 +49,12 @@ BEGIN_DOC = r'''
|
|||||||
|
|
||||||
LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection",
|
LATEXSECTIONNAMES = ["part", "chapter", "section", "subsection",
|
||||||
"subsubsection", "paragraph", "subparagraph"]
|
"subsubsection", "paragraph", "subparagraph"]
|
||||||
|
HYPERLINK_SUPPORT_NODES = (
|
||||||
|
nodes.figure,
|
||||||
|
nodes.literal_block,
|
||||||
|
nodes.table,
|
||||||
|
nodes.section,
|
||||||
|
)
|
||||||
|
|
||||||
DEFAULT_SETTINGS = {
|
DEFAULT_SETTINGS = {
|
||||||
'latex_engine': 'pdflatex',
|
'latex_engine': 'pdflatex',
|
||||||
@ -728,6 +734,14 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
return (anchor and '\\phantomsection' or '') + \
|
return (anchor and '\\phantomsection' or '') + \
|
||||||
'\\label{%s}' % self.idescape(id)
|
'\\label{%s}' % self.idescape(id)
|
||||||
|
|
||||||
|
def hypertarget_to(self, node, anchor=False):
|
||||||
|
# type: (nodes.Node, bool) -> unicode
|
||||||
|
labels = ''.join(self.hypertarget(node_id, anchor=False) for node_id in node['ids'])
|
||||||
|
if anchor:
|
||||||
|
return r'\phantomsection' + labels
|
||||||
|
else:
|
||||||
|
return labels
|
||||||
|
|
||||||
def hyperlink(self, id):
|
def hyperlink(self, id):
|
||||||
# type: (unicode) -> unicode
|
# type: (unicode) -> unicode
|
||||||
return '{\\hyperref[%s]{' % self.idescape(id)
|
return '{\\hyperref[%s]{' % self.idescape(id)
|
||||||
@ -937,8 +951,6 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
if not self.this_is_the_title:
|
if not self.this_is_the_title:
|
||||||
self.sectionlevel += 1
|
self.sectionlevel += 1
|
||||||
self.body.append('\n\n')
|
self.body.append('\n\n')
|
||||||
if node.get('ids'):
|
|
||||||
self.next_section_ids.update(node['ids'])
|
|
||||||
|
|
||||||
def depart_section(self, node):
|
def depart_section(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
@ -1033,9 +1045,9 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
# just use "subparagraph", it's not numbered anyway
|
# just use "subparagraph", it's not numbered anyway
|
||||||
self.body.append(r'\%s%s{' % (self.sectionnames[-1], short))
|
self.body.append(r'\%s%s{' % (self.sectionnames[-1], short))
|
||||||
self.context.append('}\n')
|
self.context.append('}\n' + self.hypertarget_to(node.parent))
|
||||||
|
|
||||||
self.restrict_footnote(node)
|
self.restrict_footnote(node)
|
||||||
|
|
||||||
if self.next_section_ids:
|
if self.next_section_ids:
|
||||||
for id in self.next_section_ids:
|
for id in self.next_section_ids:
|
||||||
self.context[-1] += self.hypertarget(id, anchor=False)
|
self.context[-1] += self.hypertarget(id, anchor=False)
|
||||||
@ -1306,12 +1318,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def depart_table(self, node):
|
def depart_table(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
labels = '' # type: unicode
|
labels = self.hypertarget_to(node)
|
||||||
for labelid in self.pop_hyperlink_ids('table'):
|
|
||||||
labels += self.hypertarget(labelid, anchor=False)
|
|
||||||
if node['ids']:
|
|
||||||
labels += self.hypertarget(node['ids'][0], anchor=False)
|
|
||||||
|
|
||||||
table_type = self.table.get_table_type()
|
table_type = self.table.get_table_type()
|
||||||
table = self.render(table_type + '.tex_t',
|
table = self.render(table_type + '.tex_t',
|
||||||
dict(table=self.table, labels=labels))
|
dict(table=self.table, labels=labels))
|
||||||
@ -1758,16 +1765,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
|
|
||||||
def visit_figure(self, node):
|
def visit_figure(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
ids = '' # type: unicode
|
labels = self.hypertarget_to(node)
|
||||||
for id in self.pop_hyperlink_ids('figure'):
|
|
||||||
ids += self.hypertarget(id, anchor=False)
|
|
||||||
if node['ids']:
|
|
||||||
ids += self.hypertarget(node['ids'][0], anchor=False)
|
|
||||||
self.restrict_footnote(node)
|
self.restrict_footnote(node)
|
||||||
if (len(node.children) and
|
|
||||||
isinstance(node.children[0], nodes.image) and
|
|
||||||
node.children[0]['ids']):
|
|
||||||
ids += self.hypertarget(node.children[0]['ids'][0], anchor=False)
|
|
||||||
if self.table:
|
if self.table:
|
||||||
# TODO: support align option
|
# TODO: support align option
|
||||||
if 'width' in node:
|
if 'width' in node:
|
||||||
@ -1779,7 +1778,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.body.append('\\begin{sphinxfigure-in-table}\n\\centering\n')
|
self.body.append('\\begin{sphinxfigure-in-table}\n\\centering\n')
|
||||||
if any(isinstance(child, nodes.caption) for child in node):
|
if any(isinstance(child, nodes.caption) for child in node):
|
||||||
self.body.append('\\capstart')
|
self.body.append('\\capstart')
|
||||||
self.context.append(ids + '\\end{sphinxfigure-in-table}\\relax\n')
|
self.context.append(labels + '\\end{sphinxfigure-in-table}\\relax\n')
|
||||||
elif node.get('align', '') in ('left', 'right'):
|
elif node.get('align', '') in ('left', 'right'):
|
||||||
length = None
|
length = None
|
||||||
if 'width' in node:
|
if 'width' in node:
|
||||||
@ -1788,7 +1787,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
length = self.latex_image_length(node[0]['width'])
|
length = self.latex_image_length(node[0]['width'])
|
||||||
self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
|
self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
|
||||||
(node['align'] == 'right' and 'r' or 'l', length or '0pt'))
|
(node['align'] == 'right' and 'r' or 'l', length or '0pt'))
|
||||||
self.context.append(ids + '\\end{wrapfigure}\n')
|
self.context.append(labels + '\\end{wrapfigure}\n')
|
||||||
elif self.in_minipage:
|
elif self.in_minipage:
|
||||||
self.body.append('\n\\begin{center}')
|
self.body.append('\n\\begin{center}')
|
||||||
self.context.append('\\end{center}\n')
|
self.context.append('\\end{center}\n')
|
||||||
@ -1797,7 +1796,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.elements['figure_align'])
|
self.elements['figure_align'])
|
||||||
if any(isinstance(child, nodes.caption) for child in node):
|
if any(isinstance(child, nodes.caption) for child in node):
|
||||||
self.body.append('\\capstart\n')
|
self.body.append('\\capstart\n')
|
||||||
self.context.append(ids + '\\end{figure}\n')
|
self.context.append(labels + '\\end{figure}\n')
|
||||||
|
|
||||||
def depart_figure(self, node):
|
def depart_figure(self, node):
|
||||||
# type: (nodes.Node) -> None
|
# type: (nodes.Node) -> None
|
||||||
@ -1899,6 +1898,11 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
anchor = not self.in_title
|
anchor = not self.in_title
|
||||||
self.body.append(self.hypertarget(id, anchor=anchor))
|
self.body.append(self.hypertarget(id, anchor=anchor))
|
||||||
|
|
||||||
|
# skip if visitor for next node supports hyperlink
|
||||||
|
next_node = node.next_node(ascend=True)
|
||||||
|
if isinstance(next_node, HYPERLINK_SUPPORT_NODES):
|
||||||
|
return
|
||||||
|
|
||||||
# postpone the labels until after the sectioning command
|
# postpone the labels until after the sectioning command
|
||||||
parindex = node.parent.index(node)
|
parindex = node.parent.index(node)
|
||||||
try:
|
try:
|
||||||
@ -1912,22 +1916,16 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
node.parent.parent.index(node.parent)]
|
node.parent.parent.index(node.parent)]
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
if isinstance(next, nodes.section):
|
domain = self.builder.env.get_domain('std')
|
||||||
|
figtype = domain.get_figtype(next)
|
||||||
|
if figtype and domain.get_numfig_title(next):
|
||||||
|
ids = set()
|
||||||
|
# labels for figures go in the figure body, not before
|
||||||
if node.get('refid'):
|
if node.get('refid'):
|
||||||
self.next_section_ids.add(node['refid'])
|
ids.add(node['refid'])
|
||||||
self.next_section_ids.update(node['ids'])
|
ids.update(node['ids'])
|
||||||
|
self.push_hyperlink_ids(figtype, ids)
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
domain = self.builder.env.get_domain('std')
|
|
||||||
figtype = domain.get_figtype(next)
|
|
||||||
if figtype and domain.get_numfig_title(next):
|
|
||||||
ids = set()
|
|
||||||
# labels for figures go in the figure body, not before
|
|
||||||
if node.get('refid'):
|
|
||||||
ids.add(node['refid'])
|
|
||||||
ids.update(node['ids'])
|
|
||||||
self.push_hyperlink_ids(figtype, ids)
|
|
||||||
return
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
if 'refuri' in node:
|
if 'refuri' in node:
|
||||||
@ -2232,15 +2230,10 @@ class LaTeXTranslator(nodes.NodeVisitor):
|
|||||||
self.in_parsed_literal += 1
|
self.in_parsed_literal += 1
|
||||||
self.body.append('\\begin{sphinxalltt}\n')
|
self.body.append('\\begin{sphinxalltt}\n')
|
||||||
else:
|
else:
|
||||||
ids = '' # type: unicode
|
labels = self.hypertarget_to(node)
|
||||||
for id in self.pop_hyperlink_ids('code-block'):
|
|
||||||
ids += self.hypertarget(id, anchor=False)
|
|
||||||
if node['ids']:
|
|
||||||
# suppress with anchor=False \phantomsection insertion
|
|
||||||
ids += self.hypertarget(node['ids'][0], anchor=False)
|
|
||||||
# LaTeX code will insert \phantomsection prior to \label
|
# LaTeX code will insert \phantomsection prior to \label
|
||||||
if ids and not self.in_footnote:
|
if labels and not self.in_footnote:
|
||||||
self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + ids + '}')
|
self.body.append('\n\\def\\sphinxLiteralBlockLabel{' + labels + '}')
|
||||||
code = node.astext()
|
code = node.astext()
|
||||||
lang = self.hlsettingstack[-1][0]
|
lang = self.hlsettingstack[-1][0]
|
||||||
linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
|
linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
|
||||||
|
7
tests/roots/test-latex-labels/conf.py
Normal file
7
tests/roots/test-latex-labels/conf.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'test.tex', 'The basic Sphinx documentation for testing', 'Sphinx', 'report')
|
||||||
|
]
|
68
tests/roots/test-latex-labels/index.rst
Normal file
68
tests/roots/test-latex-labels/index.rst
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
latex-labels
|
||||||
|
============
|
||||||
|
|
||||||
|
figures
|
||||||
|
-------
|
||||||
|
|
||||||
|
.. _figure1:
|
||||||
|
.. _figure2:
|
||||||
|
|
||||||
|
.. figure:: logo.jpg
|
||||||
|
|
||||||
|
labeled figure
|
||||||
|
|
||||||
|
.. figure:: logo.jpg
|
||||||
|
:name: figure3
|
||||||
|
|
||||||
|
labeled figure
|
||||||
|
|
||||||
|
code-blocks
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. _codeblock1:
|
||||||
|
.. _codeblock2:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
blah blah blah
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
:name: codeblock3
|
||||||
|
|
||||||
|
blah blah blah
|
||||||
|
|
||||||
|
tables
|
||||||
|
------
|
||||||
|
|
||||||
|
.. _table1:
|
||||||
|
.. _table2:
|
||||||
|
|
||||||
|
.. table:: table caption
|
||||||
|
|
||||||
|
==== ====
|
||||||
|
head head
|
||||||
|
cell cell
|
||||||
|
==== ====
|
||||||
|
|
||||||
|
.. table:: table caption
|
||||||
|
:name: table3
|
||||||
|
|
||||||
|
==== ====
|
||||||
|
head head
|
||||||
|
cell cell
|
||||||
|
==== ====
|
||||||
|
|
||||||
|
.. _section1:
|
||||||
|
.. _section2:
|
||||||
|
|
||||||
|
subsection
|
||||||
|
----------
|
||||||
|
|
||||||
|
.. _section3:
|
||||||
|
|
||||||
|
subsubsection
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
|
||||||
|
otherdoc
|
2
tests/roots/test-latex-labels/otherdoc.rst
Normal file
2
tests/roots/test-latex-labels/otherdoc.rst
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
otherdoc
|
||||||
|
========
|
@ -1264,3 +1264,47 @@ def test_latex_glossary(app, status, warning):
|
|||||||
r'\label{\detokenize{index:term-electron}}}] \leavevmode' in result)
|
r'\label{\detokenize{index:term-electron}}}] \leavevmode' in result)
|
||||||
assert (u'\\item[{über\\index{über|textbf}\\phantomsection'
|
assert (u'\\item[{über\\index{über|textbf}\\phantomsection'
|
||||||
r'\label{\detokenize{index:term-uber}}}] \leavevmode' in result)
|
r'\label{\detokenize{index:term-uber}}}] \leavevmode' in result)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('latex', testroot='latex-labels')
|
||||||
|
def test_latex_labels(app, status, warning):
|
||||||
|
app.builder.build_all()
|
||||||
|
|
||||||
|
result = (app.outdir / 'test.tex').text(encoding='utf8')
|
||||||
|
|
||||||
|
# figures
|
||||||
|
assert (r'\caption{labeled figure}'
|
||||||
|
r'\label{\detokenize{index:id1}}'
|
||||||
|
r'\label{\detokenize{index:figure2}}'
|
||||||
|
r'\label{\detokenize{index:figure1}}'
|
||||||
|
r'\end{figure}' in result)
|
||||||
|
assert (r'\caption{labeled figure}'
|
||||||
|
r'\label{\detokenize{index:figure3}}'
|
||||||
|
r'\end{figure}' in result)
|
||||||
|
|
||||||
|
# code-blocks
|
||||||
|
assert (r'\def\sphinxLiteralBlockLabel{'
|
||||||
|
r'\label{\detokenize{index:codeblock2}}'
|
||||||
|
r'\label{\detokenize{index:codeblock1}}}' in result)
|
||||||
|
assert (r'\def\sphinxLiteralBlockLabel{'
|
||||||
|
r'\label{\detokenize{index:codeblock3}}}' in result)
|
||||||
|
|
||||||
|
# tables
|
||||||
|
assert (r'\sphinxcaption{table caption}'
|
||||||
|
r'\label{\detokenize{index:id2}}'
|
||||||
|
r'\label{\detokenize{index:table2}}'
|
||||||
|
r'\label{\detokenize{index:table1}}' in result)
|
||||||
|
assert (r'\sphinxcaption{table caption}'
|
||||||
|
r'\label{\detokenize{index:table3}}' in result)
|
||||||
|
|
||||||
|
# sections
|
||||||
|
assert ('\\chapter{subsection}\n'
|
||||||
|
r'\label{\detokenize{index:subsection}}'
|
||||||
|
r'\label{\detokenize{index:section2}}'
|
||||||
|
r'\label{\detokenize{index:section1}}' in result)
|
||||||
|
assert ('\\section{subsubsection}\n'
|
||||||
|
r'\label{\detokenize{index:subsubsection}}'
|
||||||
|
r'\label{\detokenize{index:section3}}' in result)
|
||||||
|
assert ('\\subsection{otherdoc}\n'
|
||||||
|
r'\label{\detokenize{otherdoc:otherdoc}}'
|
||||||
|
r'\label{\detokenize{otherdoc::doc}}' in result)
|
||||||
|
Loading…
Reference in New Issue
Block a user