diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 0338a6b57..c2290f910 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -22,7 +22,7 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription -from sphinx.util import ws_re +from sphinx.util import ws_re, get_figtype from sphinx.util.nodes import clean_astext, make_refnode from sphinx.util.compat import Directive @@ -466,6 +466,9 @@ class StandardDomain(Domain): # links to headings or arbitrary labels 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis, warn_dangling=True), + # links to labels of numbered figures, tables and code-blocks + 'numref': XRefRole(lowercase=True, + warn_dangling=True), # links to labels, without a different title 'keyword': XRefRole(warn_dangling=True), } @@ -489,6 +492,7 @@ class StandardDomain(Domain): 'term': 'term not in glossary: %(target)s', 'ref': 'undefined label: %(target)s (if the link has no caption ' 'the label must precede a section header)', + 'numref': 'undefined label: %(target)s', 'keyword': 'unknown keyword: %(target)s', } @@ -574,6 +578,26 @@ class StandardDomain(Domain): continue labels[name] = docname, labelid, sectname + def build_reference_node(self, fromdocname, builder, + docname, labelid, sectname): + newnode = nodes.reference('', '', internal=True) + innernode = nodes.emphasis(sectname, sectname) + if docname == fromdocname: + newnode['refid'] = labelid + else: + # set more info in contnode; in case the + # get_relative_uri call raises NoUri, + # the builder will then have to resolve these + contnode = addnodes.pending_xref('') + contnode['refdocname'] = docname + contnode['refsectname'] = sectname + newnode['refuri'] = builder.get_relative_uri( + fromdocname, docname) + if labelid: + newnode['refuri'] += '#' + labelid + newnode.append(innernode) + return newnode + def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if typ == 'ref': @@ -589,23 +613,35 @@ class StandardDomain(Domain): ('', '', '')) if not docname: return None - newnode = nodes.reference('', '', internal=True) - innernode = nodes.emphasis(sectname, sectname) - if docname == fromdocname: - newnode['refid'] = labelid + + return self.build_reference_node(fromdocname, builder, + docname, labelid, sectname) + elif typ == 'numref': + docname, labelid = self.data['anonlabels'].get(target, ('', '')) + if not docname: + return None + + if env.config.numfig is False: + env.warn(fromdocname, 'numfig is disabled. :numref: is ignored.') + return contnode + + try: + target = env.get_doctree(docname).ids[labelid] + figtype = get_figtype(target) + figure_id = target['ids'][0] + fignumber = env.toc_fignumbers[docname][figtype][figure_id] + except IndexError: + return None + + title = contnode.astext() + if labelid == title: + prefix = env.config.numfig_prefix.get(figtype, '') + title = prefix + '.'.join(map(str, fignumber)) else: - # set more info in contnode; in case the - # get_relative_uri call raises NoUri, - # the builder will then have to resolve these - contnode = addnodes.pending_xref('') - contnode['refdocname'] = docname - contnode['refsectname'] = sectname - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) - if labelid: - newnode['refuri'] += '#' + labelid - newnode.append(innernode) - return newnode + title = title.replace('#', '.'.join(map(str, fignumber))) + + return self.build_reference_node(fromdocname, builder, + docname, labelid, title) elif typ == 'keyword': # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.data['labels'].get(target, ('', '', '')) diff --git a/sphinx/environment.py b/sphinx/environment.py index d9be0be5f..52d5ff082 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -37,7 +37,7 @@ from docutils.frontend import OptionParser from sphinx import addnodes from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ - FilenameUniqDict + FilenameUniqDict, get_figtype from sphinx.util.nodes import clean_astext, make_refnode, WarningStream from sphinx.util.osutil import SEP, find_catalog_files, getcwd, fs_encoding from sphinx.util.console import bold, purple @@ -1705,9 +1705,6 @@ class BuildEnvironment: self.toc_fignumbers = {} fignum_counter = {} - def has_child(node, cls): - return any(isinstance(child, cls) for child in node) - def get_section_number(docname, section): anchorname = '#' + section['ids'][0] secnumbers = self.toc_secnumbers.get(docname, {}) @@ -1749,16 +1746,10 @@ class BuildEnvironment: continue - if isinstance(subnode, nodes.figure): - figure_id = subnode['ids'][0] - register_fignumber(docname, secnum, 'figure', figure_id) - elif isinstance(subnode, nodes.table): - table_id = subnode['ids'][0] - register_fignumber(docname, secnum, 'table', table_id) - elif isinstance(subnode, nodes.container): - if has_child(subnode, nodes.literal_block): - code_block_id = subnode['ids'][0] - register_fignumber(docname, secnum, 'code-block', code_block_id) + figtype = get_figtype(subnode) + if figtype: + register_fignumber(docname, secnum, + figtype, subnode['ids'][0]) _walk_doctree(docname, subnode, secnum) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index e7277520b..024e25b1b 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -475,3 +475,20 @@ class PeekableIterator(object): item = next(self) self.push(item) return item + + +def get_figtype(node): + """Return figtype for given node.""" + def has_child(node, cls): + return any(isinstance(child, cls) for child in node) + + from docutils import nodes + if isinstance(node, nodes.figure): + return 'figure' + elif isinstance(node, nodes.table): + return 'table' + elif isinstance(node, nodes.container): + if has_child(node, nodes.literal_block): + return 'code-block' + + return None