#! /usr/bin/env python """ :Authors: David Goodger :Contact: goodger@users.sourceforge.net :Revision: $Revision: 1.1 $ :Date: $Date: 2002/08/07 12:43:18 $ :Copyright: This module has been placed in the public domain. Transforms for resolving references: - `Hyperlinks`: Used to resolve hyperlink targets and references. - `Footnotes`: Resolve footnote numbering and references. - `Substitutions`: Resolve substitutions. """ __docformat__ = 'reStructuredText' import re from docutils import nodes, utils from docutils.transforms import TransformError, Transform class Hyperlinks(Transform): """Resolve the various types of hyperlink targets and references.""" def transform(self): stages = [] #stages.append('Beginning of references.Hyperlinks.transform()\n' + self.doctree.pformat()) self.resolve_chained_targets() #stages.append('After references.Hyperlinks.resolve_chained_targets()\n' + self.doctree.pformat()) self.resolve_anonymous() #stages.append('After references.Hyperlinks.resolve_anonymous()\n' + self.doctree.pformat()) self.resolve_indirect() #stages.append('After references.Hyperlinks.resolve_indirect()\n' + self.doctree.pformat()) self.resolve_external_targets() #stages.append('After references.Hyperlinks.resolve_external_references()\n' + self.doctree.pformat()) self.resolve_internal_targets() #stages.append('After references.Hyperlinks.resolve_internal_references()\n' + self.doctree.pformat()) #import difflib #compare = difflib.Differ().compare #for i in range(len(stages) - 1): # print ''.join(compare(stages[i].splitlines(1), stages[i+1].splitlines(1))) def resolve_chained_targets(self): """ Attributes "refuri" and "refname" are migrated from the final direct target up the chain of contiguous adjacent internal targets, using `ChainedTargetResolver`. """ visitor = ChainedTargetResolver(self.doctree) self.doctree.walk(visitor) def resolve_anonymous(self): """ Link anonymous references to targets. Given:: internal external Corresponding references are linked via "refid" or resolved via "refuri":: text external """ if len(self.doctree.anonymous_refs) \ != len(self.doctree.anonymous_targets): msg = self.doctree.reporter.error( 'Anonymous hyperlink mismatch: %s references but %s targets.' % (len(self.doctree.anonymous_refs), len(self.doctree.anonymous_targets))) self.doctree.messages += msg msgid = self.doctree.set_id(msg) for ref in self.doctree.anonymous_refs: prb = nodes.problematic( ref.rawsource, ref.rawsource, refid=msgid) prbid = self.doctree.set_id(prb) msg.add_backref(prbid) ref.parent.replace(ref, prb) return for i in range(len(self.doctree.anonymous_refs)): ref = self.doctree.anonymous_refs[i] target = self.doctree.anonymous_targets[i] if target.hasattr('refuri'): ref['refuri'] = target['refuri'] ref.resolved = 1 else: ref['refid'] = target['id'] self.doctree.note_refid(ref) target.referenced = 1 def resolve_indirect(self): """ a) Indirect external references:: indirect 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):: indirect external Once the attribute is migrated, the preexisting "refname" attribute is dropped. b) Indirect internal references:: indirect internal 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:: indirect internal """ #import mypdb as pdb #pdb.set_trace() for target in self.doctree.indirect_targets: if not target.resolved: self.resolve_indirect_target(target) self.resolve_indirect_references(target) def resolve_indirect_target(self, target): refname = target['refname'] reftarget = None if self.doctree.explicit_targets.has_key(refname): reftarget = self.doctree.explicit_targets[refname] elif self.doctree.implicit_targets.has_key(refname): reftarget = self.doctree.implicit_targets[refname] if not reftarget: self.nonexistent_indirect_target(target) return if isinstance(reftarget, nodes.target) \ and not reftarget.resolved and reftarget.hasattr('refname'): self.one_indirect_target(reftarget) # multiply indirect if reftarget.hasattr('refuri'): target['refuri'] = reftarget['refuri'] if target.hasattr('name'): self.doctree.note_external_target(target) elif reftarget.hasattr('refid'): target['refid'] = reftarget['refid'] self.doctree.note_refid(target) else: try: target['refid'] = reftarget['id'] self.doctree.note_refid(target) except KeyError: self.nonexistent_indirect_target(target) return del target['refname'] target.resolved = 1 reftarget.referenced = 1 def nonexistent_indirect_target(self, target): naming = '' if target.hasattr('name'): naming = '"%s" ' % target['name'] reflist = self.doctree.refnames[target['name']] else: reflist = self.doctree.refnames[target['id']] naming += '(id="%s")' % target['id'] msg = self.doctree.reporter.warning( 'Indirect hyperlink target %s refers to target "%s", ' 'which does not exist.' % (naming, target['refname'])) self.doctree.messages += msg msgid = self.doctree.set_id(msg) for ref in reflist: prb = nodes.problematic( ref.rawsource, ref.rawsource, refid=msgid) prbid = self.doctree.set_id(prb) msg.add_backref(prbid) ref.parent.replace(ref, prb) target.resolved = 1 def resolve_indirect_references(self, target): if target.hasattr('refid'): attname = 'refid' call_if_named = 0 call_method = self.doctree.note_refid elif target.hasattr('refuri'): attname = 'refuri' call_if_named = 1 call_method = self.doctree.note_external_target else: return attval = target[attname] if target.hasattr('name'): name = target['name'] try: reflist = self.doctree.refnames[name] except KeyError, instance: if target.referenced: return msg = self.doctree.reporter.info( 'Indirect hyperlink target "%s" is not referenced.' % name) self.doctree.messages += msg target.referenced = 1 return delatt = 'refname' else: id = target['id'] try: reflist = self.doctree.refids[id] except KeyError, instance: if target.referenced: return msg = self.doctree.reporter.info( 'Indirect hyperlink target id="%s" is not referenced.' % id) self.doctree.messages += msg target.referenced = 1 return delatt = 'refid' for ref in reflist: if ref.resolved: continue del ref[delatt] ref[attname] = attval if not call_if_named or ref.hasattr('name'): call_method(ref) ref.resolved = 1 if isinstance(ref, nodes.target): self.resolve_indirect_references(ref) target.referenced = 1 def resolve_external_targets(self): """ Given:: direct external The "refname" attribute is replaced by the direct "refuri" attribute:: direct external """ for target in self.doctree.external_targets: if target.hasattr('refuri') and target.hasattr('name'): name = target['name'] refuri = target['refuri'] try: reflist = self.doctree.refnames[name] except KeyError, instance: if target.referenced: continue msg = self.doctree.reporter.info( 'External hyperlink target "%s" is not referenced.' % name) self.doctree.messages += msg target.referenced = 1 continue for ref in reflist: if ref.resolved: continue del ref['refname'] ref['refuri'] = refuri ref.resolved = 1 target.referenced = 1 def resolve_internal_targets(self): """ Given:: direct internal The "refname" attribute is replaced by "refid" linking to the target's "id":: direct internal """ for target in self.doctree.internal_targets: if target.hasattr('refuri') or target.hasattr('refid') \ or not target.hasattr('name'): continue name = target['name'] refid = target['id'] try: reflist = self.doctree.refnames[name] except KeyError, instance: if target.referenced: continue msg = self.doctree.reporter.info( 'Internal hyperlink target "%s" is not referenced.' % name) self.doctree.messages += msg target.referenced = 1 continue for ref in reflist: if ref.resolved: continue del ref['refname'] ref['refid'] = refid ref.resolved = 1 target.referenced = 1 class ChainedTargetResolver(nodes.NodeVisitor): """ Copy reference attributes up the length of a hyperlink target chain. "Chained targets" are multiple adjacent internal hyperlink targets which "point to" an external or indirect target. After the transform, all chained targets will effectively point to the same place. Given the following ``doctree`` as input:: I'm known as "d". ``ChainedTargetResolver(doctree).walk()`` will transform the above into:: I'm known as "d". """ def unknown_visit(self, node): pass def visit_target(self, node): if node.hasattr('refuri'): attname = 'refuri' call_if_named = self.doctree.note_external_target elif node.hasattr('refname'): attname = 'refname' call_if_named = self.doctree.note_indirect_target elif node.hasattr('refid'): attname = 'refid' call_if_named = None else: return attval = node[attname] index = node.parent.index(node) for i in range(index - 1, -1, -1): sibling = node.parent[i] if not isinstance(sibling, nodes.target) \ or sibling.hasattr('refuri') \ or sibling.hasattr('refname') \ or sibling.hasattr('refid'): break sibling[attname] = attval if sibling.hasattr('name') and call_if_named: call_if_named(sibling) class Footnotes(Transform): """ Assign numbers to autonumbered footnotes, and resolve links to footnotes, citations, and their references. Given the following ``doctree`` as input:: A labeled autonumbered footnote referece: An unlabeled autonumbered footnote referece: Unlabeled autonumbered footnote. 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:: A labeled autonumbered footnote referece: 2 An unlabeled autonumbered footnote referece: 1