All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kr.motd.maven.sphinx.dist.docutils.transforms.references.py Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
# $Id: references.py 7624 2013-03-07 14:10:26Z milde $
# Author: David Goodger 
# 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::

        
        
        
        
            This is a test.

    PropagateTargets propagates the ids and names of the internal
    targets preceding the paragraph to the paragraph itself::

        
        
        
        
            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::

        
            
                internal
            
                external
        
        

    Corresponding references are linked via "refid" or resolved via "refuri"::

        
            
                text
            
                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::

           
               
                   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
           
           
    """

    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::

        
            
                direct external
        

    The "refname" attribute is replaced by the direct "refuri" attribute::

        
            
                direct external
        
    """

    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::

            
                
                    direct internal
            

        The "refname" attribute is replaced by "refid" linking to the target's
        "id"::

            
                
                    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::

        
            
                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
            
                




© 2015 - 2024 Weber Informatics LLC | Privacy Policy