buildframework/helium/external/python/lib/2.5/docutils-0.5-py2.5.egg/docutils/transforms/references.py
changeset 179 d8ac696cc51f
parent 1 be27ed110b50
child 180 e02a83d4c571
child 592 3215c239276a
equal deleted inserted replaced
1:be27ed110b50 179:d8ac696cc51f
     1 # $Id: references.py 5018 2007-03-12 21:39:31Z wiemann $
       
     2 # Author: David Goodger <goodger@python.org>
       
     3 # Copyright: This module has been placed in the public domain.
       
     4 
       
     5 """
       
     6 Transforms for resolving references.
       
     7 """
       
     8 
       
     9 __docformat__ = 'reStructuredText'
       
    10 
       
    11 import sys
       
    12 import re
       
    13 from docutils import nodes, utils
       
    14 from docutils.transforms import TransformError, Transform
       
    15 
       
    16 
       
    17 class PropagateTargets(Transform):
       
    18 
       
    19     """
       
    20     Propagate empty internal targets to the next element.
       
    21 
       
    22     Given the following nodes::
       
    23 
       
    24         <target ids="internal1" names="internal1">
       
    25         <target anonymous="1" ids="id1">
       
    26         <target ids="internal2" names="internal2">
       
    27         <paragraph>
       
    28             This is a test.
       
    29 
       
    30     PropagateTargets propagates the ids and names of the internal
       
    31     targets preceding the paragraph to the paragraph itself::
       
    32 
       
    33         <target refid="internal1">
       
    34         <target anonymous="1" refid="id1">
       
    35         <target refid="internal2">
       
    36         <paragraph ids="internal2 id1 internal1" names="internal2 internal1">
       
    37             This is a test.
       
    38     """
       
    39 
       
    40     default_priority = 260
       
    41 
       
    42     def apply(self):
       
    43         for target in self.document.traverse(nodes.target):
       
    44             # Only block-level targets without reference (like ".. target:"):
       
    45             if (isinstance(target.parent, nodes.TextElement) or
       
    46                 (target.hasattr('refid') or target.hasattr('refuri') or
       
    47                  target.hasattr('refname'))):
       
    48                 continue
       
    49             assert len(target) == 0, 'error: block-level target has children'
       
    50             next_node = target.next_node(ascend=1)
       
    51             # Do not move names and ids into Invisibles (we'd lose the
       
    52             # attributes) or different Targetables (e.g. footnotes).
       
    53             if (next_node is not None and
       
    54                 ((not isinstance(next_node, nodes.Invisible) and
       
    55                   not isinstance(next_node, nodes.Targetable)) or
       
    56                  isinstance(next_node, nodes.target))):
       
    57                 next_node['ids'].extend(target['ids'])
       
    58                 next_node['names'].extend(target['names'])
       
    59                 # Set defaults for next_node.expect_referenced_by_name/id.
       
    60                 if not hasattr(next_node, 'expect_referenced_by_name'):
       
    61                     next_node.expect_referenced_by_name = {}
       
    62                 if not hasattr(next_node, 'expect_referenced_by_id'):
       
    63                     next_node.expect_referenced_by_id = {}
       
    64                 for id in target['ids']:
       
    65                     # Update IDs to node mapping.
       
    66                     self.document.ids[id] = next_node
       
    67                     # If next_node is referenced by id ``id``, this
       
    68                     # target shall be marked as referenced.
       
    69                     next_node.expect_referenced_by_id[id] = target
       
    70                 for name in target['names']:
       
    71                     next_node.expect_referenced_by_name[name] = target
       
    72                 # If there are any expect_referenced_by_... attributes
       
    73                 # in target set, copy them to next_node.
       
    74                 next_node.expect_referenced_by_name.update(
       
    75                     getattr(target, 'expect_referenced_by_name', {}))
       
    76                 next_node.expect_referenced_by_id.update(
       
    77                     getattr(target, 'expect_referenced_by_id', {}))
       
    78                 # Set refid to point to the first former ID of target
       
    79                 # which is now an ID of next_node.
       
    80                 target['refid'] = target['ids'][0]
       
    81                 # Clear ids and names; they have been moved to
       
    82                 # next_node.
       
    83                 target['ids'] = []
       
    84                 target['names'] = []
       
    85                 self.document.note_refid(target)
       
    86 
       
    87 
       
    88 class AnonymousHyperlinks(Transform):
       
    89 
       
    90     """
       
    91     Link anonymous references to targets.  Given::
       
    92 
       
    93         <paragraph>
       
    94             <reference anonymous="1">
       
    95                 internal
       
    96             <reference anonymous="1">
       
    97                 external
       
    98         <target anonymous="1" ids="id1">
       
    99         <target anonymous="1" ids="id2" refuri="http://external">
       
   100 
       
   101     Corresponding references are linked via "refid" or resolved via "refuri"::
       
   102 
       
   103         <paragraph>
       
   104             <reference anonymous="1" refid="id1">
       
   105                 text
       
   106             <reference anonymous="1" refuri="http://external">
       
   107                 external
       
   108         <target anonymous="1" ids="id1">
       
   109         <target anonymous="1" ids="id2" refuri="http://external">
       
   110     """
       
   111 
       
   112     default_priority = 440
       
   113 
       
   114     def apply(self):
       
   115         anonymous_refs = []
       
   116         anonymous_targets = []
       
   117         for node in self.document.traverse(nodes.reference):
       
   118             if node.get('anonymous'):
       
   119                 anonymous_refs.append(node)
       
   120         for node in self.document.traverse(nodes.target):
       
   121             if node.get('anonymous'):
       
   122                 anonymous_targets.append(node)
       
   123         if len(anonymous_refs) \
       
   124               != len(anonymous_targets):
       
   125             msg = self.document.reporter.error(
       
   126                   'Anonymous hyperlink mismatch: %s references but %s '
       
   127                   'targets.\nSee "backrefs" attribute for IDs.'
       
   128                   % (len(anonymous_refs), len(anonymous_targets)))
       
   129             msgid = self.document.set_id(msg)
       
   130             for ref in anonymous_refs:
       
   131                 prb = nodes.problematic(
       
   132                       ref.rawsource, ref.rawsource, refid=msgid)
       
   133                 prbid = self.document.set_id(prb)
       
   134                 msg.add_backref(prbid)
       
   135                 ref.replace_self(prb)
       
   136             return
       
   137         for ref, target in zip(anonymous_refs, anonymous_targets):
       
   138             target.referenced = 1
       
   139             while 1:
       
   140                 if target.hasattr('refuri'):
       
   141                     ref['refuri'] = target['refuri']
       
   142                     ref.resolved = 1
       
   143                     break
       
   144                 else:
       
   145                     if not target['ids']:
       
   146                         # Propagated target.
       
   147                         target = self.document.ids[target['refid']]
       
   148                         continue
       
   149                     ref['refid'] = target['ids'][0]
       
   150                     self.document.note_refid(ref)
       
   151                     break
       
   152 
       
   153 
       
   154 class IndirectHyperlinks(Transform):
       
   155 
       
   156     """
       
   157     a) Indirect external references::
       
   158 
       
   159            <paragraph>
       
   160                <reference refname="indirect external">
       
   161                    indirect external
       
   162            <target id="id1" name="direct external"
       
   163                refuri="http://indirect">
       
   164            <target id="id2" name="indirect external"
       
   165                refname="direct external">
       
   166 
       
   167        The "refuri" attribute is migrated back to all indirect targets
       
   168        from the final direct target (i.e. a target not referring to
       
   169        another indirect target)::
       
   170 
       
   171            <paragraph>
       
   172                <reference refname="indirect external">
       
   173                    indirect external
       
   174            <target id="id1" name="direct external"
       
   175                refuri="http://indirect">
       
   176            <target id="id2" name="indirect external"
       
   177                refuri="http://indirect">
       
   178 
       
   179        Once the attribute is migrated, the preexisting "refname" attribute
       
   180        is dropped.
       
   181 
       
   182     b) Indirect internal references::
       
   183 
       
   184            <target id="id1" name="final target">
       
   185            <paragraph>
       
   186                <reference refname="indirect internal">
       
   187                    indirect internal
       
   188            <target id="id2" name="indirect internal 2"
       
   189                refname="final target">
       
   190            <target id="id3" name="indirect internal"
       
   191                refname="indirect internal 2">
       
   192 
       
   193        Targets which indirectly refer to an internal target become one-hop
       
   194        indirect (their "refid" attributes are directly set to the internal
       
   195        target's "id"). References which indirectly refer to an internal
       
   196        target become direct internal references::
       
   197 
       
   198            <target id="id1" name="final target">
       
   199            <paragraph>
       
   200                <reference refid="id1">
       
   201                    indirect internal
       
   202            <target id="id2" name="indirect internal 2" refid="id1">
       
   203            <target id="id3" name="indirect internal" refid="id1">
       
   204     """
       
   205 
       
   206     default_priority = 460
       
   207 
       
   208     def apply(self):
       
   209         for target in self.document.indirect_targets:
       
   210             if not target.resolved:
       
   211                 self.resolve_indirect_target(target)
       
   212             self.resolve_indirect_references(target)
       
   213 
       
   214     def resolve_indirect_target(self, target):
       
   215         refname = target.get('refname')
       
   216         if refname is None:
       
   217             reftarget_id = target['refid']
       
   218         else:
       
   219             reftarget_id = self.document.nameids.get(refname)
       
   220             if not reftarget_id:
       
   221                 # Check the unknown_reference_resolvers
       
   222                 for resolver_function in \
       
   223                         self.document.transformer.unknown_reference_resolvers:
       
   224                     if resolver_function(target):
       
   225                         break
       
   226                 else:
       
   227                     self.nonexistent_indirect_target(target)
       
   228                 return
       
   229         reftarget = self.document.ids[reftarget_id]
       
   230         reftarget.note_referenced_by(id=reftarget_id)
       
   231         if isinstance(reftarget, nodes.target) \
       
   232                and not reftarget.resolved and reftarget.hasattr('refname'):
       
   233             if hasattr(target, 'multiply_indirect'):
       
   234                 #and target.multiply_indirect):
       
   235                 #del target.multiply_indirect
       
   236                 self.circular_indirect_reference(target)
       
   237                 return
       
   238             target.multiply_indirect = 1
       
   239             self.resolve_indirect_target(reftarget) # multiply indirect
       
   240             del target.multiply_indirect
       
   241         if reftarget.hasattr('refuri'):
       
   242             target['refuri'] = reftarget['refuri']
       
   243             if target.has_key('refid'):
       
   244                 del target['refid']
       
   245         elif reftarget.hasattr('refid'):
       
   246             target['refid'] = reftarget['refid']
       
   247             self.document.note_refid(target)
       
   248         else:
       
   249             if reftarget['ids']:
       
   250                 target['refid'] = reftarget_id
       
   251                 self.document.note_refid(target)
       
   252             else:
       
   253                 self.nonexistent_indirect_target(target)
       
   254                 return
       
   255         if refname is not None:
       
   256             del target['refname']
       
   257         target.resolved = 1
       
   258 
       
   259     def nonexistent_indirect_target(self, target):
       
   260         if self.document.nameids.has_key(target['refname']):
       
   261             self.indirect_target_error(target, 'which is a duplicate, and '
       
   262                                        'cannot be used as a unique reference')
       
   263         else:
       
   264             self.indirect_target_error(target, 'which does not exist')
       
   265 
       
   266     def circular_indirect_reference(self, target):
       
   267         self.indirect_target_error(target, 'forming a circular reference')
       
   268 
       
   269     def indirect_target_error(self, target, explanation):
       
   270         naming = ''
       
   271         reflist = []
       
   272         if target['names']:
       
   273             naming = '"%s" ' % target['names'][0]
       
   274         for name in target['names']:
       
   275             reflist.extend(self.document.refnames.get(name, []))
       
   276         for id in target['ids']:
       
   277             reflist.extend(self.document.refids.get(id, []))
       
   278         naming += '(id="%s")' % target['ids'][0]
       
   279         msg = self.document.reporter.error(
       
   280               'Indirect hyperlink target %s refers to target "%s", %s.'
       
   281               % (naming, target['refname'], explanation), base_node=target)
       
   282         msgid = self.document.set_id(msg)
       
   283         for ref in utils.uniq(reflist):
       
   284             prb = nodes.problematic(
       
   285                   ref.rawsource, ref.rawsource, refid=msgid)
       
   286             prbid = self.document.set_id(prb)
       
   287             msg.add_backref(prbid)
       
   288             ref.replace_self(prb)
       
   289         target.resolved = 1
       
   290 
       
   291     def resolve_indirect_references(self, target):
       
   292         if target.hasattr('refid'):
       
   293             attname = 'refid'
       
   294             call_method = self.document.note_refid
       
   295         elif target.hasattr('refuri'):
       
   296             attname = 'refuri'
       
   297             call_method = None
       
   298         else:
       
   299             return
       
   300         attval = target[attname]
       
   301         for name in target['names']:
       
   302             reflist = self.document.refnames.get(name, [])
       
   303             if reflist:
       
   304                 target.note_referenced_by(name=name)
       
   305             for ref in reflist:
       
   306                 if ref.resolved:
       
   307                     continue
       
   308                 del ref['refname']
       
   309                 ref[attname] = attval
       
   310                 if call_method:
       
   311                     call_method(ref)
       
   312                 ref.resolved = 1
       
   313                 if isinstance(ref, nodes.target):
       
   314                     self.resolve_indirect_references(ref)
       
   315         for id in target['ids']:
       
   316             reflist = self.document.refids.get(id, [])
       
   317             if reflist:
       
   318                 target.note_referenced_by(id=id)
       
   319             for ref in reflist:
       
   320                 if ref.resolved:
       
   321                     continue
       
   322                 del ref['refid']
       
   323                 ref[attname] = attval
       
   324                 if call_method:
       
   325                     call_method(ref)
       
   326                 ref.resolved = 1
       
   327                 if isinstance(ref, nodes.target):
       
   328                     self.resolve_indirect_references(ref)
       
   329 
       
   330 
       
   331 class ExternalTargets(Transform):
       
   332 
       
   333     """
       
   334     Given::
       
   335 
       
   336         <paragraph>
       
   337             <reference refname="direct external">
       
   338                 direct external
       
   339         <target id="id1" name="direct external" refuri="http://direct">
       
   340 
       
   341     The "refname" attribute is replaced by the direct "refuri" attribute::
       
   342 
       
   343         <paragraph>
       
   344             <reference refuri="http://direct">
       
   345                 direct external
       
   346         <target id="id1" name="direct external" refuri="http://direct">
       
   347     """
       
   348 
       
   349     default_priority = 640
       
   350 
       
   351     def apply(self):
       
   352         for target in self.document.traverse(nodes.target):
       
   353             if target.hasattr('refuri'):
       
   354                 refuri = target['refuri']
       
   355                 for name in target['names']:
       
   356                     reflist = self.document.refnames.get(name, [])
       
   357                     if reflist:
       
   358                         target.note_referenced_by(name=name)
       
   359                     for ref in reflist:
       
   360                         if ref.resolved:
       
   361                             continue
       
   362                         del ref['refname']
       
   363                         ref['refuri'] = refuri
       
   364                         ref.resolved = 1
       
   365 
       
   366 
       
   367 class InternalTargets(Transform):
       
   368 
       
   369     default_priority = 660
       
   370 
       
   371     def apply(self):
       
   372         for target in self.document.traverse(nodes.target):
       
   373             if not target.hasattr('refuri') and not target.hasattr('refid'):
       
   374                 self.resolve_reference_ids(target)
       
   375 
       
   376     def resolve_reference_ids(self, target):
       
   377         """
       
   378         Given::
       
   379 
       
   380             <paragraph>
       
   381                 <reference refname="direct internal">
       
   382                     direct internal
       
   383             <target id="id1" name="direct internal">
       
   384 
       
   385         The "refname" attribute is replaced by "refid" linking to the target's
       
   386         "id"::
       
   387 
       
   388             <paragraph>
       
   389                 <reference refid="id1">
       
   390                     direct internal
       
   391             <target id="id1" name="direct internal">
       
   392         """
       
   393         for name in target['names']:
       
   394             refid = self.document.nameids[name]
       
   395             reflist = self.document.refnames.get(name, [])
       
   396             if reflist:
       
   397                 target.note_referenced_by(name=name)
       
   398             for ref in reflist:
       
   399                 if ref.resolved:
       
   400                     continue
       
   401                 del ref['refname']
       
   402                 ref['refid'] = refid
       
   403                 ref.resolved = 1
       
   404 
       
   405 
       
   406 class Footnotes(Transform):
       
   407 
       
   408     """
       
   409     Assign numbers to autonumbered footnotes, and resolve links to footnotes,
       
   410     citations, and their references.
       
   411 
       
   412     Given the following ``document`` as input::
       
   413 
       
   414         <document>
       
   415             <paragraph>
       
   416                 A labeled autonumbered footnote referece:
       
   417                 <footnote_reference auto="1" id="id1" refname="footnote">
       
   418             <paragraph>
       
   419                 An unlabeled autonumbered footnote referece:
       
   420                 <footnote_reference auto="1" id="id2">
       
   421             <footnote auto="1" id="id3">
       
   422                 <paragraph>
       
   423                     Unlabeled autonumbered footnote.
       
   424             <footnote auto="1" id="footnote" name="footnote">
       
   425                 <paragraph>
       
   426                     Labeled autonumbered footnote.
       
   427 
       
   428     Auto-numbered footnotes have attribute ``auto="1"`` and no label.
       
   429     Auto-numbered footnote_references have no reference text (they're
       
   430     empty elements). When resolving the numbering, a ``label`` element
       
   431     is added to the beginning of the ``footnote``, and reference text
       
   432     to the ``footnote_reference``.
       
   433 
       
   434     The transformed result will be::
       
   435 
       
   436         <document>
       
   437             <paragraph>
       
   438                 A labeled autonumbered footnote referece:
       
   439                 <footnote_reference auto="1" id="id1" refid="footnote">
       
   440                     2
       
   441             <paragraph>
       
   442                 An unlabeled autonumbered footnote referece:
       
   443                 <footnote_reference auto="1" id="id2" refid="id3">
       
   444                     1
       
   445             <footnote auto="1" id="id3" backrefs="id2">
       
   446                 <label>
       
   447                     1
       
   448                 <paragraph>
       
   449                     Unlabeled autonumbered footnote.
       
   450             <footnote auto="1" id="footnote" name="footnote" backrefs="id1">
       
   451                 <label>
       
   452                     2
       
   453                 <paragraph>
       
   454                     Labeled autonumbered footnote.
       
   455 
       
   456     Note that the footnotes are not in the same order as the references.
       
   457 
       
   458     The labels and reference text are added to the auto-numbered ``footnote``
       
   459     and ``footnote_reference`` elements.  Footnote elements are backlinked to
       
   460     their references via "refids" attributes.  References are assigned "id"
       
   461     and "refid" attributes.
       
   462 
       
   463     After adding labels and reference text, the "auto" attributes can be
       
   464     ignored.
       
   465     """
       
   466 
       
   467     default_priority = 620
       
   468 
       
   469     autofootnote_labels = None
       
   470     """Keep track of unlabeled autonumbered footnotes."""
       
   471 
       
   472     symbols = [
       
   473           # Entries 1-4 and 6 below are from section 12.51 of
       
   474           # The Chicago Manual of Style, 14th edition.
       
   475           '*',                          # asterisk/star
       
   476           u'\u2020',                    # dagger &dagger;
       
   477           u'\u2021',                    # double dagger &Dagger;
       
   478           u'\u00A7',                    # section mark &sect;
       
   479           u'\u00B6',                    # paragraph mark (pilcrow) &para;
       
   480                                         # (parallels ['||'] in CMoS)
       
   481           '#',                          # number sign
       
   482           # The entries below were chosen arbitrarily.
       
   483           u'\u2660',                    # spade suit &spades;
       
   484           u'\u2665',                    # heart suit &hearts;
       
   485           u'\u2666',                    # diamond suit &diams;
       
   486           u'\u2663',                    # club suit &clubs;
       
   487           ]
       
   488 
       
   489     def apply(self):
       
   490         self.autofootnote_labels = []
       
   491         startnum = self.document.autofootnote_start
       
   492         self.document.autofootnote_start = self.number_footnotes(startnum)
       
   493         self.number_footnote_references(startnum)
       
   494         self.symbolize_footnotes()
       
   495         self.resolve_footnotes_and_citations()
       
   496 
       
   497     def number_footnotes(self, startnum):
       
   498         """
       
   499         Assign numbers to autonumbered footnotes.
       
   500 
       
   501         For labeled autonumbered footnotes, copy the number over to
       
   502         corresponding footnote references.
       
   503         """
       
   504         for footnote in self.document.autofootnotes:
       
   505             while 1:
       
   506                 label = str(startnum)
       
   507                 startnum += 1
       
   508                 if not self.document.nameids.has_key(label):
       
   509                     break
       
   510             footnote.insert(0, nodes.label('', label))
       
   511             for name in footnote['names']:
       
   512                 for ref in self.document.footnote_refs.get(name, []):
       
   513                     ref += nodes.Text(label)
       
   514                     ref.delattr('refname')
       
   515                     assert len(footnote['ids']) == len(ref['ids']) == 1
       
   516                     ref['refid'] = footnote['ids'][0]
       
   517                     footnote.add_backref(ref['ids'][0])
       
   518                     self.document.note_refid(ref)
       
   519                     ref.resolved = 1
       
   520             if not footnote['names'] and not footnote['dupnames']:
       
   521                 footnote['names'].append(label)
       
   522                 self.document.note_explicit_target(footnote, footnote)
       
   523                 self.autofootnote_labels.append(label)
       
   524         return startnum
       
   525 
       
   526     def number_footnote_references(self, startnum):
       
   527         """Assign numbers to autonumbered footnote references."""
       
   528         i = 0
       
   529         for ref in self.document.autofootnote_refs:
       
   530             if ref.resolved or ref.hasattr('refid'):
       
   531                 continue
       
   532             try:
       
   533                 label = self.autofootnote_labels[i]
       
   534             except IndexError:
       
   535                 msg = self.document.reporter.error(
       
   536                       'Too many autonumbered footnote references: only %s '
       
   537                       'corresponding footnotes available.'
       
   538                       % len(self.autofootnote_labels), base_node=ref)
       
   539                 msgid = self.document.set_id(msg)
       
   540                 for ref in self.document.autofootnote_refs[i:]:
       
   541                     if ref.resolved or ref.hasattr('refname'):
       
   542                         continue
       
   543                     prb = nodes.problematic(
       
   544                           ref.rawsource, ref.rawsource, refid=msgid)
       
   545                     prbid = self.document.set_id(prb)
       
   546                     msg.add_backref(prbid)
       
   547                     ref.replace_self(prb)
       
   548                 break
       
   549             ref += nodes.Text(label)
       
   550             id = self.document.nameids[label]
       
   551             footnote = self.document.ids[id]
       
   552             ref['refid'] = id
       
   553             self.document.note_refid(ref)
       
   554             assert len(ref['ids']) == 1
       
   555             footnote.add_backref(ref['ids'][0])
       
   556             ref.resolved = 1
       
   557             i += 1
       
   558 
       
   559     def symbolize_footnotes(self):
       
   560         """Add symbols indexes to "[*]"-style footnotes and references."""
       
   561         labels = []
       
   562         for footnote in self.document.symbol_footnotes:
       
   563             reps, index = divmod(self.document.symbol_footnote_start,
       
   564                                  len(self.symbols))
       
   565             labeltext = self.symbols[index] * (reps + 1)
       
   566             labels.append(labeltext)
       
   567             footnote.insert(0, nodes.label('', labeltext))
       
   568             self.document.symbol_footnote_start += 1
       
   569             self.document.set_id(footnote)
       
   570         i = 0
       
   571         for ref in self.document.symbol_footnote_refs:
       
   572             try:
       
   573                 ref += nodes.Text(labels[i])
       
   574             except IndexError:
       
   575                 msg = self.document.reporter.error(
       
   576                       'Too many symbol footnote references: only %s '
       
   577                       'corresponding footnotes available.' % len(labels),
       
   578                       base_node=ref)
       
   579                 msgid = self.document.set_id(msg)
       
   580                 for ref in self.document.symbol_footnote_refs[i:]:
       
   581                     if ref.resolved or ref.hasattr('refid'):
       
   582                         continue
       
   583                     prb = nodes.problematic(
       
   584                           ref.rawsource, ref.rawsource, refid=msgid)
       
   585                     prbid = self.document.set_id(prb)
       
   586                     msg.add_backref(prbid)
       
   587                     ref.replace_self(prb)
       
   588                 break
       
   589             footnote = self.document.symbol_footnotes[i]
       
   590             assert len(footnote['ids']) == 1
       
   591             ref['refid'] = footnote['ids'][0]
       
   592             self.document.note_refid(ref)
       
   593             footnote.add_backref(ref['ids'][0])
       
   594             i += 1
       
   595 
       
   596     def resolve_footnotes_and_citations(self):
       
   597         """
       
   598         Link manually-labeled footnotes and citations to/from their
       
   599         references.
       
   600         """
       
   601         for footnote in self.document.footnotes:
       
   602             for label in footnote['names']:
       
   603                 if self.document.footnote_refs.has_key(label):
       
   604                     reflist = self.document.footnote_refs[label]
       
   605                     self.resolve_references(footnote, reflist)
       
   606         for citation in self.document.citations:
       
   607             for label in citation['names']:
       
   608                 if self.document.citation_refs.has_key(label):
       
   609                     reflist = self.document.citation_refs[label]
       
   610                     self.resolve_references(citation, reflist)
       
   611 
       
   612     def resolve_references(self, note, reflist):
       
   613         assert len(note['ids']) == 1
       
   614         id = note['ids'][0]
       
   615         for ref in reflist:
       
   616             if ref.resolved:
       
   617                 continue
       
   618             ref.delattr('refname')
       
   619             ref['refid'] = id
       
   620             assert len(ref['ids']) == 1
       
   621             note.add_backref(ref['ids'][0])
       
   622             ref.resolved = 1
       
   623         note.resolved = 1
       
   624 
       
   625 
       
   626 class CircularSubstitutionDefinitionError(Exception): pass
       
   627 
       
   628 
       
   629 class Substitutions(Transform):
       
   630 
       
   631     """
       
   632     Given the following ``document`` as input::
       
   633 
       
   634         <document>
       
   635             <paragraph>
       
   636                 The
       
   637                 <substitution_reference refname="biohazard">
       
   638                     biohazard
       
   639                  symbol is deservedly scary-looking.
       
   640             <substitution_definition name="biohazard">
       
   641                 <image alt="biohazard" uri="biohazard.png">
       
   642 
       
   643     The ``substitution_reference`` will simply be replaced by the
       
   644     contents of the corresponding ``substitution_definition``.
       
   645 
       
   646     The transformed result will be::
       
   647 
       
   648         <document>
       
   649             <paragraph>
       
   650                 The
       
   651                 <image alt="biohazard" uri="biohazard.png">
       
   652                  symbol is deservedly scary-looking.
       
   653             <substitution_definition name="biohazard">
       
   654                 <image alt="biohazard" uri="biohazard.png">
       
   655     """
       
   656 
       
   657     default_priority = 220
       
   658     """The Substitutions transform has to be applied very early, before
       
   659     `docutils.tranforms.frontmatter.DocTitle` and others."""
       
   660 
       
   661     def apply(self):
       
   662         defs = self.document.substitution_defs
       
   663         normed = self.document.substitution_names
       
   664         subreflist = self.document.traverse(nodes.substitution_reference)
       
   665         nested = {}
       
   666         for ref in subreflist:
       
   667             refname = ref['refname']
       
   668             key = None
       
   669             if defs.has_key(refname):
       
   670                 key = refname
       
   671             else:
       
   672                 normed_name = refname.lower()
       
   673                 if normed.has_key(normed_name):
       
   674                     key = normed[normed_name]
       
   675             if key is None:
       
   676                 msg = self.document.reporter.error(
       
   677                       'Undefined substitution referenced: "%s".'
       
   678                       % refname, base_node=ref)
       
   679                 msgid = self.document.set_id(msg)
       
   680                 prb = nodes.problematic(
       
   681                       ref.rawsource, ref.rawsource, refid=msgid)
       
   682                 prbid = self.document.set_id(prb)
       
   683                 msg.add_backref(prbid)
       
   684                 ref.replace_self(prb)
       
   685             else:
       
   686                 subdef = defs[key]
       
   687                 parent = ref.parent
       
   688                 index = parent.index(ref)
       
   689                 if  (subdef.attributes.has_key('ltrim')
       
   690                      or subdef.attributes.has_key('trim')):
       
   691                     if index > 0 and isinstance(parent[index - 1],
       
   692                                                 nodes.Text):
       
   693                         parent.replace(parent[index - 1],
       
   694                                        parent[index - 1].rstrip())
       
   695                 if  (subdef.attributes.has_key('rtrim')
       
   696                      or subdef.attributes.has_key('trim')):
       
   697                     if  (len(parent) > index + 1
       
   698                          and isinstance(parent[index + 1], nodes.Text)):
       
   699                         parent.replace(parent[index + 1],
       
   700                                        parent[index + 1].lstrip())
       
   701                 subdef_copy = subdef.deepcopy()
       
   702                 try:
       
   703                     # Take care of nested substitution references:
       
   704                     for nested_ref in subdef_copy.traverse(
       
   705                           nodes.substitution_reference):
       
   706                         nested_name = normed[nested_ref['refname'].lower()]
       
   707                         if nested_name in nested.setdefault(nested_name, []):
       
   708                             raise CircularSubstitutionDefinitionError
       
   709                         else:
       
   710                             nested[nested_name].append(key)
       
   711                             subreflist.append(nested_ref)
       
   712                 except CircularSubstitutionDefinitionError:
       
   713                     parent = ref.parent
       
   714                     if isinstance(parent, nodes.substitution_definition):
       
   715                         msg = self.document.reporter.error(
       
   716                             'Circular substitution definition detected:',
       
   717                             nodes.literal_block(parent.rawsource,
       
   718                                                 parent.rawsource),
       
   719                             line=parent.line, base_node=parent)
       
   720                         parent.replace_self(msg)
       
   721                     else:
       
   722                         msg = self.document.reporter.error(
       
   723                             'Circular substitution definition referenced: "%s".'
       
   724                             % refname, base_node=ref)
       
   725                         msgid = self.document.set_id(msg)
       
   726                         prb = nodes.problematic(
       
   727                             ref.rawsource, ref.rawsource, refid=msgid)
       
   728                         prbid = self.document.set_id(prb)
       
   729                         msg.add_backref(prbid)
       
   730                         ref.replace_self(prb)
       
   731                 else:
       
   732                     ref.replace_self(subdef_copy.children)
       
   733 
       
   734 
       
   735 class TargetNotes(Transform):
       
   736 
       
   737     """
       
   738     Creates a footnote for each external target in the text, and corresponding
       
   739     footnote references after each reference.
       
   740     """
       
   741 
       
   742     default_priority = 540
       
   743     """The TargetNotes transform has to be applied after `IndirectHyperlinks`
       
   744     but before `Footnotes`."""
       
   745 
       
   746 
       
   747     def __init__(self, document, startnode):
       
   748         Transform.__init__(self, document, startnode=startnode)
       
   749 
       
   750         self.classes = startnode.details.get('class', [])
       
   751 
       
   752     def apply(self):
       
   753         notes = {}
       
   754         nodelist = []
       
   755         for target in self.document.traverse(nodes.target):
       
   756             # Only external targets.
       
   757             if not target.hasattr('refuri'):
       
   758                 continue
       
   759             names = target['names']
       
   760             refs = []
       
   761             for name in names:
       
   762                 refs.extend(self.document.refnames.get(name, []))
       
   763             if not refs:
       
   764                 continue
       
   765             footnote = self.make_target_footnote(target['refuri'], refs,
       
   766                                                  notes)
       
   767             if not notes.has_key(target['refuri']):
       
   768                 notes[target['refuri']] = footnote
       
   769                 nodelist.append(footnote)
       
   770         # Take care of anonymous references.
       
   771         for ref in self.document.traverse(nodes.reference):
       
   772             if not ref.get('anonymous'):
       
   773                 continue
       
   774             if ref.hasattr('refuri'):
       
   775                 footnote = self.make_target_footnote(ref['refuri'], [ref],
       
   776                                                      notes)
       
   777                 if not notes.has_key(ref['refuri']):
       
   778                     notes[ref['refuri']] = footnote
       
   779                     nodelist.append(footnote)
       
   780         self.startnode.replace_self(nodelist)
       
   781 
       
   782     def make_target_footnote(self, refuri, refs, notes):
       
   783         if notes.has_key(refuri):  # duplicate?
       
   784             footnote = notes[refuri]
       
   785             assert len(footnote['names']) == 1
       
   786             footnote_name = footnote['names'][0]
       
   787         else:                           # original
       
   788             footnote = nodes.footnote()
       
   789             footnote_id = self.document.set_id(footnote)
       
   790             # Use uppercase letters and a colon; they can't be
       
   791             # produced inside names by the parser.
       
   792             footnote_name = 'TARGET_NOTE: ' + footnote_id
       
   793             footnote['auto'] = 1
       
   794             footnote['names'] = [footnote_name]
       
   795             footnote_paragraph = nodes.paragraph()
       
   796             footnote_paragraph += nodes.reference('', refuri, refuri=refuri)
       
   797             footnote += footnote_paragraph
       
   798             self.document.note_autofootnote(footnote)
       
   799             self.document.note_explicit_target(footnote, footnote)
       
   800         for ref in refs:
       
   801             if isinstance(ref, nodes.target):
       
   802                 continue
       
   803             refnode = nodes.footnote_reference(
       
   804                 refname=footnote_name, auto=1)
       
   805             refnode['classes'] += self.classes
       
   806             self.document.note_autofootnote_ref(refnode)
       
   807             self.document.note_footnote_ref(refnode)
       
   808             index = ref.parent.index(ref) + 1
       
   809             reflist = [refnode]
       
   810             if not utils.get_trim_footnote_ref_space(self.document.settings):
       
   811                 if self.classes:
       
   812                     reflist.insert(0, nodes.inline(text=' ', Classes=self.classes))
       
   813                 else:
       
   814                     reflist.insert(0, nodes.Text(' '))
       
   815             ref.parent.insert(index, reflist)
       
   816         return footnote
       
   817 
       
   818 
       
   819 class DanglingReferences(Transform):
       
   820 
       
   821     """
       
   822     Check for dangling references (incl. footnote & citation) and for
       
   823     unreferenced targets.
       
   824     """
       
   825 
       
   826     default_priority = 850
       
   827 
       
   828     def apply(self):
       
   829         visitor = DanglingReferencesVisitor(
       
   830             self.document,
       
   831             self.document.transformer.unknown_reference_resolvers)
       
   832         self.document.walk(visitor)
       
   833         # *After* resolving all references, check for unreferenced
       
   834         # targets:
       
   835         for target in self.document.traverse(nodes.target):
       
   836             if not target.referenced:
       
   837                 if target.get('anonymous'):
       
   838                     # If we have unreferenced anonymous targets, there
       
   839                     # is already an error message about anonymous
       
   840                     # hyperlink mismatch; no need to generate another
       
   841                     # message.
       
   842                     continue
       
   843                 if target['names']:
       
   844                     naming = target['names'][0]
       
   845                 elif target['ids']:
       
   846                     naming = target['ids'][0]
       
   847                 else:
       
   848                     # Hack: Propagated targets always have their refid
       
   849                     # attribute set.
       
   850                     naming = target['refid']
       
   851                 self.document.reporter.info(
       
   852                     'Hyperlink target "%s" is not referenced.'
       
   853                     % naming, base_node=target)
       
   854 
       
   855 
       
   856 class DanglingReferencesVisitor(nodes.SparseNodeVisitor):
       
   857     
       
   858     def __init__(self, document, unknown_reference_resolvers):
       
   859         nodes.SparseNodeVisitor.__init__(self, document)
       
   860         self.document = document
       
   861         self.unknown_reference_resolvers = unknown_reference_resolvers
       
   862 
       
   863     def unknown_visit(self, node):
       
   864         pass
       
   865 
       
   866     def visit_reference(self, node):
       
   867         if node.resolved or not node.hasattr('refname'):
       
   868             return
       
   869         refname = node['refname']
       
   870         id = self.document.nameids.get(refname)
       
   871         if id is None:
       
   872             for resolver_function in self.unknown_reference_resolvers:
       
   873                 if resolver_function(node):
       
   874                     break
       
   875             else:
       
   876                 if self.document.nameids.has_key(refname):
       
   877                     msg = self.document.reporter.error(
       
   878                         'Duplicate target name, cannot be used as a unique '
       
   879                         'reference: "%s".' % (node['refname']), base_node=node)
       
   880                 else:
       
   881                     msg = self.document.reporter.error(
       
   882                         'Unknown target name: "%s".' % (node['refname']),
       
   883                         base_node=node)
       
   884                 msgid = self.document.set_id(msg)
       
   885                 prb = nodes.problematic(
       
   886                       node.rawsource, node.rawsource, refid=msgid)
       
   887                 prbid = self.document.set_id(prb)
       
   888                 msg.add_backref(prbid)
       
   889                 node.replace_self(prb)
       
   890         else:
       
   891             del node['refname']
       
   892             node['refid'] = id
       
   893             self.document.ids[id].note_referenced_by(id=id)
       
   894             node.resolved = 1
       
   895 
       
   896     visit_footnote_reference = visit_citation_reference = visit_reference