From 101671ae44e1686680c80cd07b452aabeb88fb63 Mon Sep 17 00:00:00 2001 From: goodger Date: Sat, 20 Apr 2002 03:01:52 +0000 Subject: Initial revision git-svn-id: http://svn.code.sf.net/p/docutils/code/trunk/docutils@18 929543f6-e4f2-0310-98a6-ba3bd3dd1d04 --- docutils/transforms/references.py | 670 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 670 insertions(+) create mode 100644 docutils/transforms/references.py (limited to 'docutils/transforms/references.py') diff --git a/docutils/transforms/references.py b/docutils/transforms/references.py new file mode 100644 index 000000000..c2ff9189b --- /dev/null +++ b/docutils/transforms/references.py @@ -0,0 +1,670 @@ +#! /usr/bin/env python +""" +:Authors: David Goodger +:Contact: goodger@users.sourceforge.net +:Revision: $Revision$ +:Date: $Date$ +: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 + +