~ubuntu-branches/ubuntu/karmic/python-docutils/karmic

« back to all changes in this revision

Viewing changes to docutils/nodes.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.54 $
4
 
# Date: $Date: 2004/04/09 22:49:02 $
 
3
# Revision: $Revision: 4242 $
 
4
# Date: $Date: 2006-01-06 00:28:53 +0100 (Fri, 06 Jan 2006) $
5
5
# Copyright: This module has been placed in the public domain.
6
6
 
7
7
"""
18
18
``isinstance(node, base_class)`` to determine the position of the node in the
19
19
hierarchy.
20
20
 
21
 
.. _DTD: http://docutils.sourceforge.net/spec/docutils.dtd
 
21
.. _DTD: http://docutils.sourceforge.net/docs/ref/docutils.dtd
22
22
"""
23
23
 
24
24
__docformat__ = 'reStructuredText'
26
26
import sys
27
27
import os
28
28
import re
29
 
import xml.dom.minidom
 
29
import warnings
30
30
from types import IntType, SliceType, StringType, UnicodeType, \
31
 
     TupleType, ListType
 
31
     TupleType, ListType, ClassType
32
32
from UserString import UserString
33
33
 
34
34
 
63
63
        """
64
64
        return 1
65
65
 
66
 
    def asdom(self, dom=xml.dom.minidom):
 
66
    def asdom(self, dom=None):
67
67
        """Return a DOM **fragment** representation of this Node."""
 
68
        if dom is None:
 
69
            import xml.dom.minidom as dom
68
70
        domroot = dom.Document()
69
71
        return self._dom_node(domroot)
70
72
 
71
73
    def pformat(self, indent='    ', level=0):
72
 
        """Return an indented pseudo-XML representation, for test purposes."""
 
74
        """
 
75
        Return an indented pseudo-XML representation, for test purposes.
 
76
 
 
77
        Override in subclasses.
 
78
        """
73
79
        raise NotImplementedError
74
80
 
75
81
    def copy(self):
76
82
        """Return a copy of self."""
77
83
        raise NotImplementedError
78
84
 
 
85
    def deepcopy(self):
 
86
        """Return a deep copy of self (also copying children)."""
 
87
        raise NotImplementedError
 
88
 
79
89
    def setup_child(self, child):
80
90
        child.parent = self
81
91
        if self.document:
87
97
 
88
98
    def walk(self, visitor):
89
99
        """
90
 
        Traverse a tree of `Node` objects, calling ``visit_...`` methods of
91
 
        `visitor` when entering each node. If there is no
92
 
        ``visit_particular_node`` method for a node of type
93
 
        ``particular_node``, the ``unknown_visit`` method is called.  (The
94
 
        `walkabout()` method is similar, except it also calls ``depart_...``
95
 
        methods before exiting each node.)
 
100
        Traverse a tree of `Node` objects, calling the
 
101
        `dispatch_visit()` method of `visitor` when entering each
 
102
        node.  (The `walkabout()` method is similar, except it also
 
103
        calls the `dispatch_departure()` method before exiting each
 
104
        node.)
96
105
 
97
106
        This tree traversal supports limited in-place tree
98
107
        modifications.  Replacing one node with one or more nodes is
100
109
        or replaced occurs after the current node, the old node will
101
110
        still be traversed, and any new nodes will not.
102
111
 
103
 
        Within ``visit_...`` methods (and ``depart_...`` methods for
 
112
        Within ``visit`` methods (and ``depart`` methods for
104
113
        `walkabout()`), `TreePruningException` subclasses may be raised
105
114
        (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`).
106
115
 
107
116
        Parameter `visitor`: A `NodeVisitor` object, containing a
108
 
        ``visit_...`` method for each `Node` subclass encountered.
 
117
        ``visit`` implementation for each `Node` subclass encountered.
109
118
        """
110
 
        name = 'visit_' + self.__class__.__name__
111
 
        method = getattr(visitor, name, visitor.unknown_visit)
112
 
        visitor.document.reporter.debug(name, category='nodes.Node.walk')
 
119
        visitor.document.reporter.debug(
 
120
            'docutils.nodes.Node.walk calling dispatch_visit for %s'
 
121
            % self.__class__.__name__)
113
122
        try:
114
 
            method(self)
 
123
            visitor.dispatch_visit(self)
115
124
        except (SkipChildren, SkipNode):
116
125
            return
117
126
        except SkipDeparture:           # not applicable; ignore
118
127
            pass
119
 
        children = self.get_children()
 
128
        children = self.children
120
129
        try:
121
130
            for child in children[:]:
122
131
                child.walk(visitor)
125
134
 
126
135
    def walkabout(self, visitor):
127
136
        """
128
 
        Perform a tree traversal similarly to `Node.walk()` (which see),
129
 
        except also call ``depart_...`` methods before exiting each node. If
130
 
        there is no ``depart_particular_node`` method for a node of type
131
 
        ``particular_node``, the ``unknown_departure`` method is called.
 
137
        Perform a tree traversal similarly to `Node.walk()` (which
 
138
        see), except also call the `dispatch_departure()` method
 
139
        before exiting each node.
132
140
 
133
 
        Parameter `visitor`: A `NodeVisitor` object, containing ``visit_...``
134
 
        and ``depart_...`` methods for each `Node` subclass encountered.
 
141
        Parameter `visitor`: A `NodeVisitor` object, containing a
 
142
        ``visit`` and ``depart`` implementation for each `Node`
 
143
        subclass encountered.
135
144
        """
136
145
        call_depart = 1
137
 
        name = 'visit_' + self.__class__.__name__
138
 
        method = getattr(visitor, name, visitor.unknown_visit)
139
 
        visitor.document.reporter.debug(name, category='nodes.Node.walkabout')
 
146
        visitor.document.reporter.debug(
 
147
            'docutils.nodes.Node.walkabout calling dispatch_visit for %s'
 
148
            % self.__class__.__name__)
140
149
        try:
141
150
            try:
142
 
                method(self)
 
151
                visitor.dispatch_visit(self)
143
152
            except SkipNode:
144
153
                return
145
154
            except SkipDeparture:
146
155
                call_depart = 0
147
 
            children = self.get_children()
 
156
            children = self.children
148
157
            try:
149
158
                for child in children[:]:
150
159
                    child.walkabout(visitor)
153
162
        except SkipChildren:
154
163
            pass
155
164
        if call_depart:
156
 
            name = 'depart_' + self.__class__.__name__
157
 
            method = getattr(visitor, name, visitor.unknown_departure)
158
165
            visitor.document.reporter.debug(
159
 
                  name, category='nodes.Node.walkabout')
160
 
            method(self)
161
 
 
 
166
                'docutils.nodes.Node.walkabout calling dispatch_departure '
 
167
                'for %s' % self.__class__.__name__)
 
168
            visitor.dispatch_departure(self)
 
169
 
 
170
    def traverse(self, condition=None,
 
171
                 include_self=1, descend=1, siblings=0, ascend=0):
 
172
        """
 
173
        Return an iterable containing
 
174
 
 
175
        * self (if include_self is true)
 
176
        * all descendants in tree traversal order (if descend is true)
 
177
        * all siblings (if siblings is true) and their descendants (if
 
178
          also descend is true)
 
179
        * the siblings of the parent (if ascend is true) and their
 
180
          descendants (if also descend is true), and so on
 
181
 
 
182
        If `condition` is not None, the iterable contains only nodes
 
183
        for which ``condition(node)`` is true.  If `condition` is a
 
184
        node class ``cls``, it is equivalent to a function consisting
 
185
        of ``return isinstance(node, cls)``.
 
186
 
 
187
        If ascend is true, assume siblings to be true as well.
 
188
 
 
189
        For example, given the following tree::
 
190
 
 
191
            <paragraph>
 
192
                <emphasis>      <--- emphasis.traverse() and
 
193
                    <strong>    <--- strong.traverse() are called.
 
194
                        Foo
 
195
                    Bar
 
196
                <reference name="Baz" refid="baz">
 
197
                    Baz
 
198
 
 
199
        Then list(emphasis.traverse()) equals ::
 
200
 
 
201
            [<emphasis>, <strong>, <#text: Foo>, <#text: Bar>]
 
202
 
 
203
        and list(strong.traverse(ascend=1)) equals ::
 
204
 
 
205
            [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>]
 
206
        """
 
207
        r = []
 
208
        if ascend:
 
209
            siblings=1
 
210
        if isinstance(condition, ClassType):
 
211
            node_class = condition
 
212
            def condition(node, node_class=node_class):
 
213
                return isinstance(node, node_class)
 
214
        if include_self and (condition is None or condition(self)):
 
215
            r.append(self)
 
216
        if descend and len(self.children):
 
217
            for child in self:
 
218
                r.extend(child.traverse(
 
219
                    include_self=1, descend=1, siblings=0, ascend=0,
 
220
                    condition=condition))
 
221
        if siblings or ascend:
 
222
            node = self
 
223
            while node.parent:
 
224
                index = node.parent.index(node)
 
225
                for sibling in node.parent[index+1:]:
 
226
                    r.extend(sibling.traverse(include_self=1, descend=descend,
 
227
                                              siblings=0, ascend=0,
 
228
                                              condition=condition))
 
229
                if not ascend:
 
230
                    break
 
231
                else:
 
232
                    node = node.parent
 
233
        return r
 
234
 
 
235
    def next_node(self, condition=None,
 
236
                  include_self=0, descend=1, siblings=0, ascend=0):
 
237
        """
 
238
        Return the first node in the iterable returned by traverse(),
 
239
        or None if the iterable is empty.
 
240
 
 
241
        Parameter list is the same as of traverse.  Note that
 
242
        include_self defaults to 0, though.
 
243
        """
 
244
        iterable = self.traverse(condition=condition,
 
245
                                 include_self=include_self, descend=descend,
 
246
                                 siblings=siblings, ascend=ascend)
 
247
        try:
 
248
            return iterable[0]
 
249
        except IndexError:
 
250
            return None
162
251
 
163
252
class Text(Node, UserString):
164
253
 
170
259
 
171
260
    tagname = '#text'
172
261
 
 
262
    children = ()
 
263
    """Text nodes have no children, and cannot have children."""
 
264
 
173
265
    def __init__(self, data, rawsource=''):
174
266
        UserString.__init__(self, data)
175
267
 
200
292
    def copy(self):
201
293
        return self.__class__(self.data)
202
294
 
 
295
    def deepcopy(self):
 
296
        return self.copy()
 
297
 
203
298
    def pformat(self, indent='    ', level=0):
204
299
        result = []
205
300
        indent = indent * level
207
302
            result.append(indent + line + '\n')
208
303
        return ''.join(result)
209
304
 
210
 
    def get_children(self):
211
 
        """Text nodes have no children. Return []."""
212
 
        return []
213
 
 
214
305
 
215
306
class Element(Node):
216
307
 
223
314
 
224
315
        element['att'] = 'value'
225
316
 
 
317
    There are two special attributes: 'ids' and 'names'.  Both are
 
318
    lists of unique identifiers, and names serve as human interfaces
 
319
    to IDs.  Names are case- and whitespace-normalized (see the
 
320
    fully_normalize_name() function), and IDs conform to the regular
 
321
    expression ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function).
 
322
 
226
323
    Elements also emulate lists for child nodes (element nodes and/or text
227
324
    nodes), indexing by integer.  To get the first child node, use::
228
325
 
243
340
    This is equivalent to ``element.extend([node1, node2])``.
244
341
    """
245
342
 
 
343
    list_attributes = ('ids', 'classes', 'names', 'dupnames', 'backrefs')
 
344
    """List attributes, automatically initialized to empty lists for
 
345
    all nodes."""
 
346
 
246
347
    tagname = None
247
348
    """The element generic identifier. If None, it is set as an instance
248
349
    attribute to the name of the class."""
262
363
        self.attributes = {}
263
364
        """Dictionary of attribute {name: value}."""
264
365
 
 
366
        # Initialize list attributes.
 
367
        for att in self.list_attributes:
 
368
            self.attributes[att] = []
 
369
 
265
370
        for att, value in attributes.items():
266
 
            self.attributes[att.lower()] = value
 
371
            att = att.lower()
 
372
            if att in self.list_attributes:
 
373
                # mutable list; make a copy for this node
 
374
                self.attributes[att] = value[:]
 
375
            else:
 
376
                self.attributes[att] = value
267
377
 
268
378
        if self.tagname is None:
269
379
            self.tagname = self.__class__.__name__
270
380
 
271
381
    def _dom_node(self, domroot):
272
382
        element = domroot.createElement(self.tagname)
273
 
        for attribute, value in self.attributes.items():
 
383
        for attribute, value in self.attlist():
274
384
            if isinstance(value, ListType):
275
 
                value = ' '.join(['%s' % v for v in value])
 
385
                value = ' '.join([serial_escape('%s' % v) for v in value])
276
386
            element.setAttribute(attribute, '%s' % value)
277
387
        for child in self.children:
278
388
            element.appendChild(child._dom_node(domroot))
285
395
            if len(data) > 60:
286
396
                data = data[:56] + ' ...'
287
397
                break
288
 
        if self.hasattr('name'):
 
398
        if self['names']:
289
399
            return '<%s "%s": %s>' % (self.__class__.__name__,
290
 
                                      self.attributes['name'], data)
 
400
                                      '; '.join(self['names']), data)
291
401
        else:
292
402
            return '<%s: %s>' % (self.__class__.__name__, data)
293
403
 
294
404
    def shortrepr(self):
295
 
        if self.hasattr('name'):
 
405
        if self['names']:
296
406
            return '<%s "%s"...>' % (self.__class__.__name__,
297
 
                                      self.attributes['name'])
 
407
                                     '; '.join(self['names']))
298
408
        else:
299
409
            return '<%s...>' % self.tagname
300
410
 
315
425
            if value is None:           # boolean attribute
316
426
                parts.append(name)
317
427
            elif isinstance(value, ListType):
318
 
                values = ['%s' % v for v in value]
 
428
                values = [serial_escape('%s' % v) for v in value]
319
429
                parts.append('%s="%s"' % (name, ' '.join(values)))
320
430
            else:
321
431
                parts.append('%s="%s"' % (name, value))
380
490
    def __iadd__(self, other):
381
491
        """Append a node or a list of nodes to `self.children`."""
382
492
        if isinstance(other, Node):
383
 
            self.setup_child(other)
384
 
            self.children.append(other)
 
493
            self.append(other)
385
494
        elif other is not None:
386
 
            for node in other:
387
 
                self.setup_child(node)
388
 
            self.children.extend(other)
 
495
            self.extend(other)
389
496
        return self
390
497
 
391
498
    def astext(self):
392
499
        return self.child_text_separator.join(
393
500
              [child.astext() for child in self.children])
394
501
 
 
502
    def non_default_attributes(self):
 
503
        atts = {}
 
504
        for key, value in self.attributes.items():
 
505
            if self.is_not_default(key):
 
506
                atts[key] = value
 
507
        return atts
 
508
 
395
509
    def attlist(self):
396
 
        attlist = self.attributes.items()
 
510
        attlist = self.non_default_attributes().items()
397
511
        attlist.sort()
398
512
        return attlist
399
513
 
418
532
 
419
533
    def extend(self, item):
420
534
        for node in item:
421
 
            self.setup_child(node)
422
 
        self.children.extend(item)
 
535
            self.append(node)
423
536
 
424
537
    def insert(self, index, item):
425
538
        if isinstance(item, Node):
437
550
    def index(self, item):
438
551
        return self.children.index(item)
439
552
 
 
553
    def is_not_default(self, key):
 
554
        if self[key] == [] and key in self.list_attributes:
 
555
            return 0
 
556
        else:
 
557
            return 1
 
558
 
 
559
    def update_basic_atts(self, dict):
 
560
        """
 
561
        Update basic attributes ('ids', 'names', 'classes',
 
562
        'dupnames', but not 'source') from node or dictionary `dict`.
 
563
        """
 
564
        if isinstance(dict, Node):
 
565
            dict = dict.attributes
 
566
        for att in ('ids', 'classes', 'names', 'dupnames'):
 
567
            for value in dict.get(att, []):
 
568
                if not value in self[att]:
 
569
                    self[att].append(value)
 
570
 
 
571
    def clear(self):
 
572
        self.children = []
 
573
 
440
574
    def replace(self, old, new):
441
575
        """Replace one child `Node` with another child or children."""
442
576
        index = self.index(old)
446
580
        elif new is not None:
447
581
            self[index:index+1] = new
448
582
 
 
583
    def replace_self(self, new):
 
584
        """
 
585
        Replace `self` node with `new`, where `new` is a node or a
 
586
        list of nodes.
 
587
        """
 
588
        update = new
 
589
        if not isinstance(new, Node):
 
590
            # `new` is a list; update first child.
 
591
            try:
 
592
                update = new[0]
 
593
            except IndexError:
 
594
                update = None
 
595
        if isinstance(update, Element):
 
596
            update.update_basic_atts(self)
 
597
        else:
 
598
            # `update` is a Text node or `new` is an empty list.
 
599
            # Assert that we aren't losing any attributes.
 
600
            for att in ('ids', 'names', 'classes', 'dupnames'):
 
601
                assert not self[att], \
 
602
                       'Losing "%s" attribute: %s' % (att, self[att])
 
603
        self.parent.replace(self, new)
 
604
 
449
605
    def first_child_matching_class(self, childclass, start=0, end=sys.maxint):
450
606
        """
451
607
        Return the index of the first child whose class exactly matches.
480
636
        if not isinstance(childclass, TupleType):
481
637
            childclass = (childclass,)
482
638
        for index in range(start, min(len(self), end)):
483
 
            match = 0
484
639
            for c in childclass:
485
640
                if isinstance(self.children[index], c):
486
 
                    match = 1
487
641
                    break
488
 
            if not match:
 
642
            else:
489
643
                return index
490
644
        return None
491
645
 
494
648
                       [child.pformat(indent, level+1)
495
649
                        for child in self.children])
496
650
 
497
 
    def get_children(self):
498
 
        """Return this element's children."""
499
 
        return self.children
500
 
 
501
651
    def copy(self):
502
652
        return self.__class__(**self.attributes)
503
653
 
 
654
    def deepcopy(self):
 
655
        copy = self.copy()
 
656
        copy.extend([child.deepcopy() for child in self.children])
 
657
        return copy
 
658
 
504
659
    def set_class(self, name):
505
 
        """Add a new name to the "class" attribute."""
506
 
        self.attributes['class'] = (self.attributes.get('class', '') + ' '
507
 
                                    + name.lower()).strip()
 
660
        """Add a new class to the "classes" attribute."""
 
661
        warnings.warn('docutils.nodes.Element.set_class deprecated; '
 
662
                      "append to Element['classes'] list attribute directly",
 
663
                      DeprecationWarning, stacklevel=2)
 
664
        assert ' ' not in name
 
665
        self['classes'].append(name.lower())
 
666
 
 
667
    def note_referenced_by(self, name=None, id=None):
 
668
        """Note that this Element has been referenced by its name
 
669
        `name` or id `id`."""
 
670
        self.referenced = 1
 
671
        # Element.expect_referenced_by_* dictionaries map names or ids
 
672
        # to nodes whose ``referenced`` attribute is set to true as
 
673
        # soon as this node is referenced by the given name or id.
 
674
        # Needed for target propagation.
 
675
        by_name = getattr(self, 'expect_referenced_by_name', {}).get(name)
 
676
        by_id = getattr(self, 'expect_referenced_by_id', {}).get(id)
 
677
        if by_name:
 
678
            assert name is not None
 
679
            by_name.referenced = 1
 
680
        if by_id:
 
681
            assert id is not None
 
682
            by_id.referenced = 1
508
683
 
509
684
 
510
685
class TextElement(Element):
512
687
    """
513
688
    An element which directly contains text.
514
689
 
515
 
    Its children are all `Text` or `TextElement` subclass nodes.  You can
 
690
    Its children are all `Text` or `Inline` subclass nodes.  You can
516
691
    check whether an element's context is inline simply by checking whether
517
692
    its immediate parent is a `TextElement` instance (including subclasses).
518
693
    This is handy for nodes like `image` that can appear both inline and as
555
730
class BackLinkable:
556
731
 
557
732
    def add_backref(self, refid):
558
 
        self.setdefault('backrefs', []).append(refid)
 
733
        self['backrefs'].append(refid)
559
734
 
560
735
 
561
736
# ====================
566
741
 
567
742
class Titular: pass
568
743
 
569
 
class PreDecorative:
570
 
    """Category of Node which may occur before Decorative Nodes."""
571
 
 
572
 
class PreBibliographic(PreDecorative):
 
744
class PreBibliographic:
573
745
    """Category of Node which may occur before Bibliographic Nodes."""
574
746
 
575
 
class Bibliographic(PreDecorative): pass
 
747
class Bibliographic: pass
576
748
 
577
 
class Decorative: pass
 
749
class Decorative(PreBibliographic): pass
578
750
 
579
751
class Structural: pass
580
752
 
582
754
 
583
755
class General(Body): pass
584
756
 
585
 
class Sequential(Body): pass
 
757
class Sequential(Body):
 
758
    """List-like elements."""
586
759
 
587
760
class Admonition(Body): pass
588
761
 
589
762
class Special(Body):
590
763
    """Special internal body elements."""
591
764
 
592
 
class Invisible:
 
765
class Invisible(PreBibliographic):
593
766
    """Internal elements that don't appear in output."""
594
767
 
595
768
class Part: pass
598
771
 
599
772
class Referential(Resolvable): pass
600
773
 
 
774
 
601
775
class Targetable(Resolvable):
602
776
 
603
777
    referenced = 0
604
778
 
605
779
    indirect_reference_name = None
606
 
    """Holds the whitespace_normalized_name (contains mixed case) of a target"""
 
780
    """Holds the whitespace_normalized_name (contains mixed case) of a target.
 
781
    Required for MoinMoin/reST compatibility."""
 
782
 
607
783
 
608
784
class Labeled:
609
785
    """Contains a `label` as its first element."""
615
791
 
616
792
class document(Root, Structural, Element):
617
793
 
 
794
    """
 
795
    The document root element.
 
796
 
 
797
    Do not instantiate this class directly; use
 
798
    `docutils.utils.new_document()` instead.
 
799
    """
 
800
 
618
801
    def __init__(self, settings, reporter, *args, **kwargs):
619
802
        Element.__init__(self, *args, **kwargs)
620
803
 
630
813
        self.reporter = reporter
631
814
        """System message generator."""
632
815
 
633
 
        self.external_targets = []
634
 
        """List of external target nodes."""
635
 
 
636
 
        self.internal_targets = []
637
 
        """List of internal target nodes."""
638
 
 
639
816
        self.indirect_targets = []
640
817
        """List of indirect target nodes."""
641
818
 
662
839
        self.ids = {}
663
840
        """Mapping of ids to nodes."""
664
841
 
665
 
        self.substitution_refs = {}
666
 
        """Mapping of substitution names to lists of substitution_reference
667
 
        nodes."""
668
 
 
669
842
        self.footnote_refs = {}
670
843
        """Mapping of footnote labels to lists of footnote_reference nodes."""
671
844
 
672
845
        self.citation_refs = {}
673
846
        """Mapping of citation labels to lists of citation_reference nodes."""
674
847
 
675
 
        self.anonymous_targets = []
676
 
        """List of anonymous target nodes."""
677
 
 
678
 
        self.anonymous_refs = []
679
 
        """List of anonymous reference nodes."""
680
 
 
681
848
        self.autofootnotes = []
682
849
        """List of auto-numbered footnote nodes."""
683
850
 
715
882
        self.transformer = docutils.transforms.Transformer(self)
716
883
        """Storage for transforms to be applied to this document."""
717
884
 
 
885
        self.decoration = None
 
886
        """Document's `decoration` node."""
 
887
 
718
888
        self.document = self
719
889
 
720
 
    def asdom(self, dom=xml.dom.minidom):
 
890
    def asdom(self, dom=None):
721
891
        """Return a DOM representation of this document."""
 
892
        if dom is None:
 
893
            import xml.dom.minidom as dom
722
894
        domroot = dom.Document()
723
895
        domroot.appendChild(self._dom_node(domroot))
724
896
        return domroot
725
897
 
726
898
    def set_id(self, node, msgnode=None):
727
 
        if node.has_key('id'):
728
 
            id = node['id']
 
899
        for id in node['ids']:
729
900
            if self.ids.has_key(id) and self.ids[id] is not node:
730
901
                msg = self.reporter.severe('Duplicate ID: "%s".' % id)
731
902
                if msgnode != None:
732
903
                    msgnode += msg
733
 
        else:
734
 
            if node.has_key('name'):
735
 
                id = make_id(node['name'])
 
904
        if not node['ids']:
 
905
            for name in node['names']:
 
906
                id = self.settings.id_prefix + make_id(name)
 
907
                if id and not self.ids.has_key(id):
 
908
                    break
736
909
            else:
737
910
                id = ''
738
 
            while not id or self.ids.has_key(id):
739
 
                id = 'id%s' % self.id_start
740
 
                self.id_start += 1
741
 
            node['id'] = id
 
911
                while not id or self.ids.has_key(id):
 
912
                    id = (self.settings.id_prefix +
 
913
                          self.settings.auto_id_prefix + str(self.id_start))
 
914
                    self.id_start += 1
 
915
            node['ids'].append(id)
742
916
        self.ids[id] = node
743
917
        return id
744
918
 
750
924
 
751
925
        The following state transition table shows how `self.nameids` ("ids")
752
926
        and `self.nametypes` ("types") change with new input (a call to this
753
 
        method), and what actions are performed:
 
927
        method), and what actions are performed ("implicit"-type system
 
928
        messages are INFO/1, and "explicit"-type system messages are ERROR/3):
754
929
 
755
930
        ====  =====  ========  ========  =======  ====  =====  =====
756
931
         Old State    Input          Action        New State   Notes
757
932
        -----------  --------  -----------------  -----------  -----
758
933
        ids   types  new type  sys.msg.  dupname  ids   types
759
934
        ====  =====  ========  ========  =======  ====  =====  =====
760
 
        --    --     explicit  --        --       new   True
761
 
        --    --     implicit  --        --       new   False
762
 
        None  False  explicit  --        --       new   True
 
935
        -     -      explicit  -         -        new   True
 
936
        -     -      implicit  -         -        new   False
 
937
        None  False  explicit  -         -        new   True
763
938
        old   False  explicit  implicit  old      new   True
764
939
        None  True   explicit  explicit  new      None  True
765
940
        old   True   explicit  explicit  new,old  None  True   [#]_
773
948
           both old and new targets are external and refer to identical URIs.
774
949
           The new target is invalidated regardless.
775
950
        """
776
 
        if node.has_key('name'):
777
 
            name = node['name']
 
951
        for name in node['names']:
778
952
            if self.nameids.has_key(name):
779
953
                self.set_duplicate_name_id(node, id, name, msgnode, explicit)
780
954
            else:
792
966
                    old_node = self.ids[old_id]
793
967
                    if node.has_key('refuri'):
794
968
                        refuri = node['refuri']
795
 
                        if old_node.has_key('name') \
 
969
                        if old_node['names'] \
796
970
                               and old_node.has_key('refuri') \
797
971
                               and old_node['refuri'] == refuri:
798
972
                            level = 1   # just inform if refuri's identical
799
973
                    if level > 1:
800
 
                        dupname(old_node)
 
974
                        dupname(old_node, name)
801
975
                        self.nameids[name] = None
802
976
                msg = self.reporter.system_message(
803
977
                    level, 'Duplicate explicit target name: "%s".' % name,
804
978
                    backrefs=[id], base_node=node)
805
979
                if msgnode != None:
806
980
                    msgnode += msg
807
 
                dupname(node)
 
981
                dupname(node, name)
808
982
            else:
809
983
                self.nameids[name] = id
810
984
                if old_id is not None:
811
985
                    old_node = self.ids[old_id]
812
 
                    dupname(old_node)
 
986
                    dupname(old_node, name)
813
987
        else:
814
988
            if old_id is not None and not old_explicit:
815
989
                self.nameids[name] = None
816
990
                old_node = self.ids[old_id]
817
 
                dupname(old_node)
818
 
            dupname(node)
 
991
                dupname(old_node, name)
 
992
            dupname(node, name)
819
993
        if not explicit or (not old_explicit and old_id is not None):
820
994
            msg = self.reporter.info(
821
995
                'Duplicate implicit target name: "%s".' % name,
841
1015
    def note_refid(self, node):
842
1016
        self.refids.setdefault(node['refid'], []).append(node)
843
1017
 
844
 
    def note_external_target(self, target):
845
 
        self.external_targets.append(target)
846
 
 
847
 
    def note_internal_target(self, target):
848
 
        self.internal_targets.append(target)
849
 
 
850
1018
    def note_indirect_target(self, target):
851
1019
        self.indirect_targets.append(target)
852
 
        if target.has_key('name'):
 
1020
        if target['names']:
853
1021
            self.note_refname(target)
854
1022
 
855
1023
    def note_anonymous_target(self, target):
856
1024
        self.set_id(target)
857
 
        self.anonymous_targets.append(target)
858
 
 
859
 
    def note_anonymous_ref(self, ref):
860
 
        self.anonymous_refs.append(ref)
861
1025
 
862
1026
    def note_autofootnote(self, footnote):
863
1027
        self.set_id(footnote)
893
1057
        self.note_refname(ref)
894
1058
 
895
1059
    def note_substitution_def(self, subdef, def_name, msgnode=None):
896
 
        name = subdef['name'] = whitespace_normalize_name(def_name)
 
1060
        name = whitespace_normalize_name(def_name)
897
1061
        if self.substitution_defs.has_key(name):
898
1062
            msg = self.reporter.error(
899
1063
                  'Duplicate substitution definition name: "%s".' % name,
901
1065
            if msgnode != None:
902
1066
                msgnode += msg
903
1067
            oldnode = self.substitution_defs[name]
904
 
            dupname(oldnode)
 
1068
            dupname(oldnode, name)
905
1069
        # keep only the last definition:
906
1070
        self.substitution_defs[name] = subdef
907
1071
        # case-insensitive mapping:
908
1072
        self.substitution_names[fully_normalize_name(name)] = name
909
1073
 
910
1074
    def note_substitution_ref(self, subref, refname):
911
 
        name = subref['refname'] = whitespace_normalize_name(refname)
912
 
        self.substitution_refs.setdefault(name, []).append(subref)
 
1075
        subref['refname'] = whitespace_normalize_name(refname)
913
1076
 
914
1077
    def note_pending(self, pending, priority=None):
915
1078
        self.transformer.add_pending(pending, priority)
931
1094
        return self.__class__(self.settings, self.reporter,
932
1095
                              **self.attributes)
933
1096
 
 
1097
    def get_decoration(self):
 
1098
        if not self.decoration:
 
1099
            self.decoration = decoration()
 
1100
            index = self.first_child_not_matching_class(Titular)
 
1101
            if index is None:
 
1102
                self.append(self.decoration)
 
1103
            else:
 
1104
                self.insert(index, self.decoration)
 
1105
        return self.decoration
 
1106
 
934
1107
 
935
1108
# ================
936
1109
#  Title Elements
946
1119
# ========================
947
1120
 
948
1121
class docinfo(Bibliographic, Element): pass
949
 
class info(Bibliographic, Element): pass
950
1122
class author(Bibliographic, TextElement): pass
951
1123
class authors(Bibliographic, Element): pass
952
1124
class organization(Bibliographic, TextElement): pass
963
1135
#  Decorative Elements
964
1136
# =====================
965
1137
 
966
 
class decoration(Decorative, Element): pass
 
1138
class decoration(Decorative, Element):
 
1139
 
 
1140
    def get_header(self):
 
1141
        if not len(self.children) or not isinstance(self.children[0], header):
 
1142
            self.insert(0, header())
 
1143
        return self.children[0]
 
1144
 
 
1145
    def get_footer(self):
 
1146
        if not len(self.children) or not isinstance(self.children[-1], footer):
 
1147
            self.append(footer())
 
1148
        return self.children[-1]
 
1149
 
 
1150
 
967
1151
class header(Decorative, Element): pass
968
1152
class footer(Decorative, Element): pass
969
1153
 
1014
1198
# ===============
1015
1199
 
1016
1200
class paragraph(General, TextElement): pass
 
1201
class compound(General, Element): pass
 
1202
class container(General, Element): pass
1017
1203
class bullet_list(Sequential, Element): pass
1018
1204
class enumerated_list(Sequential, Element): pass
1019
1205
class list_item(Part, Element): pass
1056
1242
class description(Part, Element): pass
1057
1243
class literal_block(General, FixedTextElement): pass
1058
1244
class doctest_block(General, FixedTextElement): pass
1059
 
class line_block(General, FixedTextElement): pass
 
1245
class line_block(General, Element): pass
 
1246
 
 
1247
 
 
1248
class line(Part, TextElement):
 
1249
 
 
1250
    indent = None
 
1251
 
 
1252
 
1060
1253
class block_quote(General, Element): pass
1061
1254
class attribution(Part, TextElement): pass
1062
1255
class attention(Admonition, Element): pass
1069
1262
class hint(Admonition, Element): pass
1070
1263
class warning(Admonition, Element): pass
1071
1264
class admonition(Admonition, Element): pass
1072
 
class comment(Special, Invisible, PreBibliographic, FixedTextElement): pass
 
1265
class comment(Special, Invisible, FixedTextElement): pass
1073
1266
class substitution_definition(Special, Invisible, TextElement): pass
1074
1267
class target(Special, Invisible, Inline, TextElement, Targetable): pass
1075
 
class footnote(General, Element, Labeled, BackLinkable): pass
1076
 
class citation(General, Element, Labeled, BackLinkable): pass
 
1268
class footnote(General, BackLinkable, Element, Labeled, Targetable): pass
 
1269
class citation(General, BackLinkable, Element, Labeled, Targetable): pass
1077
1270
class label(Part, TextElement): pass
1078
1271
class figure(General, Element): pass
1079
1272
class caption(Part, TextElement): pass
1087
1280
class entry(Part, Element): pass
1088
1281
 
1089
1282
 
1090
 
class system_message(Special, PreBibliographic, Element, BackLinkable):
 
1283
class system_message(Special, BackLinkable, PreBibliographic, Element):
 
1284
 
 
1285
    """
 
1286
    System message element.
 
1287
 
 
1288
    Do not instantiate this class directly; use
 
1289
    ``document.reporter.info/warning/error/severe()`` instead.
 
1290
    """
1091
1291
 
1092
1292
    def __init__(self, message=None, *children, **attributes):
1093
1293
        if message:
1105
1305
                                       self['level'], Element.astext(self))
1106
1306
 
1107
1307
 
1108
 
class pending(Special, Invisible, PreBibliographic, Element):
 
1308
class pending(Special, Invisible, Element):
1109
1309
 
1110
1310
    """
1111
1311
    The "pending" element is used to encapsulate a pending operation: the
1123
1323
 
1124
1324
    But the "contents" directive can't do its work until the entire document
1125
1325
    has been parsed and possibly transformed to some extent.  So the directive
1126
 
    code leaves a placeholder behind that will trigger the second phase of the
1127
 
    its processing, something like this::
 
1326
    code leaves a placeholder behind that will trigger the second phase of its
 
1327
    processing, something like this::
1128
1328
 
1129
1329
        <pending ...public attributes...> + internal attributes
1130
1330
 
1171
1371
 
1172
1372
    def copy(self):
1173
1373
        return self.__class__(self.transform, self.details, self.rawsource,
1174
 
                              **self.attribuates)
 
1374
                              **self.attributes)
1175
1375
 
1176
1376
 
1177
1377
class raw(Special, Inline, PreBibliographic, FixedTextElement):
1201
1401
class subscript(Inline, TextElement): pass
1202
1402
 
1203
1403
 
1204
 
class image(General, Inline, TextElement):
 
1404
class image(General, Inline, Element):
1205
1405
 
1206
1406
    def astext(self):
1207
1407
        return self.get('alt', '')
1222
1422
        authors
1223
1423
    block_quote bullet_list
1224
1424
    caption caution citation citation_reference classifier colspec comment
1225
 
        contact copyright
 
1425
        compound contact container copyright
1226
1426
    danger date decoration definition definition_list definition_list_item
1227
1427
        description docinfo doctest_block document
1228
1428
    emphasis entry enumerated_list error
1230
1430
        footnote footnote_reference
1231
1431
    generated
1232
1432
    header hint
1233
 
    image important info inline
1234
 
    label legend line_block list_item literal literal_block
 
1433
    image important inline
 
1434
    label legend line line_block list_item literal literal_block
1235
1435
    note
1236
1436
    option option_argument option_group option_list option_list_item
1237
1437
        option_string organization
1249
1449
class NodeVisitor:
1250
1450
 
1251
1451
    """
1252
 
    "Visitor" pattern [GoF95]_ abstract superclass implementation for document
1253
 
    tree traversals.
1254
 
 
1255
 
    Each node class has corresponding methods, doing nothing by default;
1256
 
    override individual methods for specific and useful behaviour.  The
1257
 
    "``visit_`` + node class name" method is called by `Node.walk()` upon
1258
 
    entering a node.  `Node.walkabout()` also calls the "``depart_`` + node
1259
 
    class name" method before exiting a node.
 
1452
    "Visitor" pattern [GoF95]_ abstract superclass implementation for
 
1453
    document tree traversals.
 
1454
 
 
1455
    Each node class has corresponding methods, doing nothing by
 
1456
    default; override individual methods for specific and useful
 
1457
    behaviour.  The `dispatch_visit()` method is called by
 
1458
    `Node.walk()` upon entering a node.  `Node.walkabout()` also calls
 
1459
    the `dispatch_departure()` method before exiting a node.
 
1460
 
 
1461
    The dispatch methods call "``visit_`` + node class name" or
 
1462
    "``depart_`` + node class name", resp.
1260
1463
 
1261
1464
    This is a base class for visitors whose ``visit_...`` & ``depart_...``
1262
1465
    methods should be implemented for *all* node types encountered (such as
1272
1475
       1995.
1273
1476
    """
1274
1477
 
 
1478
    optional = ()
 
1479
    """
 
1480
    Tuple containing node class names (as strings).
 
1481
 
 
1482
    No exception will be raised if writers do not implement visit
 
1483
    or departure functions for these node classes.
 
1484
 
 
1485
    Used to ensure transitional compatibility with existing 3rd-party writers.
 
1486
    """
 
1487
 
1275
1488
    def __init__(self, document):
1276
1489
        self.document = document
1277
1490
 
 
1491
    def dispatch_visit(self, node):
 
1492
        """
 
1493
        Call self."``visit_`` + node class name" with `node` as
 
1494
        parameter.  If the ``visit_...`` method does not exist, call
 
1495
        self.unknown_visit.
 
1496
        """
 
1497
        node_name = node.__class__.__name__
 
1498
        method = getattr(self, 'visit_' + node_name, self.unknown_visit)
 
1499
        self.document.reporter.debug(
 
1500
            'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s'
 
1501
            % (method.__name__, node_name))
 
1502
        return method(node)
 
1503
 
 
1504
    def dispatch_departure(self, node):
 
1505
        """
 
1506
        Call self."``depart_`` + node class name" with `node` as
 
1507
        parameter.  If the ``depart_...`` method does not exist, call
 
1508
        self.unknown_departure.
 
1509
        """
 
1510
        node_name = node.__class__.__name__
 
1511
        method = getattr(self, 'depart_' + node_name, self.unknown_departure)
 
1512
        self.document.reporter.debug(
 
1513
            'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s'
 
1514
            % (method.__name__, node_name))
 
1515
        return method(node)
 
1516
 
1278
1517
    def unknown_visit(self, node):
1279
1518
        """
1280
1519
        Called when entering unknown `Node` types.
1281
1520
 
1282
1521
        Raise an exception unless overridden.
1283
1522
        """
1284
 
        raise NotImplementedError('%s visiting unknown node type: %s'
1285
 
                                  % (self.__class__, node.__class__.__name__))
 
1523
        if  (node.document.settings.strict_visitor
 
1524
             or node.__class__.__name__ not in self.optional):
 
1525
            raise NotImplementedError(
 
1526
                '%s visiting unknown node type: %s'
 
1527
                % (self.__class__, node.__class__.__name__))
1286
1528
 
1287
1529
    def unknown_departure(self, node):
1288
1530
        """
1290
1532
 
1291
1533
        Raise exception unless overridden.
1292
1534
        """
1293
 
        raise NotImplementedError('%s departing unknown node type: %s'
1294
 
                                  % (self.__class__, node.__class__.__name__))
 
1535
        if  (node.document.settings.strict_visitor
 
1536
             or node.__class__.__name__ not in self.optional):
 
1537
            raise NotImplementedError(
 
1538
                '%s departing unknown node type: %s'
 
1539
                % (self.__class__, node.__class__.__name__))
1295
1540
 
1296
1541
 
1297
1542
class SparseNodeVisitor(NodeVisitor):
1303
1548
    subclasses), subclass `NodeVisitor` instead.
1304
1549
    """
1305
1550
 
 
1551
 
1306
1552
class GenericNodeVisitor(NodeVisitor):
1307
1553
 
1308
1554
    """
1344
1590
        setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
1345
1591
        setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
1346
1592
        setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
1347
 
        setattr(SparseNodeVisitor, 'depart' + _name, _nop)
 
1593
        setattr(SparseNodeVisitor, 'depart_' + _name, _nop)
1348
1594
 
1349
1595
_add_node_class_names(node_class_names)
1350
1596
 
 
1597
 
1351
1598
class TreeCopyVisitor(GenericNodeVisitor):
1352
1599
 
1353
1600
    """
1480
1727
_non_id_chars = re.compile('[^a-z0-9]+')
1481
1728
_non_id_at_ends = re.compile('^[-0-9]+|-+$')
1482
1729
 
1483
 
def dupname(node):
1484
 
    node['dupname'] = node['name']
1485
 
    del node['name']
 
1730
def dupname(node, name):
 
1731
    node['dupnames'].append(name)
 
1732
    node['names'].remove(name)
 
1733
    # Assume that this method is referenced, even though it isn't; we
 
1734
    # don't want to throw unnecessary system_messages.
 
1735
    node.referenced = 1
1486
1736
 
1487
1737
def fully_normalize_name(name):
1488
1738
    """Return a case- and whitespace-normalized name."""
1491
1741
def whitespace_normalize_name(name):
1492
1742
    """Return a whitespace-normalized name."""
1493
1743
    return ' '.join(name.split())
 
1744
 
 
1745
def serial_escape(value):
 
1746
    """Escape string values that are elements of a list, for serialization."""
 
1747
    return value.replace('\\', r'\\').replace(' ', r'\ ')
 
1748
 
 
1749
 
1750
#
 
1751
# Local Variables:
 
1752
# indent-tabs-mode: nil
 
1753
# sentence-end-double-space: t
 
1754
# fill-column: 78
 
1755
# End: