Mercurial > repos > shellac > guppy_basecaller
diff env/lib/python3.7/site-packages/docutils/transforms/references.py @ 2:6af9afd405e9 draft
"planemo upload commit 0a63dd5f4d38a1f6944587f52a8cd79874177fc1"
author | shellac |
---|---|
date | Thu, 14 May 2020 14:56:58 -0400 |
parents | 26e78fe6e8c4 |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.7/site-packages/docutils/transforms/references.py Thu May 14 14:56:58 2020 -0400 @@ -0,0 +1,911 @@ +# $Id: references.py 8387 2019-09-06 13:16:34Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Transforms for resolving references. +""" + +__docformat__ = 'reStructuredText' + +import sys +import re +from docutils import nodes, utils +from docutils.transforms import TransformError, Transform + + +class PropagateTargets(Transform): + + """ + Propagate empty internal targets to the next element. + + Given the following nodes:: + + <target ids="internal1" names="internal1"> + <target anonymous="1" ids="id1"> + <target ids="internal2" names="internal2"> + <paragraph> + This is a test. + + PropagateTargets propagates the ids and names of the internal + targets preceding the paragraph to the paragraph itself:: + + <target refid="internal1"> + <target anonymous="1" refid="id1"> + <target refid="internal2"> + <paragraph ids="internal2 id1 internal1" names="internal2 internal1"> + This is a test. + """ + + default_priority = 260 + + def apply(self): + for target in self.document.traverse(nodes.target): + # Only block-level targets without reference (like ".. target:"): + if (isinstance(target.parent, nodes.TextElement) or + (target.hasattr('refid') or target.hasattr('refuri') or + target.hasattr('refname'))): + continue + assert len(target) == 0, 'error: block-level target has children' + next_node = target.next_node(ascend=True) + # Do not move names and ids into Invisibles (we'd lose the + # attributes) or different Targetables (e.g. footnotes). + if (next_node is not None and + ((not isinstance(next_node, nodes.Invisible) and + not isinstance(next_node, nodes.Targetable)) or + isinstance(next_node, nodes.target))): + next_node['ids'].extend(target['ids']) + next_node['names'].extend(target['names']) + # Set defaults for next_node.expect_referenced_by_name/id. + if not hasattr(next_node, 'expect_referenced_by_name'): + next_node.expect_referenced_by_name = {} + if not hasattr(next_node, 'expect_referenced_by_id'): + next_node.expect_referenced_by_id = {} + for id in target['ids']: + # Update IDs to node mapping. + self.document.ids[id] = next_node + # If next_node is referenced by id ``id``, this + # target shall be marked as referenced. + next_node.expect_referenced_by_id[id] = target + for name in target['names']: + next_node.expect_referenced_by_name[name] = target + # If there are any expect_referenced_by_... attributes + # in target set, copy them to next_node. + next_node.expect_referenced_by_name.update( + getattr(target, 'expect_referenced_by_name', {})) + next_node.expect_referenced_by_id.update( + getattr(target, 'expect_referenced_by_id', {})) + # Set refid to point to the first former ID of target + # which is now an ID of next_node. + target['refid'] = target['ids'][0] + # Clear ids and names; they have been moved to + # next_node. + target['ids'] = [] + target['names'] = [] + self.document.note_refid(target) + + +class AnonymousHyperlinks(Transform): + + """ + Link anonymous references to targets. Given:: + + <paragraph> + <reference anonymous="1"> + internal + <reference anonymous="1"> + external + <target anonymous="1" ids="id1"> + <target anonymous="1" ids="id2" refuri="http://external"> + + Corresponding references are linked via "refid" or resolved via "refuri":: + + <paragraph> + <reference anonymous="1" refid="id1"> + text + <reference anonymous="1" refuri="http://external"> + external + <target anonymous="1" ids="id1"> + <target anonymous="1" ids="id2" refuri="http://external"> + """ + + default_priority = 440 + + def apply(self): + anonymous_refs = [] + anonymous_targets = [] + for node in self.document.traverse(nodes.reference): + if node.get('anonymous'): + anonymous_refs.append(node) + for node in self.document.traverse(nodes.target): + if node.get('anonymous'): + anonymous_targets.append(node) + if len(anonymous_refs) \ + != len(anonymous_targets): + msg = self.document.reporter.error( + 'Anonymous hyperlink mismatch: %s references but %s ' + 'targets.\nSee "backrefs" attribute for IDs.' + % (len(anonymous_refs), len(anonymous_targets))) + msgid = self.document.set_id(msg) + for ref in anonymous_refs: + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + return + for ref, target in zip(anonymous_refs, anonymous_targets): + target.referenced = 1 + while True: + if target.hasattr('refuri'): + ref['refuri'] = target['refuri'] + ref.resolved = 1 + break + else: + if not target['ids']: + # Propagated target. + target = self.document.ids[target['refid']] + continue + ref['refid'] = target['ids'][0] + self.document.note_refid(ref) + break + + +class IndirectHyperlinks(Transform): + + """ + a) Indirect external references:: + + <paragraph> + <reference refname="indirect external"> + indirect external + <target id="id1" name="direct external" + refuri="http://indirect"> + <target id="id2" name="indirect external" + refname="direct external"> + + The "refuri" attribute is migrated back to all indirect targets + from the final direct target (i.e. a target not referring to + another indirect target):: + + <paragraph> + <reference refname="indirect external"> + indirect external + <target id="id1" name="direct external" + refuri="http://indirect"> + <target id="id2" name="indirect external" + refuri="http://indirect"> + + Once the attribute is migrated, the preexisting "refname" attribute + is dropped. + + b) Indirect internal references:: + + <target id="id1" name="final target"> + <paragraph> + <reference refname="indirect internal"> + indirect internal + <target id="id2" name="indirect internal 2" + refname="final target"> + <target id="id3" name="indirect internal" + refname="indirect internal 2"> + + Targets which indirectly refer to an internal target become one-hop + indirect (their "refid" attributes are directly set to the internal + target's "id"). References which indirectly refer to an internal + target become direct internal references:: + + <target id="id1" name="final target"> + <paragraph> + <reference refid="id1"> + indirect internal + <target id="id2" name="indirect internal 2" refid="id1"> + <target id="id3" name="indirect internal" refid="id1"> + """ + + default_priority = 460 + + def apply(self): + for target in self.document.indirect_targets: + if not target.resolved: + self.resolve_indirect_target(target) + self.resolve_indirect_references(target) + + def resolve_indirect_target(self, target): + refname = target.get('refname') + if refname is None: + reftarget_id = target['refid'] + else: + reftarget_id = self.document.nameids.get(refname) + if not reftarget_id: + # Check the unknown_reference_resolvers + for resolver_function in \ + self.document.transformer.unknown_reference_resolvers: + if resolver_function(target): + break + else: + self.nonexistent_indirect_target(target) + return + reftarget = self.document.ids[reftarget_id] + reftarget.note_referenced_by(id=reftarget_id) + if isinstance(reftarget, nodes.target) \ + and not reftarget.resolved and reftarget.hasattr('refname'): + if hasattr(target, 'multiply_indirect'): + #and target.multiply_indirect): + #del target.multiply_indirect + self.circular_indirect_reference(target) + return + target.multiply_indirect = 1 + self.resolve_indirect_target(reftarget) # multiply indirect + del target.multiply_indirect + if reftarget.hasattr('refuri'): + target['refuri'] = reftarget['refuri'] + if 'refid' in target: + del target['refid'] + elif reftarget.hasattr('refid'): + target['refid'] = reftarget['refid'] + self.document.note_refid(target) + else: + if reftarget['ids']: + target['refid'] = reftarget_id + self.document.note_refid(target) + else: + self.nonexistent_indirect_target(target) + return + if refname is not None: + del target['refname'] + target.resolved = 1 + + def nonexistent_indirect_target(self, target): + if target['refname'] in self.document.nameids: + self.indirect_target_error(target, 'which is a duplicate, and ' + 'cannot be used as a unique reference') + else: + self.indirect_target_error(target, 'which does not exist') + + def circular_indirect_reference(self, target): + self.indirect_target_error(target, 'forming a circular reference') + + def indirect_target_error(self, target, explanation): + naming = '' + reflist = [] + if target['names']: + naming = '"%s" ' % target['names'][0] + for name in target['names']: + reflist.extend(self.document.refnames.get(name, [])) + for id in target['ids']: + reflist.extend(self.document.refids.get(id, [])) + if target['ids']: + naming += '(id="%s")' % target['ids'][0] + msg = self.document.reporter.error( + 'Indirect hyperlink target %s refers to target "%s", %s.' + % (naming, target['refname'], explanation), base_node=target) + msgid = self.document.set_id(msg) + for ref in utils.uniq(reflist): + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + target.resolved = 1 + + def resolve_indirect_references(self, target): + if target.hasattr('refid'): + attname = 'refid' + call_method = self.document.note_refid + elif target.hasattr('refuri'): + attname = 'refuri' + call_method = None + else: + return + attval = target[attname] + for name in target['names']: + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + del ref['refname'] + ref[attname] = attval + if call_method: + call_method(ref) + ref.resolved = 1 + if isinstance(ref, nodes.target): + self.resolve_indirect_references(ref) + for id in target['ids']: + reflist = self.document.refids.get(id, []) + if reflist: + target.note_referenced_by(id=id) + for ref in reflist: + if ref.resolved: + continue + del ref['refid'] + ref[attname] = attval + if call_method: + call_method(ref) + ref.resolved = 1 + if isinstance(ref, nodes.target): + self.resolve_indirect_references(ref) + + +class ExternalTargets(Transform): + + """ + Given:: + + <paragraph> + <reference refname="direct external"> + direct external + <target id="id1" name="direct external" refuri="http://direct"> + + The "refname" attribute is replaced by the direct "refuri" attribute:: + + <paragraph> + <reference refuri="http://direct"> + direct external + <target id="id1" name="direct external" refuri="http://direct"> + """ + + default_priority = 640 + + def apply(self): + for target in self.document.traverse(nodes.target): + if target.hasattr('refuri'): + refuri = target['refuri'] + for name in target['names']: + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + del ref['refname'] + ref['refuri'] = refuri + ref.resolved = 1 + + +class InternalTargets(Transform): + + default_priority = 660 + + def apply(self): + for target in self.document.traverse(nodes.target): + if not target.hasattr('refuri') and not target.hasattr('refid'): + self.resolve_reference_ids(target) + + def resolve_reference_ids(self, target): + """ + Given:: + + <paragraph> + <reference refname="direct internal"> + direct internal + <target id="id1" name="direct internal"> + + The "refname" attribute is replaced by "refid" linking to the target's + "id":: + + <paragraph> + <reference refid="id1"> + direct internal + <target id="id1" name="direct internal"> + """ + for name in target['names']: + refid = self.document.nameids.get(name) + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + if refid: + del ref['refname'] + ref['refid'] = refid + ref.resolved = 1 + + +class Footnotes(Transform): + + """ + Assign numbers to autonumbered footnotes, and resolve links to footnotes, + citations, and their references. + + Given the following ``document`` as input:: + + <document> + <paragraph> + A labeled autonumbered footnote referece: + <footnote_reference auto="1" id="id1" refname="footnote"> + <paragraph> + An unlabeled autonumbered footnote referece: + <footnote_reference auto="1" id="id2"> + <footnote auto="1" id="id3"> + <paragraph> + Unlabeled autonumbered footnote. + <footnote auto="1" id="footnote" name="footnote"> + <paragraph> + Labeled autonumbered footnote. + + Auto-numbered footnotes have attribute ``auto="1"`` and no label. + Auto-numbered footnote_references have no reference text (they're + empty elements). When resolving the numbering, a ``label`` element + is added to the beginning of the ``footnote``, and reference text + to the ``footnote_reference``. + + The transformed result will be:: + + <document> + <paragraph> + A labeled autonumbered footnote referece: + <footnote_reference auto="1" id="id1" refid="footnote"> + 2 + <paragraph> + An unlabeled autonumbered footnote referece: + <footnote_reference auto="1" id="id2" refid="id3"> + 1 + <footnote auto="1" id="id3" backrefs="id2"> + <label> + 1 + <paragraph> + Unlabeled autonumbered footnote. + <footnote auto="1" id="footnote" name="footnote" backrefs="id1"> + <label> + 2 + <paragraph> + Labeled autonumbered footnote. + + Note that the footnotes are not in the same order as the references. + + The labels and reference text are added to the auto-numbered ``footnote`` + and ``footnote_reference`` elements. Footnote elements are backlinked to + their references via "refids" attributes. References are assigned "id" + and "refid" attributes. + + After adding labels and reference text, the "auto" attributes can be + ignored. + """ + + default_priority = 620 + + autofootnote_labels = None + """Keep track of unlabeled autonumbered footnotes.""" + + symbols = [ + # Entries 1-4 and 6 below are from section 12.51 of + # The Chicago Manual of Style, 14th edition. + '*', # asterisk/star + u'\u2020', # dagger † + u'\u2021', # double dagger ‡ + u'\u00A7', # section mark § + u'\u00B6', # paragraph mark (pilcrow) ¶ + # (parallels ['||'] in CMoS) + '#', # number sign + # The entries below were chosen arbitrarily. + u'\u2660', # spade suit ♠ + u'\u2665', # heart suit ♥ + u'\u2666', # diamond suit ♦ + u'\u2663', # club suit ♣ + ] + + def apply(self): + self.autofootnote_labels = [] + startnum = self.document.autofootnote_start + self.document.autofootnote_start = self.number_footnotes(startnum) + self.number_footnote_references(startnum) + self.symbolize_footnotes() + self.resolve_footnotes_and_citations() + + def number_footnotes(self, startnum): + """ + Assign numbers to autonumbered footnotes. + + For labeled autonumbered footnotes, copy the number over to + corresponding footnote references. + """ + for footnote in self.document.autofootnotes: + while True: + label = str(startnum) + startnum += 1 + if label not in self.document.nameids: + break + footnote.insert(0, nodes.label('', label)) + for name in footnote['names']: + for ref in self.document.footnote_refs.get(name, []): + ref += nodes.Text(label) + ref.delattr('refname') + assert len(footnote['ids']) == len(ref['ids']) == 1 + ref['refid'] = footnote['ids'][0] + footnote.add_backref(ref['ids'][0]) + self.document.note_refid(ref) + ref.resolved = 1 + if not footnote['names'] and not footnote['dupnames']: + footnote['names'].append(label) + self.document.note_explicit_target(footnote, footnote) + self.autofootnote_labels.append(label) + return startnum + + def number_footnote_references(self, startnum): + """Assign numbers to autonumbered footnote references.""" + i = 0 + for ref in self.document.autofootnote_refs: + if ref.resolved or ref.hasattr('refid'): + continue + try: + label = self.autofootnote_labels[i] + except IndexError: + msg = self.document.reporter.error( + 'Too many autonumbered footnote references: only %s ' + 'corresponding footnotes available.' + % len(self.autofootnote_labels), base_node=ref) + msgid = self.document.set_id(msg) + for ref in self.document.autofootnote_refs[i:]: + if ref.resolved or ref.hasattr('refname'): + continue + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + break + ref += nodes.Text(label) + id = self.document.nameids[label] + footnote = self.document.ids[id] + ref['refid'] = id + self.document.note_refid(ref) + assert len(ref['ids']) == 1 + footnote.add_backref(ref['ids'][0]) + ref.resolved = 1 + i += 1 + + def symbolize_footnotes(self): + """Add symbols indexes to "[*]"-style footnotes and references.""" + labels = [] + for footnote in self.document.symbol_footnotes: + reps, index = divmod(self.document.symbol_footnote_start, + len(self.symbols)) + labeltext = self.symbols[index] * (reps + 1) + labels.append(labeltext) + footnote.insert(0, nodes.label('', labeltext)) + self.document.symbol_footnote_start += 1 + self.document.set_id(footnote) + i = 0 + for ref in self.document.symbol_footnote_refs: + try: + ref += nodes.Text(labels[i]) + except IndexError: + msg = self.document.reporter.error( + 'Too many symbol footnote references: only %s ' + 'corresponding footnotes available.' % len(labels), + base_node=ref) + msgid = self.document.set_id(msg) + for ref in self.document.symbol_footnote_refs[i:]: + if ref.resolved or ref.hasattr('refid'): + continue + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + break + footnote = self.document.symbol_footnotes[i] + assert len(footnote['ids']) == 1 + ref['refid'] = footnote['ids'][0] + self.document.note_refid(ref) + footnote.add_backref(ref['ids'][0]) + i += 1 + + def resolve_footnotes_and_citations(self): + """ + Link manually-labeled footnotes and citations to/from their + references. + """ + for footnote in self.document.footnotes: + for label in footnote['names']: + if label in self.document.footnote_refs: + reflist = self.document.footnote_refs[label] + self.resolve_references(footnote, reflist) + for citation in self.document.citations: + for label in citation['names']: + if label in self.document.citation_refs: + reflist = self.document.citation_refs[label] + self.resolve_references(citation, reflist) + + def resolve_references(self, note, reflist): + assert len(note['ids']) == 1 + id = note['ids'][0] + for ref in reflist: + if ref.resolved: + continue + ref.delattr('refname') + ref['refid'] = id + assert len(ref['ids']) == 1 + note.add_backref(ref['ids'][0]) + ref.resolved = 1 + note.resolved = 1 + + +class CircularSubstitutionDefinitionError(Exception): pass + + +class Substitutions(Transform): + + """ + Given the following ``document`` as input:: + + <document> + <paragraph> + The + <substitution_reference refname="biohazard"> + biohazard + symbol is deservedly scary-looking. + <substitution_definition name="biohazard"> + <image alt="biohazard" uri="biohazard.png"> + + The ``substitution_reference`` will simply be replaced by the + contents of the corresponding ``substitution_definition``. + + The transformed result will be:: + + <document> + <paragraph> + The + <image alt="biohazard" uri="biohazard.png"> + symbol is deservedly scary-looking. + <substitution_definition name="biohazard"> + <image alt="biohazard" uri="biohazard.png"> + """ + + default_priority = 220 + """The Substitutions transform has to be applied very early, before + `docutils.tranforms.frontmatter.DocTitle` and others.""" + + def apply(self): + defs = self.document.substitution_defs + normed = self.document.substitution_names + subreflist = list(self.document.traverse(nodes.substitution_reference)) + nested = {} + for ref in subreflist: + refname = ref['refname'] + key = None + if refname in defs: + key = refname + else: + normed_name = refname.lower() + if normed_name in normed: + key = normed[normed_name] + if key is None: + msg = self.document.reporter.error( + 'Undefined substitution referenced: "%s".' + % refname, base_node=ref) + msgid = self.document.set_id(msg) + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + else: + subdef = defs[key] + parent = ref.parent + index = parent.index(ref) + if ('ltrim' in subdef.attributes + or 'trim' in subdef.attributes): + if index > 0 and isinstance(parent[index - 1], + nodes.Text): + parent[index - 1] = parent[index - 1].rstrip() + if ('rtrim' in subdef.attributes + or 'trim' in subdef.attributes): + if (len(parent) > index + 1 + and isinstance(parent[index + 1], nodes.Text)): + parent[index + 1] = parent[index + 1].lstrip() + subdef_copy = subdef.deepcopy() + try: + # Take care of nested substitution references: + for nested_ref in subdef_copy.traverse( + nodes.substitution_reference): + nested_name = normed[nested_ref['refname'].lower()] + if nested_name in nested.setdefault(nested_name, []): + raise CircularSubstitutionDefinitionError + else: + nested[nested_name].append(key) + nested_ref['ref-origin'] = ref + subreflist.append(nested_ref) + except CircularSubstitutionDefinitionError: + parent = ref.parent + if isinstance(parent, nodes.substitution_definition): + msg = self.document.reporter.error( + 'Circular substitution definition detected:', + nodes.literal_block(parent.rawsource, + parent.rawsource), + line=parent.line, base_node=parent) + parent.replace_self(msg) + else: + # find original ref substitution which cased this error + ref_origin = ref + while ref_origin.hasattr('ref-origin'): + ref_origin = ref_origin['ref-origin'] + msg = self.document.reporter.error( + 'Circular substitution definition referenced: ' + '"%s".' % refname, base_node=ref_origin) + msgid = self.document.set_id(msg) + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + else: + ref.replace_self(subdef_copy.children) + # register refname of the replacment node(s) + # (needed for resolution of references) + for node in subdef_copy.children: + if isinstance(node, nodes.Referential): + # HACK: verify refname attribute exists. + # Test with docs/dev/todo.txt, see. |donate| + if 'refname' in node: + self.document.note_refname(node) + + +class TargetNotes(Transform): + + """ + Creates a footnote for each external target in the text, and corresponding + footnote references after each reference. + """ + + default_priority = 540 + """The TargetNotes transform has to be applied after `IndirectHyperlinks` + but before `Footnotes`.""" + + + def __init__(self, document, startnode): + Transform.__init__(self, document, startnode=startnode) + + self.classes = startnode.details.get('class', []) + + def apply(self): + notes = {} + nodelist = [] + for target in self.document.traverse(nodes.target): + # Only external targets. + if not target.hasattr('refuri'): + continue + names = target['names'] + refs = [] + for name in names: + refs.extend(self.document.refnames.get(name, [])) + if not refs: + continue + footnote = self.make_target_footnote(target['refuri'], refs, + notes) + if target['refuri'] not in notes: + notes[target['refuri']] = footnote + nodelist.append(footnote) + # Take care of anonymous references. + for ref in self.document.traverse(nodes.reference): + if not ref.get('anonymous'): + continue + if ref.hasattr('refuri'): + footnote = self.make_target_footnote(ref['refuri'], [ref], + notes) + if ref['refuri'] not in notes: + notes[ref['refuri']] = footnote + nodelist.append(footnote) + self.startnode.replace_self(nodelist) + + def make_target_footnote(self, refuri, refs, notes): + if refuri in notes: # duplicate? + footnote = notes[refuri] + assert len(footnote['names']) == 1 + footnote_name = footnote['names'][0] + else: # original + footnote = nodes.footnote() + footnote_id = self.document.set_id(footnote) + # Use uppercase letters and a colon; they can't be + # produced inside names by the parser. + footnote_name = 'TARGET_NOTE: ' + footnote_id + footnote['auto'] = 1 + footnote['names'] = [footnote_name] + footnote_paragraph = nodes.paragraph() + footnote_paragraph += nodes.reference('', refuri, refuri=refuri) + footnote += footnote_paragraph + self.document.note_autofootnote(footnote) + self.document.note_explicit_target(footnote, footnote) + for ref in refs: + if isinstance(ref, nodes.target): + continue + refnode = nodes.footnote_reference(refname=footnote_name, auto=1) + refnode['classes'] += self.classes + self.document.note_autofootnote_ref(refnode) + self.document.note_footnote_ref(refnode) + index = ref.parent.index(ref) + 1 + reflist = [refnode] + if not utils.get_trim_footnote_ref_space(self.document.settings): + if self.classes: + reflist.insert(0, nodes.inline(text=' ', Classes=self.classes)) + else: + reflist.insert(0, nodes.Text(' ')) + ref.parent.insert(index, reflist) + return footnote + + +class DanglingReferences(Transform): + + """ + Check for dangling references (incl. footnote & citation) and for + unreferenced targets. + """ + + default_priority = 850 + + def apply(self): + visitor = DanglingReferencesVisitor( + self.document, + self.document.transformer.unknown_reference_resolvers) + self.document.walk(visitor) + # *After* resolving all references, check for unreferenced + # targets: + for target in self.document.traverse(nodes.target): + if not target.referenced: + if target.get('anonymous'): + # If we have unreferenced anonymous targets, there + # is already an error message about anonymous + # hyperlink mismatch; no need to generate another + # message. + continue + if target['names']: + naming = target['names'][0] + elif target['ids']: + naming = target['ids'][0] + else: + # Hack: Propagated targets always have their refid + # attribute set. + naming = target['refid'] + self.document.reporter.info( + 'Hyperlink target "%s" is not referenced.' + % naming, base_node=target) + + +class DanglingReferencesVisitor(nodes.SparseNodeVisitor): + + def __init__(self, document, unknown_reference_resolvers): + nodes.SparseNodeVisitor.__init__(self, document) + self.document = document + self.unknown_reference_resolvers = unknown_reference_resolvers + + def unknown_visit(self, node): + pass + + def visit_reference(self, node): + if node.resolved or not node.hasattr('refname'): + return + refname = node['refname'] + id = self.document.nameids.get(refname) + if id is None: + for resolver_function in self.unknown_reference_resolvers: + if resolver_function(node): + break + else: + if refname in self.document.nameids: + msg = self.document.reporter.error( + 'Duplicate target name, cannot be used as a unique ' + 'reference: "%s".' % (node['refname']), base_node=node) + else: + msg = self.document.reporter.error( + 'Unknown target name: "%s".' % (node['refname']), + base_node=node) + msgid = self.document.set_id(msg) + prb = nodes.problematic( + node.rawsource, node.rawsource, refid=msgid) + try: + prbid = node['ids'][0] + except IndexError: + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + node.replace_self(prb) + else: + del node['refname'] + node['refid'] = id + self.document.ids[id].note_referenced_by(id=id) + node.resolved = 1 + + visit_footnote_reference = visit_citation_reference = visit_reference