root/galaxy-central/eggs/docutils-0.4-py2.6.egg/docutils/transforms/references.py @ 3

リビジョン 3, 35.0 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

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