~ubuntu-branches/ubuntu/hardy/python-docutils/hardy

« back to all changes in this revision

Viewing changes to docutils/transforms/references.py

  • Committer: Bazaar Package Importer
  • Author(s): martin f. krafft
  • Date: 2006-07-10 11:45:05 UTC
  • mfrom: (2.1.4 edgy)
  • Revision ID: james.westby@ubuntu.com-20060710114505-otkhqcslevewxmz5
Tags: 0.4-3
Added build dependency on python-central (closes: #377580).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Author: David Goodger
2
2
# Contact: goodger@users.sourceforge.net
3
 
# Revision: $Revision: 1.21 $
4
 
# Date: $Date: 2004/05/04 00:38:15 $
 
3
# Revision: $Revision: 4233 $
 
4
# Date: $Date: 2005-12-29 00:48:48 +0100 (Thu, 29 Dec 2005) $
5
5
# Copyright: This module has been placed in the public domain.
6
6
 
7
7
"""
16
16
from docutils.transforms import TransformError, Transform
17
17
 
18
18
 
19
 
indices = xrange(sys.maxint)
20
 
 
21
 
 
22
 
class ChainedTargets(Transform):
23
 
 
24
 
    """
25
 
    Attributes "refuri" and "refname" are migrated from the final direct
26
 
    target up the chain of contiguous adjacent internal targets, using
27
 
    `ChainedTargetResolver`.
28
 
    """
29
 
 
30
 
    default_priority = 420
 
19
class 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
31
43
 
32
44
    def apply(self):
33
 
        visitor = ChainedTargetResolver(self.document)
34
 
        self.document.walk(visitor)
35
 
 
36
 
 
37
 
class ChainedTargetResolver(nodes.SparseNodeVisitor):
38
 
 
39
 
    """
40
 
    Copy reference attributes up the length of a hyperlink target chain.
41
 
 
42
 
    "Chained targets" are multiple adjacent internal hyperlink targets which
43
 
    "point to" an external or indirect target.  After the transform, all
44
 
    chained targets will effectively point to the same place.
45
 
 
46
 
    Given the following ``document`` as input::
47
 
 
48
 
        <document>
49
 
            <target id="a" name="a">
50
 
            <target id="b" name="b">
51
 
            <target id="c" name="c" refuri="http://chained.external.targets">
52
 
            <target id="d" name="d">
53
 
            <paragraph>
54
 
                I'm known as "d".
55
 
            <target id="e" name="e">
56
 
            <target id="id1">
57
 
            <target id="f" name="f" refname="d">
58
 
 
59
 
    ``ChainedTargetResolver(document).walk()`` will transform the above into::
60
 
 
61
 
        <document>
62
 
            <target id="a" name="a" refuri="http://chained.external.targets">
63
 
            <target id="b" name="b" refuri="http://chained.external.targets">
64
 
            <target id="c" name="c" refuri="http://chained.external.targets">
65
 
            <target id="d" name="d">
66
 
            <paragraph>
67
 
                I'm known as "d".
68
 
            <target id="e" name="e" refname="d">
69
 
            <target id="id1" refname="d">
70
 
            <target id="f" name="f" refname="d">
71
 
    """
72
 
 
73
 
    def unknown_visit(self, node):
74
 
        pass
75
 
 
76
 
    def visit_target(self, node):
77
 
        if node.hasattr('refuri'):
78
 
            attname = 'refuri'
79
 
            call_if_named = self.document.note_external_target
80
 
        elif node.hasattr('refname'):
81
 
            attname = 'refname'
82
 
            call_if_named = self.document.note_indirect_target
83
 
        elif node.hasattr('refid'):
84
 
            attname = 'refid'
85
 
            call_if_named = None
86
 
        else:
87
 
            return
88
 
        attval = node[attname]
89
 
        index = node.parent.index(node)
90
 
        for i in range(index - 1, -1, -1):
91
 
            sibling = node.parent[i]
92
 
            if not isinstance(sibling, nodes.target) \
93
 
                  or sibling.hasattr('refuri') \
94
 
                  or sibling.hasattr('refname') \
95
 
                  or sibling.hasattr('refid'):
96
 
                break
97
 
            sibling[attname] = attval
98
 
            if sibling.hasattr('name') and call_if_named:
99
 
                call_if_named(sibling)
 
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)
100
88
 
101
89
 
102
90
class AnonymousHyperlinks(Transform):
109
97
                internal
110
98
            <reference anonymous="1">
111
99
                external
112
 
        <target anonymous="1" id="id1">
113
 
        <target anonymous="1" id="id2" refuri="http://external">
 
100
        <target anonymous="1" ids="id1">
 
101
        <target anonymous="1" ids="id2" refuri="http://external">
114
102
 
115
103
    Corresponding references are linked via "refid" or resolved via "refuri"::
116
104
 
119
107
                text
120
108
            <reference anonymous="1" refuri="http://external">
121
109
                external
122
 
        <target anonymous="1" id="id1">
123
 
        <target anonymous="1" id="id2" refuri="http://external">
 
110
        <target anonymous="1" ids="id1">
 
111
        <target anonymous="1" ids="id2" refuri="http://external">
124
112
    """
125
113
 
126
114
    default_priority = 440
127
115
 
128
116
    def apply(self):
129
 
        if len(self.document.anonymous_refs) \
130
 
              != len(self.document.anonymous_targets):
 
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):
131
127
            msg = self.document.reporter.error(
132
128
                  'Anonymous hyperlink mismatch: %s references but %s '
133
129
                  'targets.\nSee "backrefs" attribute for IDs.'
134
 
                  % (len(self.document.anonymous_refs),
135
 
                     len(self.document.anonymous_targets)))
 
130
                  % (len(anonymous_refs), len(anonymous_targets)))
136
131
            msgid = self.document.set_id(msg)
137
 
            for ref in self.document.anonymous_refs:
 
132
            for ref in anonymous_refs:
138
133
                prb = nodes.problematic(
139
134
                      ref.rawsource, ref.rawsource, refid=msgid)
140
135
                prbid = self.document.set_id(prb)
141
136
                msg.add_backref(prbid)
142
 
                ref.parent.replace(ref, prb)
 
137
                ref.replace_self(prb)
143
138
            return
144
 
        for ref, target in zip(self.document.anonymous_refs,
145
 
                               self.document.anonymous_targets):
146
 
            if target.hasattr('refuri'):
147
 
                ref['refuri'] = target['refuri']
148
 
                ref.resolved = 1
149
 
            else:
150
 
                ref['refid'] = target['id']
151
 
                self.document.note_refid(ref)
 
139
        for ref, target in zip(anonymous_refs, anonymous_targets):
152
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
153
154
 
154
155
 
155
156
class IndirectHyperlinks(Transform):
213
214
            self.resolve_indirect_references(target)
214
215
 
215
216
    def resolve_indirect_target(self, target):
216
 
        refname = target['refname']
217
 
        reftarget_id = self.document.nameids.get(refname)
218
 
        if not reftarget_id:
219
 
            # Check the unknown_reference_resolvers
220
 
            for resolver_function in (self.document.transformer
221
 
                                      .unknown_reference_resolvers):
222
 
                if resolver_function(target):
223
 
                    break
224
 
            else:
225
 
                self.nonexistent_indirect_target(target)
226
 
            return
 
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
227
231
        reftarget = self.document.ids[reftarget_id]
 
232
        reftarget.note_referenced_by(id=reftarget_id)
228
233
        if isinstance(reftarget, nodes.target) \
229
 
              and not reftarget.resolved and reftarget.hasattr('refname'):
 
234
               and not reftarget.resolved and reftarget.hasattr('refname'):
230
235
            if hasattr(target, 'multiply_indirect'):
231
236
                #and target.multiply_indirect):
232
237
                #del target.multiply_indirect
237
242
            del target.multiply_indirect
238
243
        if reftarget.hasattr('refuri'):
239
244
            target['refuri'] = reftarget['refuri']
240
 
            if target.hasattr('name'):
241
 
                self.document.note_external_target(target)
 
245
            if target.has_key('refid'):
 
246
                del target['refid']
242
247
        elif reftarget.hasattr('refid'):
243
248
            target['refid'] = reftarget['refid']
244
249
            self.document.note_refid(target)
245
250
        else:
246
 
            try:
247
 
                target['refid'] = reftarget['id']
 
251
            if reftarget['ids']:
 
252
                target['refid'] = reftarget_id
248
253
                self.document.note_refid(target)
249
 
            except KeyError:
 
254
            else:
250
255
                self.nonexistent_indirect_target(target)
251
256
                return
252
 
        del target['refname']
 
257
        if refname is not None:
 
258
            del target['refname']
253
259
        target.resolved = 1
254
 
        reftarget.referenced = 1
255
260
 
256
261
    def nonexistent_indirect_target(self, target):
257
262
        if self.document.nameids.has_key(target['refname']):
265
270
 
266
271
    def indirect_target_error(self, target, explanation):
267
272
        naming = ''
268
 
        if target.hasattr('name'):
269
 
            naming = '"%s" ' % target['name']
270
 
            reflist = self.document.refnames.get(target['name'], [])
271
 
        else:
272
 
            reflist = self.document.refids.get(target['id'], [])
273
 
        naming += '(id="%s")' % target['id']
 
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]
274
281
        msg = self.document.reporter.error(
275
282
              'Indirect hyperlink target %s refers to target "%s", %s.'
276
 
              % (naming, target['refname'], explanation),
277
 
              base_node=target)
 
283
              % (naming, target['refname'], explanation), base_node=target)
278
284
        msgid = self.document.set_id(msg)
279
 
        for ref in reflist:
 
285
        for ref in uniq(reflist):
280
286
            prb = nodes.problematic(
281
287
                  ref.rawsource, ref.rawsource, refid=msgid)
282
288
            prbid = self.document.set_id(prb)
283
289
            msg.add_backref(prbid)
284
 
            ref.parent.replace(ref, prb)
 
290
            ref.replace_self(prb)
285
291
        target.resolved = 1
286
292
 
287
293
    def resolve_indirect_references(self, target):
288
294
        if target.hasattr('refid'):
289
295
            attname = 'refid'
290
 
            call_if_named = 0
291
296
            call_method = self.document.note_refid
292
297
        elif target.hasattr('refuri'):
293
298
            attname = 'refuri'
294
 
            call_if_named = 1
295
 
            call_method = self.document.note_external_target
 
299
            call_method = None
296
300
        else:
297
301
            return
298
302
        attval = target[attname]
299
 
        if target.hasattr('name'):
300
 
            name = target['name']
301
 
            try:
302
 
                reflist = self.document.refnames[name]
303
 
            except KeyError, instance:
304
 
                if target.referenced:
305
 
                    return
306
 
                msg = self.document.reporter.info(
307
 
                      'Indirect hyperlink target "%s" is not referenced.'
308
 
                      % name, base_node=target)
309
 
                target.referenced = 1
310
 
                return
311
 
            delatt = 'refname'
312
 
        else:
313
 
            id = target['id']
314
 
            try:
315
 
                reflist = self.document.refids[id]
316
 
            except KeyError, instance:
317
 
                if target.referenced:
318
 
                    return
319
 
                msg = self.document.reporter.info(
320
 
                      'Indirect hyperlink target id="%s" is not referenced.'
321
 
                      % id, base_node=target)
322
 
                target.referenced = 1
323
 
                return
324
 
            delatt = 'refid'
325
 
        for ref in reflist:
326
 
            if ref.resolved:
327
 
                continue
328
 
            del ref[delatt]
329
 
            ref[attname] = attval
330
 
            if not call_if_named or ref.hasattr('name'):
331
 
                call_method(ref)
332
 
            ref.resolved = 1
333
 
            if isinstance(ref, nodes.target):
334
 
                self.resolve_indirect_references(ref)
335
 
        target.referenced = 1
 
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)
336
331
 
337
332
 
338
333
class ExternalTargets(Transform):
356
351
    default_priority = 640
357
352
 
358
353
    def apply(self):
359
 
        for target in self.document.external_targets:
360
 
            if target.hasattr('refuri') and target.hasattr('name'):
361
 
                name = target['name']
 
354
        for target in self.document.traverse(nodes.target):
 
355
            if target.hasattr('refuri'):
362
356
                refuri = target['refuri']
363
 
                try:
364
 
                    reflist = self.document.refnames[name]
365
 
                except KeyError, instance:
366
 
                    # @@@ First clause correct???
367
 
                    if not isinstance(target, nodes.target) or target.referenced:
368
 
                        continue
369
 
                    msg = self.document.reporter.info(
370
 
                          'External hyperlink target "%s" is not referenced.'
371
 
                          % name, base_node=target)
372
 
                    target.referenced = 1
373
 
                    continue
374
 
                for ref in reflist:
375
 
                    if ref.resolved:
376
 
                        continue
377
 
                    del ref['refname']
378
 
                    ref['refuri'] = refuri
379
 
                    ref.resolved = 1
380
 
                target.referenced = 1
 
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
381
367
 
382
368
 
383
369
class InternalTargets(Transform):
384
370
 
385
 
    """
386
 
    Given::
387
 
 
388
 
        <paragraph>
389
 
            <reference refname="direct internal">
390
 
                direct internal
391
 
        <target id="id1" name="direct internal">
392
 
 
393
 
    The "refname" attribute is replaced by "refid" linking to the target's
394
 
    "id"::
395
 
 
396
 
        <paragraph>
397
 
            <reference refid="id1">
398
 
                direct internal
399
 
        <target id="id1" name="direct internal">
400
 
    """
401
 
 
402
371
    default_priority = 660
403
372
 
404
373
    def apply(self):
405
 
        for target in self.document.internal_targets:
406
 
            if target.hasattr('refuri') or target.hasattr('refid') \
407
 
                  or not target.hasattr('name'):
408
 
                continue
409
 
            name = target['name']
410
 
            refid = target['id']
411
 
            try:
412
 
                reflist = self.document.refnames[name]
413
 
            except KeyError, instance:
414
 
                if target.referenced:
415
 
                    continue
416
 
                msg = self.document.reporter.info(
417
 
                      'Internal hyperlink target "%s" is not referenced.'
418
 
                      % name, base_node=target)
419
 
                target.referenced = 1
420
 
                continue
 
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)
421
400
            for ref in reflist:
422
401
                if ref.resolved:
423
402
                    continue
424
403
                del ref['refname']
425
404
                ref['refid'] = refid
426
405
                ref.resolved = 1
427
 
            target.referenced = 1
428
406
 
429
407
 
430
408
class Footnotes(Transform):
532
510
                if not self.document.nameids.has_key(label):
533
511
                    break
534
512
            footnote.insert(0, nodes.label('', label))
535
 
            if footnote.hasattr('dupname'):
536
 
                continue
537
 
            if footnote.hasattr('name'):
538
 
                name = footnote['name']
 
513
            for name in footnote['names']:
539
514
                for ref in self.document.footnote_refs.get(name, []):
540
515
                    ref += nodes.Text(label)
541
516
                    ref.delattr('refname')
542
 
                    ref['refid'] = footnote['id']
543
 
                    footnote.add_backref(ref['id'])
 
517
                    assert len(footnote['ids']) == len(ref['ids']) == 1
 
518
                    ref['refid'] = footnote['ids'][0]
 
519
                    footnote.add_backref(ref['ids'][0])
544
520
                    self.document.note_refid(ref)
545
521
                    ref.resolved = 1
546
 
            else:
547
 
                footnote['name'] = label
 
522
            if not footnote['names'] and not footnote['dupnames']:
 
523
                footnote['names'].append(label)
548
524
                self.document.note_explicit_target(footnote, footnote)
549
525
                self.autofootnote_labels.append(label)
550
526
        return startnum
570
546
                          ref.rawsource, ref.rawsource, refid=msgid)
571
547
                    prbid = self.document.set_id(prb)
572
548
                    msg.add_backref(prbid)
573
 
                    ref.parent.replace(ref, prb)
 
549
                    ref.replace_self(prb)
574
550
                break
575
551
            ref += nodes.Text(label)
576
552
            id = self.document.nameids[label]
577
553
            footnote = self.document.ids[id]
578
554
            ref['refid'] = id
579
555
            self.document.note_refid(ref)
580
 
            footnote.add_backref(ref['id'])
 
556
            assert len(ref['ids']) == 1
 
557
            footnote.add_backref(ref['ids'][0])
581
558
            ref.resolved = 1
582
559
            i += 1
583
560
 
609
586
                          ref.rawsource, ref.rawsource, refid=msgid)
610
587
                    prbid = self.document.set_id(prb)
611
588
                    msg.add_backref(prbid)
612
 
                    ref.parent.replace(ref, prb)
 
589
                    ref.replace_self(prb)
613
590
                break
614
591
            footnote = self.document.symbol_footnotes[i]
615
 
            ref['refid'] = footnote['id']
 
592
            assert len(footnote['ids']) == 1
 
593
            ref['refid'] = footnote['ids'][0]
616
594
            self.document.note_refid(ref)
617
 
            footnote.add_backref(ref['id'])
 
595
            footnote.add_backref(ref['ids'][0])
618
596
            i += 1
619
597
 
620
598
    def resolve_footnotes_and_citations(self):
623
601
        references.
624
602
        """
625
603
        for footnote in self.document.footnotes:
626
 
            label = footnote['name']
627
 
            if self.document.footnote_refs.has_key(label):
628
 
                reflist = self.document.footnote_refs[label]
629
 
                self.resolve_references(footnote, reflist)
 
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)
630
608
        for citation in self.document.citations:
631
 
            label = citation['name']
632
 
            if self.document.citation_refs.has_key(label):
633
 
                reflist = self.document.citation_refs[label]
634
 
                self.resolve_references(citation, reflist)
 
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)
635
613
 
636
614
    def resolve_references(self, note, reflist):
637
 
        id = note['id']
 
615
        assert len(note['ids']) == 1
 
616
        id = note['ids'][0]
638
617
        for ref in reflist:
639
618
            if ref.resolved:
640
619
                continue
641
620
            ref.delattr('refname')
642
621
            ref['refid'] = id
643
 
            note.add_backref(ref['id'])
 
622
            assert len(ref['ids']) == 1
 
623
            note.add_backref(ref['ids'][0])
644
624
            ref.resolved = 1
645
625
        note.resolved = 1
646
626
 
647
627
 
 
628
class CircularSubstitutionDefinitionError(Exception): pass
 
629
 
 
630
 
648
631
class Substitutions(Transform):
649
632
 
650
633
    """
680
663
    def apply(self):
681
664
        defs = self.document.substitution_defs
682
665
        normed = self.document.substitution_names
683
 
        for refname, refs in self.document.substitution_refs.items():
684
 
            for ref in refs:
685
 
                key = None
686
 
                if defs.has_key(refname):
687
 
                    key = refname
688
 
                else:
689
 
                    normed_name = refname.lower()
690
 
                    if normed.has_key(normed_name):
691
 
                        key = normed[normed_name]
692
 
                if key is None:
693
 
                    msg = self.document.reporter.error(
694
 
                          'Undefined substitution referenced: "%s".'
695
 
                          % refname, base_node=ref)
696
 
                    msgid = self.document.set_id(msg)
697
 
                    prb = nodes.problematic(
698
 
                          ref.rawsource, ref.rawsource, refid=msgid)
699
 
                    prbid = self.document.set_id(prb)
700
 
                    msg.add_backref(prbid)
701
 
                    ref.parent.replace(ref, prb)
702
 
                else:
703
 
                    ref.parent.replace(ref, defs[key].get_children())
704
 
        self.document.substitution_refs = None  # release replaced references
 
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)
705
735
 
706
736
 
707
737
class TargetNotes(Transform):
715
745
    """The TargetNotes transform has to be applied after `IndirectHyperlinks`
716
746
    but before `Footnotes`."""
717
747
 
 
748
 
 
749
    def __init__(self, document, startnode):
 
750
        Transform.__init__(self, document, startnode=startnode)
 
751
 
 
752
        self.classes = startnode.details.get('class', [])
 
753
 
718
754
    def apply(self):
719
755
        notes = {}
720
756
        nodelist = []
721
 
        for target in self.document.external_targets:
722
 
            name = target.get('name')
723
 
            if not name:
724
 
                print >>sys.stderr, 'no name on target: %r' % target
 
757
        for target in self.document.traverse(nodes.target):
 
758
            # Only external targets.
 
759
            if not target.hasattr('refuri'):
725
760
                continue
726
 
            refs = self.document.refnames.get(name, [])
 
761
            names = target['names']
 
762
            refs = []
 
763
            for name in names:
 
764
                refs.extend(self.document.refnames.get(name, []))
727
765
            if not refs:
728
766
                continue
729
 
            footnote = self.make_target_footnote(target, refs, notes)
 
767
            footnote = self.make_target_footnote(target['refuri'], refs,
 
768
                                                 notes)
730
769
            if not notes.has_key(target['refuri']):
731
770
                notes[target['refuri']] = footnote
732
771
                nodelist.append(footnote)
733
 
        if len(self.document.anonymous_targets) \
734
 
               == len(self.document.anonymous_refs):
735
 
            for target, ref in zip(self.document.anonymous_targets,
736
 
                                   self.document.anonymous_refs):
737
 
                if target.hasattr('refuri'):
738
 
                    footnote = self.make_target_footnote(target, [ref], notes)
739
 
                    if not notes.has_key(target['refuri']):
740
 
                        notes[target['refuri']] = footnote
741
 
                        nodelist.append(footnote)
742
 
        self.startnode.parent.replace(self.startnode, nodelist)
 
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)
743
783
 
744
 
    def make_target_footnote(self, target, refs, notes):
745
 
        refuri = target['refuri']
 
784
    def make_target_footnote(self, refuri, refs, notes):
746
785
        if notes.has_key(refuri):  # duplicate?
747
786
            footnote = notes[refuri]
748
 
            footnote_name = footnote['name']
 
787
            assert len(footnote['names']) == 1
 
788
            footnote_name = footnote['names'][0]
749
789
        else:                           # original
750
790
            footnote = nodes.footnote()
751
791
            footnote_id = self.document.set_id(footnote)
752
 
            # Use a colon; they can't be produced inside names by the parser:
753
 
            footnote_name = 'target_note: ' + footnote_id
 
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
754
795
            footnote['auto'] = 1
755
 
            footnote['name'] = footnote_name
 
796
            footnote['names'] = [footnote_name]
756
797
            footnote_paragraph = nodes.paragraph()
757
798
            footnote_paragraph += nodes.reference('', refuri, refuri=refuri)
758
799
            footnote += footnote_paragraph
763
804
                continue
764
805
            refnode = nodes.footnote_reference(
765
806
                refname=footnote_name, auto=1)
 
807
            refnode['classes'] += self.classes
766
808
            self.document.note_autofootnote_ref(refnode)
767
809
            self.document.note_footnote_ref(refnode)
768
810
            index = ref.parent.index(ref) + 1
769
811
            reflist = [refnode]
770
 
            if not self.document.settings.trim_footnote_reference_space:
771
 
                reflist.insert(0, nodes.Text(' '))
 
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(' '))
772
817
            ref.parent.insert(index, reflist)
773
818
        return footnote
 
819
 
 
820
 
 
821
class 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
 
 
858
class 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
 
 
901
def uniq(L):
 
902
     r = []
 
903
     for item in L:
 
904
         if not item in r:
 
905
             r.append(item)
 
906
     return r