def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, type: str, target: str, node: pending_xref, contnode: Element ) -> Optional[Element]: if "." in target: parts = target.split(".") clsname = parts[0] target = parts[1] else: clsname = node.get('dml:class') matches = self.find_obj(env, clsname, target, type, 1) if not matches: return None elif len(matches) > 1: logger.warning(__('more than one target found for cross-reference %r: %s'), target, ', '.join(match[0] for match in matches), type='ref', subtype='python', location=node) name, obj = matches[0] # determine the content of the reference by conditions content = find_pending_xref_condition(node, 'resolved') if content: children = content.children else: # if not found, use contnode children = [contnode] return make_refnode(builder, fromdocname, obj[0], obj[1], children, name)
def find_pending_xref_condition(self, node: pending_xref, conditions: Sequence[str] ) -> Optional[List[Node]]: for condition in conditions: matched = find_pending_xref_condition(node, condition) if matched: return matched.children else: return None
def run(self, **kwargs: Any) -> None: for node in self.document.traverse(addnodes.pending_xref): contnode = cast(nodes.TextElement, node[0].deepcopy()) newnode = None typ = node['reftype'] target = node['reftarget'] refdoc = node.get('refdoc', self.env.docname) domain = None try: if 'refdomain' in node and node['refdomain']: # let the domain try to resolve the reference try: domain = self.env.domains[node['refdomain']] except KeyError as exc: raise NoUri(target, typ) from exc newnode = domain.resolve_xref(self.env, refdoc, self.app.builder, typ, target, node, contnode) # really hardwired reference types elif typ == 'any': newnode = self.resolve_anyref(refdoc, node, contnode) # no new node found? try the missing-reference event if newnode is None: newnode = self.app.emit_firstresult( 'missing-reference', self.env, node, contnode, allowed_exceptions=(NoUri, )) # still not found? warn if node wishes to be warned about or # we are in nit-picky mode if newnode is None: self.warn_missing_reference(refdoc, typ, target, node, domain) except NoUri: newnode = None if newnode: newnodes = [newnode] # type: List[Node] else: newnodes = [contnode] if newnode is None and isinstance( node[0], addnodes.pending_xref_condition): matched = find_pending_xref_condition(node, "*") if matched: newnodes = matched.children else: logger.warning(__( 'Could not determine the fallback text for the ' 'cross-reference. Might be a bug.'), location=node) node.replace_self(newnodes)
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement) -> nodes.reference: """Attempt to resolve a missing reference via intersphinx references.""" target = node['reftarget'] inventories = InventoryAdapter(env) objtypes: List[str] = None if node['reftype'] == 'any': # we search anything! objtypes = [ '%s:%s' % (domain.name, objtype) for domain in env.domains.values() for objtype in domain.object_types ] domain = None else: domain = node.get('refdomain') if not domain: # only objects in domains are in the inventory return None objtypes = env.get_domain(domain).objtypes_for_role(node['reftype']) if not objtypes: return None objtypes = ['%s:%s' % (domain, objtype) for objtype in objtypes] if 'std:cmdoption' in objtypes: # until Sphinx-1.6, cmdoptions are stored as std:option objtypes.append('std:option') if 'py:attribute' in objtypes: # Since Sphinx-2.1, properties are stored as py:method objtypes.append('py:method') # determine the contnode by pending_xref_condition content = find_pending_xref_condition(node, 'resolved') if content: # resolved condition found. contnodes = content.children contnode = content.children[0] # type: ignore else: # not resolved. Use the given contnode contnodes = [contnode] to_try = [(inventories.main_inventory, target)] if domain: full_qualified_name = env.get_domain(domain).get_full_qualified_name( node) if full_qualified_name: to_try.append((inventories.main_inventory, full_qualified_name)) in_set = None if ':' in target: # first part may be the foreign doc set name setname, newtarget = target.split(':', 1) if setname in inventories.named_inventory: in_set = setname to_try.append((inventories.named_inventory[setname], newtarget)) if domain: node['reftarget'] = newtarget full_qualified_name = env.get_domain( domain).get_full_qualified_name(node) if full_qualified_name: to_try.append((inventories.named_inventory[setname], full_qualified_name)) for inventory, target in to_try: for objtype in objtypes: if objtype not in inventory or target not in inventory[objtype]: continue proj, version, uri, dispname = inventory[objtype][target] if '://' not in uri and node.get('refdoc'): # get correct path in case of subdirectories uri = path.join(relative_path(node['refdoc'], '.'), uri) if version: reftitle = _('(in %s v%s)') % (proj, version) else: reftitle = _('(in %s)') % (proj, ) newnode = nodes.reference('', '', internal=False, refuri=uri, reftitle=reftitle) if node.get('refexplicit'): # use whatever title was given newnode.extend(contnodes) elif dispname == '-' or \ (domain == 'std' and node['reftype'] == 'keyword'): # use whatever title was given, but strip prefix title = contnode.astext() if in_set and title.startswith(in_set + ':'): newnode.append( contnode.__class__(title[len(in_set) + 1:], title[len(in_set) + 1:])) else: newnode.extend(contnodes) else: # else use the given display name (used for :ref:) newnode.append(contnode.__class__(dispname, dispname)) return newnode # at least get rid of the ':' in the target if no explicit title given if in_set is not None and not node.get('refexplicit', True): if len(contnode) and isinstance(contnode[0], nodes.Text): contnode[0] = nodes.Text(newtarget, contnode[0].rawsource) return None