Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/docutils/nodes.py: 57%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# $Id$
2# Author: David Goodger <goodger@python.org>
3# Maintainer: docutils-develop@lists.sourceforge.net
4# Copyright: This module has been placed in the public domain.
6"""
7Docutils document tree element class library.
9Classes in CamelCase are abstract base classes or auxiliary classes. The one
10exception is `Text`, for a text (PCDATA) node; uppercase is used to
11differentiate from element classes. Classes in lower_case_with_underscores
12are element classes, matching the XML element generic identifiers in the DTD_.
14The position of each node (the level at which it can occur) is significant and
15is represented by abstract base classes (`Root`, `Structural`, `Body`,
16`Inline`, etc.). Certain transformations will be easier because we can use
17``isinstance(node, base_class)`` to determine the position of the node in the
18hierarchy.
20.. _DTD: https://docutils.sourceforge.io/docs/ref/docutils.dtd
21"""
23__docformat__ = 'reStructuredText'
25from collections import Counter
26import re
27import sys
28import unicodedata
29import warnings
30# import xml.dom.minidom as dom # -> conditional import in Node.asdom()
31# and document.asdom()
33# import docutils.transforms # -> conditional import in document.__init__()
36# ==============================
37# Functional Node Base Classes
38# ==============================
40class Node:
41 """Abstract base class of nodes in a document tree."""
43 parent = None
44 """Back-reference to the Node immediately containing this Node."""
46 source = None
47 """Path or description of the input source which generated this Node."""
49 line = None
50 """The line number (1-based) of the beginning of this Node in `source`."""
52 _document = None
54 @property
55 def document(self):
56 """Return the `document` root node of the tree containing this Node.
57 """
58 try:
59 return self._document or self.parent.document
60 except AttributeError:
61 return None
63 @document.setter
64 def document(self, value):
65 self._document = value
67 def __bool__(self):
68 """
69 Node instances are always true, even if they're empty. A node is more
70 than a simple container. Its boolean "truth" does not depend on
71 having one or more subnodes in the doctree.
73 Use `len()` to check node length.
74 """
75 return True
77 def asdom(self, dom=None):
78 """Return a DOM **fragment** representation of this Node."""
79 if dom is None:
80 import xml.dom.minidom as dom
81 domroot = dom.Document()
82 return self._dom_node(domroot)
84 def pformat(self, indent=' ', level=0):
85 """
86 Return an indented pseudo-XML representation, for test purposes.
88 Override in subclasses.
89 """
90 raise NotImplementedError
92 def copy(self):
93 """Return a copy of self."""
94 raise NotImplementedError
96 def deepcopy(self):
97 """Return a deep copy of self (also copying children)."""
98 raise NotImplementedError
100 def astext(self):
101 """Return a string representation of this Node."""
102 raise NotImplementedError
104 def setup_child(self, child):
105 child.parent = self
106 if self.document:
107 child.document = self.document
108 if child.source is None:
109 child.source = self.document.current_source
110 if child.line is None:
111 child.line = self.document.current_line
113 def walk(self, visitor):
114 """
115 Traverse a tree of `Node` objects, calling the
116 `dispatch_visit()` method of `visitor` when entering each
117 node. (The `walkabout()` method is similar, except it also
118 calls the `dispatch_departure()` method before exiting each
119 node.)
121 This tree traversal supports limited in-place tree
122 modifications. Replacing one node with one or more nodes is
123 OK, as is removing an element. However, if the node removed
124 or replaced occurs after the current node, the old node will
125 still be traversed, and any new nodes will not.
127 Within ``visit`` methods (and ``depart`` methods for
128 `walkabout()`), `TreePruningException` subclasses may be raised
129 (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`).
131 Parameter `visitor`: A `NodeVisitor` object, containing a
132 ``visit`` implementation for each `Node` subclass encountered.
134 Return true if we should stop the traversal.
135 """
136 stop = False
137 visitor.document.reporter.debug(
138 'docutils.nodes.Node.walk calling dispatch_visit for %s'
139 % self.__class__.__name__)
140 try:
141 try:
142 visitor.dispatch_visit(self)
143 except (SkipChildren, SkipNode):
144 return stop
145 except SkipDeparture: # not applicable; ignore
146 pass
147 children = self.children
148 try:
149 for child in children[:]:
150 if child.walk(visitor):
151 stop = True
152 break
153 except SkipSiblings:
154 pass
155 except StopTraversal:
156 stop = True
157 return stop
159 def walkabout(self, visitor):
160 """
161 Perform a tree traversal similarly to `Node.walk()` (which
162 see), except also call the `dispatch_departure()` method
163 before exiting each node.
165 Parameter `visitor`: A `NodeVisitor` object, containing a
166 ``visit`` and ``depart`` implementation for each `Node`
167 subclass encountered.
169 Return true if we should stop the traversal.
170 """
171 call_depart = True
172 stop = False
173 visitor.document.reporter.debug(
174 'docutils.nodes.Node.walkabout calling dispatch_visit for %s'
175 % self.__class__.__name__)
176 try:
177 try:
178 visitor.dispatch_visit(self)
179 except SkipNode:
180 return stop
181 except SkipDeparture:
182 call_depart = False
183 children = self.children
184 try:
185 for child in children[:]:
186 if child.walkabout(visitor):
187 stop = True
188 break
189 except SkipSiblings:
190 pass
191 except SkipChildren:
192 pass
193 except StopTraversal:
194 stop = True
195 if call_depart:
196 visitor.document.reporter.debug(
197 'docutils.nodes.Node.walkabout calling dispatch_departure '
198 'for %s' % self.__class__.__name__)
199 visitor.dispatch_departure(self)
200 return stop
202 def _fast_findall(self, cls):
203 """Return iterator that only supports instance checks."""
204 if isinstance(self, cls):
205 yield self
206 for child in self.children:
207 yield from child._fast_findall(cls)
209 def _superfast_findall(self):
210 """Return iterator that doesn't check for a condition."""
211 # This is different from ``iter(self)`` implemented via
212 # __getitem__() and __len__() in the Element subclass,
213 # which yields only the direct children.
214 yield self
215 for child in self.children:
216 yield from child._superfast_findall()
218 def findall(self, condition=None, include_self=True, descend=True,
219 siblings=False, ascend=False):
220 """
221 Return an iterator yielding nodes following `self`:
223 * self (if `include_self` is true)
224 * all descendants in tree traversal order (if `descend` is true)
225 * the following siblings (if `siblings` is true) and their
226 descendants (if also `descend` is true)
227 * the following siblings of the parent (if `ascend` is true) and
228 their descendants (if also `descend` is true), and so on.
230 If `condition` is not None, the iterator yields only nodes
231 for which ``condition(node)`` is true. If `condition` is a
232 node class ``cls``, it is equivalent to a function consisting
233 of ``return isinstance(node, cls)``.
235 If `ascend` is true, assume `siblings` to be true as well.
237 If the tree structure is modified during iteration, the result
238 is undefined.
240 For example, given the following tree::
242 <paragraph>
243 <emphasis> <--- emphasis.traverse() and
244 <strong> <--- strong.traverse() are called.
245 Foo
246 Bar
247 <reference name="Baz" refid="baz">
248 Baz
250 Then tuple(emphasis.traverse()) equals ::
252 (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>)
254 and list(strong.traverse(ascend=True) equals ::
256 [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>]
257 """
258 if ascend:
259 siblings = True
260 # Check for special argument combinations that allow using an
261 # optimized version of traverse()
262 if include_self and descend and not siblings:
263 if condition is None:
264 yield from self._superfast_findall()
265 return
266 elif isinstance(condition, type):
267 yield from self._fast_findall(condition)
268 return
269 # Check if `condition` is a class (check for TypeType for Python
270 # implementations that use only new-style classes, like PyPy).
271 if isinstance(condition, type):
272 node_class = condition
274 def condition(node, node_class=node_class):
275 return isinstance(node, node_class)
277 if include_self and (condition is None or condition(self)):
278 yield self
279 if descend and len(self.children):
280 for child in self:
281 yield from child.findall(condition=condition,
282 include_self=True, descend=True,
283 siblings=False, ascend=False)
284 if siblings or ascend:
285 node = self
286 while node.parent:
287 index = node.parent.index(node)
288 # extra check since Text nodes have value-equality
289 while node.parent[index] is not node:
290 index = node.parent.index(node, index + 1)
291 for sibling in node.parent[index+1:]:
292 yield from sibling.findall(
293 condition=condition,
294 include_self=True, descend=descend,
295 siblings=False, ascend=False)
296 if not ascend:
297 break
298 else:
299 node = node.parent
301 def traverse(self, condition=None, include_self=True, descend=True,
302 siblings=False, ascend=False):
303 """Return list of nodes following `self`.
305 For looping, Node.findall() is faster and more memory efficient.
306 """
307 # traverse() may be eventually removed:
308 warnings.warn('nodes.Node.traverse() is obsoleted by Node.findall().',
309 PendingDeprecationWarning, stacklevel=2)
310 return list(self.findall(condition, include_self, descend,
311 siblings, ascend))
313 def next_node(self, condition=None, include_self=False, descend=True,
314 siblings=False, ascend=False):
315 """
316 Return the first node in the iterator returned by findall(),
317 or None if the iterable is empty.
319 Parameter list is the same as of `findall()`. Note that `include_self`
320 defaults to False, though.
321 """
322 try:
323 return next(self.findall(condition, include_self,
324 descend, siblings, ascend))
325 except StopIteration:
326 return None
329class Text(Node, str):
330 """
331 Instances are terminal nodes (leaves) containing text only; no child
332 nodes or attributes. Initialize by passing a string to the constructor.
334 Access the raw (null-escaped) text with ``str(<instance>)``
335 and unescaped text with ``<instance>.astext()``.
336 """
338 tagname = '#text'
340 children = ()
341 """Text nodes have no children, and cannot have children."""
343 def __new__(cls, data, rawsource=None):
344 """Assert that `data` is not an array of bytes
345 and warn if the deprecated `rawsource` argument is used.
346 """
347 if isinstance(data, bytes):
348 raise TypeError('expecting str data, not bytes')
349 if rawsource is not None:
350 warnings.warn('nodes.Text: initialization argument "rawsource" '
351 'is ignored and will be removed in Docutils 2.0.',
352 DeprecationWarning, stacklevel=2)
353 return str.__new__(cls, data)
355 def shortrepr(self, maxlen=18):
356 data = self
357 if len(data) > maxlen:
358 data = data[:maxlen-4] + ' ...'
359 return '<%s: %r>' % (self.tagname, str(data))
361 def __repr__(self):
362 return self.shortrepr(maxlen=68)
364 def astext(self):
365 return str(unescape(self))
367 def _dom_node(self, domroot):
368 return domroot.createTextNode(str(self))
370 def copy(self):
371 return self.__class__(str(self))
373 def deepcopy(self):
374 return self.copy()
376 def pformat(self, indent=' ', level=0):
377 try:
378 if self.document.settings.detailed:
379 tag = '%s%s' % (indent*level, '<#text>')
380 lines = (indent*(level+1) + repr(line)
381 for line in self.splitlines(True))
382 return '\n'.join((tag, *lines)) + '\n'
383 except AttributeError:
384 pass
385 indent = indent * level
386 lines = [indent+line for line in self.astext().splitlines()]
387 if not lines:
388 return ''
389 return '\n'.join(lines) + '\n'
391 # rstrip and lstrip are used by substitution definitions where
392 # they are expected to return a Text instance, this was formerly
393 # taken care of by UserString.
395 def rstrip(self, chars=None):
396 return self.__class__(str.rstrip(self, chars))
398 def lstrip(self, chars=None):
399 return self.__class__(str.lstrip(self, chars))
401 def validate(self):
402 pass # Text nodes have no attributes and no children.
405class Element(Node):
406 """
407 `Element` is the superclass to all specific elements.
409 Elements contain attributes and child nodes.
410 They can be described as a cross between a list and a dictionary.
412 Elements emulate dictionaries for external [#]_ attributes, indexing by
413 attribute name (a string). To set the attribute 'att' to 'value', do::
415 element['att'] = 'value'
417 .. [#] External attributes correspond to the XML element attributes.
418 From its `Node` superclass, Element also inherits "internal"
419 class attributes that are accessed using the standard syntax, e.g.
420 ``element.parent``.
422 There are two special attributes: 'ids' and 'names'. Both are
423 lists of unique identifiers: 'ids' conform to the regular expression
424 ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function for rationale and
425 details). 'names' serve as user-friendly interfaces to IDs; they are
426 case- and whitespace-normalized (see the fully_normalize_name() function).
428 Elements emulate lists for child nodes (element nodes and/or text
429 nodes), indexing by integer. To get the first child node, use::
431 element[0]
433 to iterate over the child nodes (without descending), use::
435 for child in element:
436 ...
438 Elements may be constructed using the ``+=`` operator. To add one new
439 child node to element, do::
441 element += node
443 This is equivalent to ``element.append(node)``.
445 To add a list of multiple child nodes at once, use the same ``+=``
446 operator::
448 element += [node1, node2]
450 This is equivalent to ``element.extend([node1, node2])``.
451 """
453 list_attributes = ('ids', 'classes', 'names', 'dupnames')
454 """Tuple of attributes that are initialized to empty lists.
456 NOTE: Derived classes should update this value when supporting
457 additional list attributes.
458 """
460 valid_attributes = list_attributes + ('source',)
461 """Tuple of attributes that are valid for elements of this class.
463 NOTE: Derived classes should update this value when supporting
464 additional attributes.
465 """
467 common_attributes = valid_attributes
468 """Tuple of `common attributes`__ known to all Doctree Element classes.
470 __ https://docutils.sourceforge.io/docs/ref/doctree.html#common-attributes
471 """
473 known_attributes = common_attributes
474 """Alias for `common_attributes`. Will be removed in Docutils 2.0."""
476 basic_attributes = list_attributes
477 """Common list attributes. Deprecated. Will be removed in Docutils 2.0."""
479 local_attributes = ('backrefs',)
480 """Obsolete. Will be removed in Docutils 2.0."""
482 valid_children = tuple()
483 """Valid class or tuple of valid classes for child elements.
485 NOTE: Derived classes should update this value
486 when supporting child elements.
487 """
489 valid_len = (1, None)
490 """Tuple of minimal and maximal number of child elements.
492 A maximal value of None stands for "no upper limit".
494 Default: one or more child elements.
496 NOTE: Derived classes should update this value when there are different
497 restrictions to the number of child elements.
498 """
500 tagname = None
501 """The element generic identifier.
503 If None, it is set as an instance attribute to the name of the class.
504 """
506 child_text_separator = '\n\n'
507 """Separator for child nodes, used by `astext()` method."""
509 def __init__(self, rawsource='', *children, **attributes):
510 self.rawsource = rawsource
511 """The raw text from which this element was constructed.
513 For informative and debugging purposes. Don't rely on its value!
515 NOTE: some elements do not set this value (default '').
516 """
518 self.children = []
519 """List of child nodes (elements and/or `Text`)."""
521 self.extend(children) # maintain parent info
523 self.attributes = {}
524 """Dictionary of attribute {name: value}."""
526 # Initialize list attributes.
527 for att in self.list_attributes:
528 self.attributes[att] = []
530 for att, value in attributes.items():
531 att = att.lower() # normalize attribute name
532 if att in self.list_attributes:
533 # lists are mutable; make a copy for this node
534 self.attributes[att] = value[:]
535 else:
536 self.attributes[att] = value
538 if self.tagname is None:
539 self.tagname = self.__class__.__name__
541 def _dom_node(self, domroot):
542 element = domroot.createElement(self.tagname)
543 for attribute, value in self.attlist():
544 if isinstance(value, list):
545 value = ' '.join(serial_escape('%s' % (v,)) for v in value)
546 element.setAttribute(attribute, '%s' % value)
547 for child in self.children:
548 element.appendChild(child._dom_node(domroot))
549 return element
551 def __repr__(self):
552 data = ''
553 for c in self.children:
554 data += c.shortrepr()
555 if len(data) > 60:
556 data = data[:56] + ' ...'
557 break
558 if self['names']:
559 return '<%s "%s": %s>' % (self.__class__.__name__,
560 '; '.join(self['names']), data)
561 else:
562 return '<%s: %s>' % (self.__class__.__name__, data)
564 def shortrepr(self):
565 if self['names']:
566 return '<%s "%s"...>' % (self.__class__.__name__,
567 '; '.join(self['names']))
568 else:
569 return '<%s...>' % self.tagname
571 def __str__(self):
572 if self.children:
573 return '%s%s%s' % (self.starttag(),
574 ''.join(str(c) for c in self.children),
575 self.endtag())
576 else:
577 return self.emptytag()
579 def starttag(self, quoteattr=None):
580 # the optional arg is used by the docutils_xml writer
581 if quoteattr is None:
582 quoteattr = pseudo_quoteattr
583 parts = [self.tagname]
584 for name, value in self.attlist():
585 if value is None: # boolean attribute
586 parts.append('%s="True"' % name)
587 continue
588 if isinstance(value, bool):
589 value = str(int(value))
590 if isinstance(value, list):
591 values = [serial_escape('%s' % (v,)) for v in value]
592 value = ' '.join(values)
593 else:
594 value = str(value)
595 value = quoteattr(value)
596 parts.append('%s=%s' % (name, value))
597 return '<%s>' % ' '.join(parts)
599 def endtag(self):
600 return '</%s>' % self.tagname
602 def emptytag(self):
603 attributes = ('%s="%s"' % (n, v) for n, v in self.attlist())
604 return '<%s/>' % ' '.join((self.tagname, *attributes))
606 def __len__(self):
607 return len(self.children)
609 def __contains__(self, key):
610 # Test for both, children and attributes with operator ``in``.
611 if isinstance(key, str):
612 return key in self.attributes
613 return key in self.children
615 def __getitem__(self, key):
616 if isinstance(key, str):
617 return self.attributes[key]
618 elif isinstance(key, int):
619 return self.children[key]
620 elif isinstance(key, slice):
621 assert key.step in (None, 1), 'cannot handle slice with stride'
622 return self.children[key.start:key.stop]
623 else:
624 raise TypeError('element index must be an integer, a slice, or '
625 'an attribute name string')
627 def __setitem__(self, key, item):
628 if isinstance(key, str):
629 self.attributes[str(key)] = item
630 elif isinstance(key, int):
631 self.setup_child(item)
632 self.children[key] = item
633 elif isinstance(key, slice):
634 assert key.step in (None, 1), 'cannot handle slice with stride'
635 for node in item:
636 self.setup_child(node)
637 self.children[key.start:key.stop] = item
638 else:
639 raise TypeError('element index must be an integer, a slice, or '
640 'an attribute name string')
642 def __delitem__(self, key):
643 if isinstance(key, str):
644 del self.attributes[key]
645 elif isinstance(key, int):
646 del self.children[key]
647 elif isinstance(key, slice):
648 assert key.step in (None, 1), 'cannot handle slice with stride'
649 del self.children[key.start:key.stop]
650 else:
651 raise TypeError('element index must be an integer, a simple '
652 'slice, or an attribute name string')
654 def __add__(self, other):
655 return self.children + other
657 def __radd__(self, other):
658 return other + self.children
660 def __iadd__(self, other):
661 """Append a node or a list of nodes to `self.children`."""
662 if isinstance(other, Node):
663 self.append(other)
664 elif other is not None:
665 self.extend(other)
666 return self
668 def astext(self):
669 return self.child_text_separator.join(
670 [child.astext() for child in self.children])
672 def non_default_attributes(self):
673 atts = {}
674 for key, value in self.attributes.items():
675 if self.is_not_default(key):
676 atts[key] = value
677 return atts
679 def attlist(self):
680 return sorted(self.non_default_attributes().items())
682 def get(self, key, failobj=None):
683 return self.attributes.get(key, failobj)
685 def hasattr(self, attr):
686 return attr in self.attributes
688 def delattr(self, attr):
689 if attr in self.attributes:
690 del self.attributes[attr]
692 def setdefault(self, key, failobj=None):
693 return self.attributes.setdefault(key, failobj)
695 has_key = hasattr
697 def get_language_code(self, fallback=''):
698 """Return node's language tag.
700 Look iteratively in self and parents for a class argument
701 starting with ``language-`` and return the remainder of it
702 (which should be a `BCP49` language tag) or the `fallback`.
703 """
704 for cls in self.get('classes', []):
705 if cls.startswith('language-'):
706 return cls[9:]
707 try:
708 return self.parent.get_language(fallback)
709 except AttributeError:
710 return fallback
712 def append(self, item):
713 self.setup_child(item)
714 self.children.append(item)
716 def extend(self, item):
717 for node in item:
718 self.append(node)
720 def insert(self, index, item):
721 if isinstance(item, Node):
722 self.setup_child(item)
723 self.children.insert(index, item)
724 elif item is not None:
725 self[index:index] = item
727 def pop(self, i=-1):
728 return self.children.pop(i)
730 def remove(self, item):
731 self.children.remove(item)
733 def index(self, item, start=0, stop=sys.maxsize):
734 return self.children.index(item, start, stop)
736 def previous_sibling(self):
737 """Return preceding sibling node or ``None``."""
738 try:
739 i = self.parent.index(self)
740 except (AttributeError):
741 return None
742 return self.parent[i-1] if i > 0 else None
744 def is_not_default(self, key):
745 if self[key] == [] and key in self.list_attributes:
746 return 0
747 else:
748 return 1
750 def update_basic_atts(self, dict_):
751 """
752 Update basic attributes ('ids', 'names', 'classes',
753 'dupnames', but not 'source') from node or dictionary `dict_`.
755 Provisional.
756 """
757 if isinstance(dict_, Node):
758 dict_ = dict_.attributes
759 for att in self.basic_attributes:
760 self.append_attr_list(att, dict_.get(att, []))
762 def append_attr_list(self, attr, values):
763 """
764 For each element in values, if it does not exist in self[attr], append
765 it.
767 NOTE: Requires self[attr] and values to be sequence type and the
768 former should specifically be a list.
769 """
770 # List Concatenation
771 for value in values:
772 if value not in self[attr]:
773 self[attr].append(value)
775 def coerce_append_attr_list(self, attr, value):
776 """
777 First, convert both self[attr] and value to a non-string sequence
778 type; if either is not already a sequence, convert it to a list of one
779 element. Then call append_attr_list.
781 NOTE: self[attr] and value both must not be None.
782 """
783 # List Concatenation
784 if not isinstance(self.get(attr), list):
785 self[attr] = [self[attr]]
786 if not isinstance(value, list):
787 value = [value]
788 self.append_attr_list(attr, value)
790 def replace_attr(self, attr, value, force=True):
791 """
792 If self[attr] does not exist or force is True or omitted, set
793 self[attr] to value, otherwise do nothing.
794 """
795 # One or the other
796 if force or self.get(attr) is None:
797 self[attr] = value
799 def copy_attr_convert(self, attr, value, replace=True):
800 """
801 If attr is an attribute of self, set self[attr] to
802 [self[attr], value], otherwise set self[attr] to value.
804 NOTE: replace is not used by this function and is kept only for
805 compatibility with the other copy functions.
806 """
807 if self.get(attr) is not value:
808 self.coerce_append_attr_list(attr, value)
810 def copy_attr_coerce(self, attr, value, replace):
811 """
812 If attr is an attribute of self and either self[attr] or value is a
813 list, convert all non-sequence values to a sequence of 1 element and
814 then concatenate the two sequence, setting the result to self[attr].
815 If both self[attr] and value are non-sequences and replace is True or
816 self[attr] is None, replace self[attr] with value. Otherwise, do
817 nothing.
818 """
819 if self.get(attr) is not value:
820 if isinstance(self.get(attr), list) or \
821 isinstance(value, list):
822 self.coerce_append_attr_list(attr, value)
823 else:
824 self.replace_attr(attr, value, replace)
826 def copy_attr_concatenate(self, attr, value, replace):
827 """
828 If attr is an attribute of self and both self[attr] and value are
829 lists, concatenate the two sequences, setting the result to
830 self[attr]. If either self[attr] or value are non-sequences and
831 replace is True or self[attr] is None, replace self[attr] with value.
832 Otherwise, do nothing.
833 """
834 if self.get(attr) is not value:
835 if isinstance(self.get(attr), list) and \
836 isinstance(value, list):
837 self.append_attr_list(attr, value)
838 else:
839 self.replace_attr(attr, value, replace)
841 def copy_attr_consistent(self, attr, value, replace):
842 """
843 If replace is True or self[attr] is None, replace self[attr] with
844 value. Otherwise, do nothing.
845 """
846 if self.get(attr) is not value:
847 self.replace_attr(attr, value, replace)
849 def update_all_atts(self, dict_, update_fun=copy_attr_consistent,
850 replace=True, and_source=False):
851 """
852 Updates all attributes from node or dictionary `dict_`.
854 Appends the basic attributes ('ids', 'names', 'classes',
855 'dupnames', but not 'source') and then, for all other attributes in
856 dict_, updates the same attribute in self. When attributes with the
857 same identifier appear in both self and dict_, the two values are
858 merged based on the value of update_fun. Generally, when replace is
859 True, the values in self are replaced or merged with the values in
860 dict_; otherwise, the values in self may be preserved or merged. When
861 and_source is True, the 'source' attribute is included in the copy.
863 NOTE: When replace is False, and self contains a 'source' attribute,
864 'source' is not replaced even when dict_ has a 'source'
865 attribute, though it may still be merged into a list depending
866 on the value of update_fun.
867 NOTE: It is easier to call the update-specific methods then to pass
868 the update_fun method to this function.
869 """
870 if isinstance(dict_, Node):
871 dict_ = dict_.attributes
873 # Include the source attribute when copying?
874 if and_source:
875 filter_fun = self.is_not_list_attribute
876 else:
877 filter_fun = self.is_not_known_attribute
879 # Copy the basic attributes
880 self.update_basic_atts(dict_)
882 # Grab other attributes in dict_ not in self except the
883 # (All basic attributes should be copied already)
884 for att in filter(filter_fun, dict_):
885 update_fun(self, att, dict_[att], replace)
887 def update_all_atts_consistantly(self, dict_, replace=True,
888 and_source=False):
889 """
890 Updates all attributes from node or dictionary `dict_`.
892 Appends the basic attributes ('ids', 'names', 'classes',
893 'dupnames', but not 'source') and then, for all other attributes in
894 dict_, updates the same attribute in self. When attributes with the
895 same identifier appear in both self and dict_ and replace is True, the
896 values in self are replaced with the values in dict_; otherwise, the
897 values in self are preserved. When and_source is True, the 'source'
898 attribute is included in the copy.
900 NOTE: When replace is False, and self contains a 'source' attribute,
901 'source' is not replaced even when dict_ has a 'source'
902 attribute, though it may still be merged into a list depending
903 on the value of update_fun.
904 """
905 self.update_all_atts(dict_, Element.copy_attr_consistent, replace,
906 and_source)
908 def update_all_atts_concatenating(self, dict_, replace=True,
909 and_source=False):
910 """
911 Updates all attributes from node or dictionary `dict_`.
913 Appends the basic attributes ('ids', 'names', 'classes',
914 'dupnames', but not 'source') and then, for all other attributes in
915 dict_, updates the same attribute in self. When attributes with the
916 same identifier appear in both self and dict_ whose values aren't each
917 lists and replace is True, the values in self are replaced with the
918 values in dict_; if the values from self and dict_ for the given
919 identifier are both of list type, then the two lists are concatenated
920 and the result stored in self; otherwise, the values in self are
921 preserved. When and_source is True, the 'source' attribute is
922 included in the copy.
924 NOTE: When replace is False, and self contains a 'source' attribute,
925 'source' is not replaced even when dict_ has a 'source'
926 attribute, though it may still be merged into a list depending
927 on the value of update_fun.
928 """
929 self.update_all_atts(dict_, Element.copy_attr_concatenate, replace,
930 and_source)
932 def update_all_atts_coercion(self, dict_, replace=True,
933 and_source=False):
934 """
935 Updates all attributes from node or dictionary `dict_`.
937 Appends the basic attributes ('ids', 'names', 'classes',
938 'dupnames', but not 'source') and then, for all other attributes in
939 dict_, updates the same attribute in self. When attributes with the
940 same identifier appear in both self and dict_ whose values are both
941 not lists and replace is True, the values in self are replaced with
942 the values in dict_; if either of the values from self and dict_ for
943 the given identifier are of list type, then first any non-lists are
944 converted to 1-element lists and then the two lists are concatenated
945 and the result stored in self; otherwise, the values in self are
946 preserved. When and_source is True, the 'source' attribute is
947 included in the copy.
949 NOTE: When replace is False, and self contains a 'source' attribute,
950 'source' is not replaced even when dict_ has a 'source'
951 attribute, though it may still be merged into a list depending
952 on the value of update_fun.
953 """
954 self.update_all_atts(dict_, Element.copy_attr_coerce, replace,
955 and_source)
957 def update_all_atts_convert(self, dict_, and_source=False):
958 """
959 Updates all attributes from node or dictionary `dict_`.
961 Appends the basic attributes ('ids', 'names', 'classes',
962 'dupnames', but not 'source') and then, for all other attributes in
963 dict_, updates the same attribute in self. When attributes with the
964 same identifier appear in both self and dict_ then first any non-lists
965 are converted to 1-element lists and then the two lists are
966 concatenated and the result stored in self; otherwise, the values in
967 self are preserved. When and_source is True, the 'source' attribute
968 is included in the copy.
970 NOTE: When replace is False, and self contains a 'source' attribute,
971 'source' is not replaced even when dict_ has a 'source'
972 attribute, though it may still be merged into a list depending
973 on the value of update_fun.
974 """
975 self.update_all_atts(dict_, Element.copy_attr_convert,
976 and_source=and_source)
978 def clear(self):
979 self.children = []
981 def replace(self, old, new):
982 """Replace one child `Node` with another child or children."""
983 index = self.index(old)
984 if isinstance(new, Node):
985 self.setup_child(new)
986 self[index] = new
987 elif new is not None:
988 self[index:index+1] = new
990 def replace_self(self, new):
991 """
992 Replace `self` node with `new`, where `new` is a node or a
993 list of nodes.
995 Provisional: the handling of node attributes will be revised.
996 """
997 update = new
998 if not isinstance(new, Node):
999 # `new` is a list; update first child.
1000 try:
1001 update = new[0]
1002 except IndexError:
1003 update = None
1004 if isinstance(update, Element):
1005 update.update_basic_atts(self)
1006 else:
1007 # `update` is a Text node or `new` is an empty list.
1008 # Assert that we aren't losing any attributes.
1009 for att in self.basic_attributes:
1010 assert not self[att], \
1011 'Losing "%s" attribute: %s' % (att, self[att])
1012 self.parent.replace(self, new)
1014 def first_child_matching_class(self, childclass, start=0, end=sys.maxsize):
1015 """
1016 Return the index of the first child whose class exactly matches.
1018 Parameters:
1020 - `childclass`: A `Node` subclass to search for, or a tuple of `Node`
1021 classes. If a tuple, any of the classes may match.
1022 - `start`: Initial index to check.
1023 - `end`: Initial index to *not* check.
1024 """
1025 if not isinstance(childclass, tuple):
1026 childclass = (childclass,)
1027 for index in range(start, min(len(self), end)):
1028 for c in childclass:
1029 if isinstance(self[index], c):
1030 return index
1031 return None
1033 def first_child_not_matching_class(self, childclass, start=0,
1034 end=sys.maxsize):
1035 """
1036 Return the index of the first child whose class does *not* match.
1038 Parameters:
1040 - `childclass`: A `Node` subclass to skip, or a tuple of `Node`
1041 classes. If a tuple, none of the classes may match.
1042 - `start`: Initial index to check.
1043 - `end`: Initial index to *not* check.
1044 """
1045 if not isinstance(childclass, tuple):
1046 childclass = (childclass,)
1047 for index in range(start, min(len(self), end)):
1048 for c in childclass:
1049 if isinstance(self.children[index], c):
1050 break
1051 else:
1052 return index
1053 return None
1055 def pformat(self, indent=' ', level=0):
1056 tagline = '%s%s\n' % (indent*level, self.starttag())
1057 childreps = (c.pformat(indent, level+1) for c in self.children)
1058 return ''.join((tagline, *childreps))
1060 def copy(self):
1061 obj = self.__class__(rawsource=self.rawsource, **self.attributes)
1062 obj._document = self._document
1063 obj.source = self.source
1064 obj.line = self.line
1065 return obj
1067 def deepcopy(self):
1068 copy = self.copy()
1069 copy.extend([child.deepcopy() for child in self.children])
1070 return copy
1072 def set_class(self, name):
1073 """Add a new class to the "classes" attribute."""
1074 warnings.warn('docutils.nodes.Element.set_class() is deprecated; '
1075 ' and will be removed in Docutils 0.21 or later.'
1076 "Append to Element['classes'] list attribute directly",
1077 DeprecationWarning, stacklevel=2)
1078 assert ' ' not in name
1079 self['classes'].append(name.lower())
1081 def note_referenced_by(self, name=None, id=None):
1082 """Note that this Element has been referenced by its name
1083 `name` or id `id`."""
1084 self.referenced = True
1085 # Element.expect_referenced_by_* dictionaries map names or ids
1086 # to nodes whose ``referenced`` attribute is set to true as
1087 # soon as this node is referenced by the given name or id.
1088 # Needed for target propagation.
1089 by_name = getattr(self, 'expect_referenced_by_name', {}).get(name)
1090 by_id = getattr(self, 'expect_referenced_by_id', {}).get(id)
1091 if by_name:
1092 assert name is not None
1093 by_name.referenced = True
1094 if by_id:
1095 assert id is not None
1096 by_id.referenced = True
1098 @classmethod
1099 def is_not_list_attribute(cls, attr):
1100 """
1101 Returns True if and only if the given attribute is NOT one of the
1102 basic list attributes defined for all Elements.
1103 """
1104 return attr not in cls.list_attributes
1106 @classmethod
1107 def is_not_known_attribute(cls, attr):
1108 """
1109 Return True if `attr` is NOT defined for all Element instances.
1111 Provisional. May be removed in Docutils 2.0.
1112 """
1113 return attr not in cls.common_attributes
1115 def validate_attributes(self):
1116 """Normalize and validate element attributes.
1118 Convert string values to expected datatype.
1119 Normalize values.
1121 Raise `ValueError` for invalid attributes or attribute values.
1123 Provisional.
1124 """
1125 messages = []
1126 for key, value in self.attributes.items():
1127 if key.startswith('internal:'):
1128 continue # see docs/user/config.html#expose-internals
1129 if key not in self.valid_attributes:
1130 va = ' '.join(self.valid_attributes)
1131 messages.append(f'Attribute "{key}" not one of "{va}".')
1132 continue
1133 try:
1134 self.attributes[key] = ATTRIBUTE_VALIDATORS[key](value)
1135 except (ValueError, TypeError, KeyError) as e:
1136 messages.append(
1137 f'Attribute "{key}" has invalid value "{value}".\n'
1138 + e.args[0]) # message argument
1139 if messages:
1140 raise ValueError('\n'.join(messages))
1142 def validate(self):
1143 messages = []
1144 try:
1145 self.validate_attributes()
1146 except ValueError as e:
1147 messages.append(e.args[0]) # the message argument
1148 # TODO: check number of children
1149 n_min, n_max = self.valid_len
1150 if len(self.children) < n_min:
1151 messages.append(f'Expects at least {n_min} children, '
1152 f'not {len(self.children)}.')
1153 if n_max is not None and len(self.children) > n_max:
1154 messages.append(f'Expects at most {n_max} children, '
1155 f'not {len(self.children)}.')
1156 for child in self.children:
1157 if not isinstance(child, self.valid_children):
1158 messages.append(f'May not contain "{child.tagname}" elements.')
1159 child.validate()
1160 if messages:
1161 msg = f'Element <{self.tagname}> invalid:\n' + '\n'.join(messages)
1162 try:
1163 self.document.reporter.warning(msg)
1164 except AttributeError:
1165 raise ValueError(msg)
1168# ========
1169# Mixins
1170# ========
1172class Resolvable:
1173 resolved = False
1176class BackLinkable:
1177 """Mixin for Elements that accept a "backrefs" attribute."""
1179 list_attributes = Element.list_attributes + ('backrefs',)
1180 valid_attributes = Element.valid_attributes + ('backrefs',)
1182 def add_backref(self, refid):
1183 self['backrefs'].append(refid)
1186# ====================
1187# Element Categories
1188# ====================
1190class Root:
1191 """Element at the root of a document tree."""
1194class Structural:
1195 """`Structural elements`__.
1197 __ https://docutils.sourceforge.io/docs/ref/doctree.html
1198 #structural-elements
1199 """
1202class SubRoot:
1203 """Elements that may only be children of the root element."""
1206class SubStructural(SubRoot):
1207 """`Structural subelements`__ are children of structural elements.
1209 Most Structural elements accept only some of the SubStructural elements.
1211 __ https://docutils.sourceforge.io/docs/ref/doctree.html
1212 #structural-subelements
1213 """
1216class Bibliographic:
1217 """`Bibliographic Elements`__ (displayed document meta-data).
1219 __ https://docutils.sourceforge.io/docs/ref/doctree.html
1220 #bibliographic-elements
1221 """
1224class Body:
1225 """`Body elements`__.
1227 __ https://docutils.sourceforge.io/docs/ref/doctree.html#body-elements
1228 """
1231class Admonition(Body):
1232 """Admonitions (distinctive and self-contained notices)."""
1233 valid_children = Body # (%body.elements;)
1236class Sequential(Body):
1237 """List-like body elements."""
1240class General(Body):
1241 """Miscellaneous body elements."""
1244class Special(Body):
1245 """Special internal body elements."""
1248class Part:
1249 """`Body Subelements`__ always occur within specific parent elements.
1251 __ https://docutils.sourceforge.io/docs/ref/doctree.html#body-subelements
1252 """
1255class Decorative:
1256 """Decorative elements (`header` and `footer`).
1258 Children of `decoration`.
1259 """
1260 valid_children = Body # (%body.elements;)
1263class Inline:
1264 """Inline elements contain text data and possibly other inline elements.
1265 """
1268# Orthogonal categories
1270class PreBibliographic:
1271 """Elements which may occur before Bibliographic Elements."""
1274class Invisible(Special, PreBibliographic):
1275 """Internal elements that don't appear in output."""
1278class Labeled:
1279 """Contains a `label` as its first element."""
1282class Referential(Resolvable):
1283 """Elements holding a cross-reference (outgoing hyperlink)."""
1286class Targetable(Resolvable):
1287 """Cross-reference targets (incoming hyperlink)."""
1288 referenced = 0
1290 indirect_reference_name = None
1291 """Holds the whitespace_normalized_name (contains mixed case) of a target.
1292 Required for MoinMoin/reST compatibility.
1294 Provisional.
1295 """
1298class Titular:
1299 """Title, sub-title, or informal heading (rubric)."""
1302class TextElement(Element):
1303 """
1304 An element which directly contains text.
1306 Its children are all `Text` or `Inline` subclass nodes. You can
1307 check whether an element's context is inline simply by checking whether
1308 its immediate parent is a `TextElement` instance (including subclasses).
1309 This is handy for nodes like `image` that can appear both inline and as
1310 standalone body elements.
1312 If passing children to `__init__()`, make sure to set `text` to
1313 ``''`` or some other suitable value.
1314 """
1315 valid_children = (Text, Inline) # (#PCDATA | %inline.elements;)*
1316 valid_len = (0, None)
1317 child_text_separator = ''
1318 """Separator for child nodes, used by `astext()` method."""
1320 def __init__(self, rawsource='', text='', *children, **attributes):
1321 if text != '':
1322 textnode = Text(text)
1323 Element.__init__(self, rawsource, textnode, *children,
1324 **attributes)
1325 else:
1326 Element.__init__(self, rawsource, *children, **attributes)
1329class FixedTextElement(TextElement):
1330 """An element which directly contains preformatted text."""
1332 valid_attributes = Element.valid_attributes + ('xml:space',)
1334 def __init__(self, rawsource='', text='', *children, **attributes):
1335 super().__init__(rawsource, text, *children, **attributes)
1336 self.attributes['xml:space'] = 'preserve'
1339class PureTextElement(TextElement):
1340 """An element which only contains text, no children."""
1341 valid_children = Text # (#PCDATA)
1344# ==============
1345# Root Element
1346# ==============
1348class document(Root, Element):
1349 """
1350 The document root element.
1352 Do not instantiate this class directly; use
1353 `docutils.utils.new_document()` instead.
1354 """
1355 valid_attributes = Element.valid_attributes + ('title',)
1356 # content model: ( (title, subtitle?)?,
1357 # meta*,
1358 # decoration?,
1359 # (docinfo, transition?)?,
1360 # %structure.model; )
1361 valid_children = (Structural, SubRoot, Body)
1362 valid_len = (0, None) # may be empty
1364 def __init__(self, settings, reporter, *args, **kwargs):
1365 Element.__init__(self, *args, **kwargs)
1367 self.current_source = None
1368 """Path to or description of the input source being processed."""
1370 self.current_line = None
1371 """Line number (1-based) of `current_source`."""
1373 self.settings = settings
1374 """Runtime settings data record."""
1376 self.reporter = reporter
1377 """System message generator."""
1379 self.indirect_targets = []
1380 """List of indirect target nodes."""
1382 self.substitution_defs = {}
1383 """Mapping of substitution names to substitution_definition nodes."""
1385 self.substitution_names = {}
1386 """Mapping of case-normalized substitution names to case-sensitive
1387 names."""
1389 self.refnames = {}
1390 """Mapping of names to lists of referencing nodes."""
1392 self.refids = {}
1393 """Mapping of ids to lists of referencing nodes."""
1395 self.nameids = {}
1396 """Mapping of names to unique id's."""
1398 self.nametypes = {}
1399 """Mapping of names to hyperlink type (boolean: True => explicit,
1400 False => implicit."""
1402 self.ids = {}
1403 """Mapping of ids to nodes."""
1405 self.footnote_refs = {}
1406 """Mapping of footnote labels to lists of footnote_reference nodes."""
1408 self.citation_refs = {}
1409 """Mapping of citation labels to lists of citation_reference nodes."""
1411 self.autofootnotes = []
1412 """List of auto-numbered footnote nodes."""
1414 self.autofootnote_refs = []
1415 """List of auto-numbered footnote_reference nodes."""
1417 self.symbol_footnotes = []
1418 """List of symbol footnote nodes."""
1420 self.symbol_footnote_refs = []
1421 """List of symbol footnote_reference nodes."""
1423 self.footnotes = []
1424 """List of manually-numbered footnote nodes."""
1426 self.citations = []
1427 """List of citation nodes."""
1429 self.autofootnote_start = 1
1430 """Initial auto-numbered footnote number."""
1432 self.symbol_footnote_start = 0
1433 """Initial symbol footnote symbol index."""
1435 self.id_counter = Counter()
1436 """Numbers added to otherwise identical IDs."""
1438 self.parse_messages = []
1439 """System messages generated while parsing."""
1441 self.transform_messages = []
1442 """System messages generated while applying transforms."""
1444 import docutils.transforms
1445 self.transformer = docutils.transforms.Transformer(self)
1446 """Storage for transforms to be applied to this document."""
1448 self.include_log = []
1449 """The current source's parents (to detect inclusion loops)."""
1451 self.decoration = None
1452 """Document's `decoration` node."""
1454 self._document = self
1456 def __getstate__(self):
1457 """
1458 Return dict with unpicklable references removed.
1459 """
1460 state = self.__dict__.copy()
1461 state['reporter'] = None
1462 state['transformer'] = None
1463 return state
1465 def asdom(self, dom=None):
1466 """Return a DOM representation of this document."""
1467 if dom is None:
1468 import xml.dom.minidom as dom
1469 domroot = dom.Document()
1470 domroot.appendChild(self._dom_node(domroot))
1471 return domroot
1473 def set_id(self, node, msgnode=None, suggested_prefix=''):
1474 if node['ids']:
1475 # register and check for duplicates
1476 for id in node['ids']:
1477 self.ids.setdefault(id, node)
1478 if self.ids[id] is not node:
1479 msg = self.reporter.severe('Duplicate ID: "%s".' % id)
1480 if msgnode is not None:
1481 msgnode += msg
1482 return id
1483 # generate and set id
1484 id_prefix = self.settings.id_prefix
1485 auto_id_prefix = self.settings.auto_id_prefix
1486 base_id = ''
1487 id = ''
1488 for name in node['names']:
1489 if id_prefix:
1490 # allow names starting with numbers if `id_prefix`
1491 base_id = make_id('x'+name)[1:]
1492 else:
1493 base_id = make_id(name)
1494 # TODO: normalize id-prefix? (would make code simpler)
1495 id = id_prefix + base_id
1496 if base_id and id not in self.ids:
1497 break
1498 else:
1499 if base_id and auto_id_prefix.endswith('%'):
1500 # disambiguate name-derived ID
1501 # TODO: remove second condition after announcing change
1502 prefix = id + '-'
1503 else:
1504 prefix = id_prefix + auto_id_prefix
1505 if prefix.endswith('%'):
1506 prefix = '%s%s-' % (prefix[:-1],
1507 suggested_prefix
1508 or make_id(node.tagname))
1509 while True:
1510 self.id_counter[prefix] += 1
1511 id = '%s%d' % (prefix, self.id_counter[prefix])
1512 if id not in self.ids:
1513 break
1514 node['ids'].append(id)
1515 self.ids[id] = node
1516 return id
1518 def set_name_id_map(self, node, id, msgnode=None, explicit=None):
1519 """
1520 `self.nameids` maps names to IDs, while `self.nametypes` maps names to
1521 booleans representing hyperlink type (True==explicit,
1522 False==implicit). This method updates the mappings.
1524 The following state transition table shows how `self.nameids` items
1525 ("id") and `self.nametypes` items ("type") change with new input
1526 (a call to this method), and what actions are performed
1527 ("implicit"-type system messages are INFO/1, and
1528 "explicit"-type system messages are ERROR/3):
1530 ==== ===== ======== ======== ======= ==== ===== =====
1531 Old State Input Action New State Notes
1532 ----------- -------- ----------------- ----------- -----
1533 id type new type sys.msg. dupname id type
1534 ==== ===== ======== ======== ======= ==== ===== =====
1535 - - explicit - - new True
1536 - - implicit - - new False
1537 - False explicit - - new True
1538 old False explicit implicit old new True
1539 - True explicit explicit new - True
1540 old True explicit explicit new,old - True [#]_
1541 - False implicit implicit new - False
1542 old False implicit implicit new,old - False
1543 - True implicit implicit new - True
1544 old True implicit implicit new old True
1545 ==== ===== ======== ======== ======= ==== ===== =====
1547 .. [#] Do not clear the name-to-id map or invalidate the old target if
1548 both old and new targets are external and refer to identical URIs.
1549 The new target is invalidated regardless.
1550 """
1551 for name in tuple(node['names']):
1552 if name in self.nameids:
1553 self.set_duplicate_name_id(node, id, name, msgnode, explicit)
1554 # attention: modifies node['names']
1555 else:
1556 self.nameids[name] = id
1557 self.nametypes[name] = explicit
1559 def set_duplicate_name_id(self, node, id, name, msgnode, explicit):
1560 old_id = self.nameids[name]
1561 old_explicit = self.nametypes[name]
1562 self.nametypes[name] = old_explicit or explicit
1563 if explicit:
1564 if old_explicit:
1565 level = 2
1566 if old_id is not None:
1567 old_node = self.ids[old_id]
1568 if 'refuri' in node:
1569 refuri = node['refuri']
1570 if (old_node['names']
1571 and 'refuri' in old_node
1572 and old_node['refuri'] == refuri):
1573 level = 1 # just inform if refuri's identical
1574 if level > 1:
1575 dupname(old_node, name)
1576 self.nameids[name] = None
1577 msg = self.reporter.system_message(
1578 level, 'Duplicate explicit target name: "%s".' % name,
1579 backrefs=[id], base_node=node)
1580 if msgnode is not None:
1581 msgnode += msg
1582 dupname(node, name)
1583 else:
1584 self.nameids[name] = id
1585 if old_id is not None:
1586 old_node = self.ids[old_id]
1587 dupname(old_node, name)
1588 else:
1589 if old_id is not None and not old_explicit:
1590 self.nameids[name] = None
1591 old_node = self.ids[old_id]
1592 dupname(old_node, name)
1593 dupname(node, name)
1594 if not explicit or (not old_explicit and old_id is not None):
1595 msg = self.reporter.info(
1596 'Duplicate implicit target name: "%s".' % name,
1597 backrefs=[id], base_node=node)
1598 if msgnode is not None:
1599 msgnode += msg
1601 def has_name(self, name):
1602 return name in self.nameids
1604 # "note" here is an imperative verb: "take note of".
1605 def note_implicit_target(self, target, msgnode=None):
1606 id = self.set_id(target, msgnode)
1607 self.set_name_id_map(target, id, msgnode, explicit=False)
1609 def note_explicit_target(self, target, msgnode=None):
1610 id = self.set_id(target, msgnode)
1611 self.set_name_id_map(target, id, msgnode, explicit=True)
1613 def note_refname(self, node):
1614 self.refnames.setdefault(node['refname'], []).append(node)
1616 def note_refid(self, node):
1617 self.refids.setdefault(node['refid'], []).append(node)
1619 def note_indirect_target(self, target):
1620 self.indirect_targets.append(target)
1621 if target['names']:
1622 self.note_refname(target)
1624 def note_anonymous_target(self, target):
1625 self.set_id(target)
1627 def note_autofootnote(self, footnote):
1628 self.set_id(footnote)
1629 self.autofootnotes.append(footnote)
1631 def note_autofootnote_ref(self, ref):
1632 self.set_id(ref)
1633 self.autofootnote_refs.append(ref)
1635 def note_symbol_footnote(self, footnote):
1636 self.set_id(footnote)
1637 self.symbol_footnotes.append(footnote)
1639 def note_symbol_footnote_ref(self, ref):
1640 self.set_id(ref)
1641 self.symbol_footnote_refs.append(ref)
1643 def note_footnote(self, footnote):
1644 self.set_id(footnote)
1645 self.footnotes.append(footnote)
1647 def note_footnote_ref(self, ref):
1648 self.set_id(ref)
1649 self.footnote_refs.setdefault(ref['refname'], []).append(ref)
1650 self.note_refname(ref)
1652 def note_citation(self, citation):
1653 self.citations.append(citation)
1655 def note_citation_ref(self, ref):
1656 self.set_id(ref)
1657 self.citation_refs.setdefault(ref['refname'], []).append(ref)
1658 self.note_refname(ref)
1660 def note_substitution_def(self, subdef, def_name, msgnode=None):
1661 name = whitespace_normalize_name(def_name)
1662 if name in self.substitution_defs:
1663 msg = self.reporter.error(
1664 'Duplicate substitution definition name: "%s".' % name,
1665 base_node=subdef)
1666 if msgnode is not None:
1667 msgnode += msg
1668 oldnode = self.substitution_defs[name]
1669 dupname(oldnode, name)
1670 # keep only the last definition:
1671 self.substitution_defs[name] = subdef
1672 # case-insensitive mapping:
1673 self.substitution_names[fully_normalize_name(name)] = name
1675 def note_substitution_ref(self, subref, refname):
1676 subref['refname'] = whitespace_normalize_name(refname)
1678 def note_pending(self, pending, priority=None):
1679 self.transformer.add_pending(pending, priority)
1681 def note_parse_message(self, message):
1682 self.parse_messages.append(message)
1684 def note_transform_message(self, message):
1685 self.transform_messages.append(message)
1687 def note_source(self, source, offset):
1688 self.current_source = source
1689 if offset is None:
1690 self.current_line = offset
1691 else:
1692 self.current_line = offset + 1
1694 def copy(self):
1695 obj = self.__class__(self.settings, self.reporter,
1696 **self.attributes)
1697 obj.source = self.source
1698 obj.line = self.line
1699 return obj
1701 def get_decoration(self):
1702 if not self.decoration:
1703 self.decoration = decoration()
1704 index = self.first_child_not_matching_class((Titular, meta))
1705 if index is None:
1706 self.append(self.decoration)
1707 else:
1708 self.insert(index, self.decoration)
1709 return self.decoration
1712# ================
1713# Title Elements
1714# ================
1716class title(Titular, PreBibliographic, SubStructural, TextElement):
1717 valid_attributes = Element.valid_attributes + ('auto', 'refid')
1720class subtitle(Titular, PreBibliographic, SubStructural, TextElement): pass
1721class rubric(Titular, General, TextElement): pass
1724# ==================
1725# Meta-Data Element
1726# ==================
1728class meta(PreBibliographic, SubRoot, Element):
1729 """Container for "invisible" bibliographic data, or meta-data."""
1730 valid_attributes = Element.valid_attributes + (
1731 'content', 'dir', 'http-equiv', 'lang', 'media', 'name', 'scheme')
1732 valid_len = (0, 0) # The <meta> element has no content.
1735# ========================
1736# Bibliographic Elements
1737# ========================
1739class docinfo(SubRoot, Element):
1740 """Container for displayed document meta-data."""
1741 valid_children = Bibliographic # (%bibliographic.elements;)+
1744class author(Bibliographic, TextElement): pass
1745class organization(Bibliographic, TextElement): pass
1746class address(Bibliographic, FixedTextElement): pass
1747class contact(Bibliographic, TextElement): pass
1748class version(Bibliographic, TextElement): pass
1749class revision(Bibliographic, TextElement): pass
1750class status(Bibliographic, TextElement): pass
1751class date(Bibliographic, TextElement): pass
1752class copyright(Bibliographic, TextElement): pass
1755class authors(Bibliographic, Element):
1756 """Container for author information for documents with multiple authors.
1757 """
1758 # content model: (author, organization?, address?, contact?)+
1759 valid_children = (author, organization, address, contact)
1762# =====================
1763# Decorative Elements
1764# =====================
1767class decoration(PreBibliographic, SubRoot, Element):
1768 """Container for `header` and `footer`."""
1769 valid_children = Decorative # (header?, footer?)
1770 valid_len = (0, 2) # TODO: empty element does not make sense.
1772 def get_header(self):
1773 if not len(self.children) or not isinstance(self.children[0], header):
1774 self.insert(0, header())
1775 return self.children[0]
1777 def get_footer(self):
1778 if not len(self.children) or not isinstance(self.children[-1], footer):
1779 self.append(footer())
1780 return self.children[-1]
1783class header(Decorative, Element): pass
1784class footer(Decorative, Element): pass
1787# =====================
1788# Structural Elements
1789# =====================
1791class section(Structural, Element):
1792 """Document section. The main unit of hierarchy."""
1793 # content model: (title, subtitle?, %structure.model;)
1794 valid_children = (Structural, SubStructural, Body)
1797class topic(Structural, Element):
1798 """
1799 Topics are terminal, "leaf" mini-sections, like block quotes with titles,
1800 or textual figures. A topic is just like a section, except that
1801 it has no subsections, it does not get listed in the ToC,
1802 and it doesn't have to conform to section placement rules.
1804 Topics are allowed wherever body elements (list, table, etc.) are allowed,
1805 but only at the top level of a sideber, section or document.
1806 Topics cannot nest inside topics, or body elements; you can't have
1807 a topic inside a table, list, block quote, etc.
1808 """
1809 # "depth" and "local" attributes may be added by the "Contents" transform:
1810 valid_attributes = Element.valid_attributes + ('depth', 'local')
1811 valid_children = (title, Body) # (title?, (%body.elements;)+)
1814class sidebar(Structural, Element):
1815 """
1816 Sidebars are like miniature, parallel documents that occur inside other
1817 documents, providing related or reference material. A sidebar is
1818 typically offset by a border and "floats" to the side of the page; the
1819 document's main text may flow around it. Sidebars can also be likened to
1820 super-footnotes; their content is outside of the flow of the document's
1821 main text.
1823 Sidebars are allowed wherever body elements (list, table, etc.) are
1824 allowed, but only at the top level of a section or document. Sidebars
1825 cannot nest inside sidebars, topics, or body elements; you can't have a
1826 sidebar inside a table, list, block quote, etc.
1827 """
1828 # content model: ((title, subtitle?)?, (%body.elements; | topic)+)
1829 valid_children = (title, subtitle, topic, Body)
1832class transition(SubStructural, Element):
1833 """Transitions are breaks between untitled text parts.
1835 A transition may not begin or end a section or document, nor may two
1836 transitions be immediately adjacent.
1837 """
1838 valid_len = (0, 0) # empty element
1841# ===============
1842# Body Elements
1843# ===============
1845class paragraph(General, TextElement): pass
1848class compound(General, Element):
1849 valid_children = Body # (%body.elements;)+
1852class container(General, Element):
1853 valid_children = Body # (%body.elements;)+
1856class attribution(Part, TextElement):
1857 """Visible reference to the source of a `block_quote`."""
1860class block_quote(General, Element):
1861 """An extended quotation, set off from the main text."""
1862 valid_children = (Body, attribution) # ((%body.elements;)+, attribution?)
1865# Lists
1866# =====
1867#
1868# Lists (Sequential) and related Body Subelements (Part)
1870class list_item(Part, Element):
1871 valid_children = Body # (%body.elements;)*
1872 valid_len = (0, None)
1875class bullet_list(Sequential, Element):
1876 valid_attributes = Element.valid_attributes + ('bullet',)
1877 valid_children = list_item # (list_item+)
1880class enumerated_list(Sequential, Element):
1881 valid_attributes = Element.valid_attributes + (
1882 'enumtype', 'prefix', 'suffix', 'start')
1883 valid_children = list_item # (list_item+)
1886class term(Part, TextElement): pass
1887class classifier(Part, TextElement): pass
1890class definition(Part, Element):
1891 """Definition of a `term` in a `definition_list`."""
1892 valid_children = Body # (%body.elements;)+
1895class definition_list_item(Part, Element):
1896 valid_children = (term, classifier, definition)
1897 valid_len = (2, None) # (term, classifier*, definition)
1900class definition_list(Sequential, Element):
1901 """List of terms and their definitions.
1903 Can be used for glossaries or dictionaries, to describe or
1904 classify things, for dialogues, or to itemize subtopics.
1905 """
1906 valid_children = definition_list_item # (definition_list_item+)
1909class field_name(Part, TextElement): pass
1912class field_body(Part, Element):
1913 valid_children = Body # (%body.elements;)*
1914 valid_len = (0, None)
1917class field(Part, Bibliographic, Element):
1918 valid_children = (field_name, field_body) # (field_name, field_body)
1919 valid_len = (2, 2)
1922class field_list(Sequential, Element):
1923 """List of label & data pairs.
1925 Typically rendered as a two-column list.
1926 Also used for extension syntax or special processing.
1927 """
1928 valid_children = field # (field+)
1931class option_string(Part, PureTextElement):
1932 """A literal command-line option. Typically monospaced."""
1935class option_argument(Part, PureTextElement):
1936 """Placeholder text for option arguments."""
1937 valid_attributes = Element.valid_attributes + ('delimiter',)
1939 def astext(self):
1940 return self.get('delimiter', ' ') + TextElement.astext(self)
1943class option(Part, Element):
1944 """Option element in an `option_list_item`.
1946 Groups an option string with zero or more option argument placeholders.
1947 """
1948 child_text_separator = ''
1949 # content model: (option_string, option_argument*)
1950 valid_children = (option_string, option_argument)
1953class option_group(Part, Element):
1954 """Groups together one or more `option` elements, all synonyms."""
1955 child_text_separator = ', '
1956 valid_children = option # (option+)
1959class description(Part, Element):
1960 """Describtion of a command-line option."""
1961 valid_children = Body # (%body.elements;)+
1964class option_list_item(Part, Element):
1965 """Container for a pair of `option_group` and `description` elements.
1966 """
1967 child_text_separator = ' '
1968 valid_children = (option_group, description) # (option_group, description)
1969 valid_len = (2, 2)
1972class option_list(Sequential, Element):
1973 """Two-column list of command-line options and descriptions."""
1974 valid_children = option_list_item # (option_list_item+)
1977# Pre-formatted text blocks
1978# =========================
1980class literal_block(General, FixedTextElement): pass
1981class doctest_block(General, FixedTextElement): pass
1984class math_block(General, FixedTextElement, PureTextElement):
1985 """Mathematical notation (display formula)."""
1988class line(Part, TextElement):
1989 """Single line of text in a `line_block`."""
1990 indent = None
1993class line_block(General, Element):
1994 """Sequence of lines and nested line blocks.
1995 """
1996 # recursive content model: (line | line_block)+
1999line_block.valid_children = (line, line_block)
2002# Admonitions
2003# ===========
2004# distinctive and self-contained notices
2006class attention(Admonition, Element): pass
2007class caution(Admonition, Element): pass
2008class danger(Admonition, Element): pass
2009class error(Admonition, Element): pass
2010class important(Admonition, Element): pass
2011class note(Admonition, Element): pass
2012class tip(Admonition, Element): pass
2013class hint(Admonition, Element): pass
2014class warning(Admonition, Element): pass
2017class admonition(Admonition, Element):
2018 valid_children = (title, Body) # (title, (%body.elements;)+)
2019 valid_len = (2, None)
2022# Invisible elements
2023# ==================
2025class comment(Invisible, FixedTextElement, PureTextElement):
2026 """Author notes, hidden from the output."""
2029class substitution_definition(Invisible, TextElement):
2030 valid_attributes = Element.valid_attributes + ('ltrim', 'rtrim')
2033class target(Invisible, Inline, TextElement, Targetable):
2034 valid_attributes = Element.valid_attributes + (
2035 'anonymous', 'refid', 'refname', 'refuri')
2038# Footnote and citation
2039# =====================
2041class label(Part, PureTextElement):
2042 """Visible identifier for footnotes and citations."""
2045class footnote(General, BackLinkable, Element, Labeled, Targetable):
2046 """Labelled note providing additional context (footnote or endnote)."""
2047 valid_attributes = Element.valid_attributes + ('auto', 'backrefs')
2048 valid_children = (label, Body) # (label?, (%body.elements;)+)
2051class citation(General, BackLinkable, Element, Labeled, Targetable):
2052 valid_children = (label, Body) # (label, (%body.elements;)+)
2053 valid_len = (2, None)
2056# Graphical elements
2057# ==================
2059class image(General, Inline, Element):
2060 """Reference to an image resource.
2062 May be body element or inline element.
2063 """
2064 valid_attributes = Element.valid_attributes + (
2065 'uri', 'alt', 'align', 'height', 'width', 'scale', 'loading')
2066 valid_len = (0, 0) # emtpy element
2068 def astext(self):
2069 return self.get('alt', '')
2072class caption(Part, TextElement): pass
2075class legend(Part, Element):
2076 """A wrapper for text accompanying a `figure` that is not the caption."""
2077 valid_children = Body # (%body.elements;)
2080class figure(General, Element):
2081 """A formal figure, generally an illustration, with a title."""
2082 valid_attributes = Element.valid_attributes + ('align', 'width')
2083 # content model: (image, ((caption, legend?) | legend))
2084 valid_children = (image, caption, legend)
2085 valid_len = (1, 3)
2086 # TODO: According to the DTD, a caption or legend is required
2087 # but rST allows "bare" figures which are formatted differently from
2088 # images (floating in LaTeX, nested in a <figure> in HTML).
2091# Tables
2092# ======
2094class entry(Part, Element):
2095 """An entry in a `row` (a table cell)."""
2096 valid_attributes = Element.valid_attributes + (
2097 'align', 'char', 'charoff', 'colname', 'colsep', 'morecols',
2098 'morerows', 'namest', 'nameend', 'rowsep', 'valign')
2099 valid_children = Body # %tbl.entry.mdl -> (%body.elements;)*
2100 valid_len = (0, None) # may be empty
2103class row(Part, Element):
2104 """Row of table cells."""
2105 valid_attributes = Element.valid_attributes + ('rowsep', 'valign')
2106 valid_children = entry # (%tbl.row.mdl;) -> entry+
2109class colspec(Part, Element):
2110 """Specifications for a column in a `tgroup`."""
2111 valid_attributes = Element.valid_attributes + (
2112 'align', 'char', 'charoff', 'colname', 'colnum',
2113 'colsep', 'colwidth', 'rowsep', 'stub')
2114 valid_len = (0, 0) # empty element
2117class thead(Part, Element):
2118 """Row(s) that form the head of a `tgroup`."""
2119 valid_attributes = Element.valid_attributes + ('valign',)
2120 valid_children = row # (row+)
2123class tbody(Part, Element):
2124 """Body of a `tgroup`."""
2125 valid_attributes = Element.valid_attributes + ('valign',)
2126 valid_children = row # (row+)
2129class tgroup(Part, Element):
2130 """A portion of a table. Most tables have just one `tgroup`."""
2131 valid_attributes = Element.valid_attributes + (
2132 'align', 'cols', 'colsep', 'rowsep')
2133 valid_children = (colspec, thead, tbody) # (colspec*, thead?, tbody)
2136class table(General, Element):
2137 """A data arrangement with rows and columns."""
2138 valid_attributes = Element.valid_attributes + (
2139 'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width')
2140 valid_children = (title, tgroup) # (title?, tgroup+)
2143# Special purpose elements
2144# ========================
2146class system_message(Special, BackLinkable, PreBibliographic, Element):
2147 """
2148 System message element.
2150 Do not instantiate this class directly; use
2151 ``document.reporter.info/warning/error/severe()`` instead.
2152 """
2153 valid_attributes = BackLinkable.valid_attributes + (
2154 'level', 'line', 'type')
2155 valid_children = Body # (%body.elements;)+
2157 def __init__(self, message=None, *children, **attributes):
2158 rawsource = attributes.pop('rawsource', '')
2159 if message:
2160 p = paragraph('', message)
2161 children = (p,) + children
2162 try:
2163 Element.__init__(self, rawsource, *children, **attributes)
2164 except: # noqa catchall
2165 print('system_message: children=%r' % (children,))
2166 raise
2168 def astext(self):
2169 line = self.get('line', '')
2170 return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
2171 self['level'], Element.astext(self))
2174class pending(Invisible, Element):
2175 """
2176 Placeholder for pending operations.
2178 The "pending" element is used to encapsulate a pending operation: the
2179 operation (transform), the point at which to apply it, and any data it
2180 requires. Only the pending operation's location within the document is
2181 stored in the public document tree (by the "pending" object itself); the
2182 operation and its data are stored in the "pending" object's internal
2183 instance attributes.
2185 For example, say you want a table of contents in your reStructuredText
2186 document. The easiest way to specify where to put it is from within the
2187 document, with a directive::
2189 .. contents::
2191 But the "contents" directive can't do its work until the entire document
2192 has been parsed and possibly transformed to some extent. So the directive
2193 code leaves a placeholder behind that will trigger the second phase of its
2194 processing, something like this::
2196 <pending ...public attributes...> + internal attributes
2198 Use `document.note_pending()` so that the
2199 `docutils.transforms.Transformer` stage of processing can run all pending
2200 transforms.
2201 """
2202 valid_len = (0, 0) # empty element
2204 def __init__(self, transform, details=None,
2205 rawsource='', *children, **attributes):
2206 Element.__init__(self, rawsource, *children, **attributes)
2208 self.transform = transform
2209 """The `docutils.transforms.Transform` class implementing the pending
2210 operation."""
2212 self.details = details or {}
2213 """Detail data (dictionary) required by the pending operation."""
2215 def pformat(self, indent=' ', level=0):
2216 internals = ['.. internal attributes:',
2217 ' .transform: %s.%s' % (self.transform.__module__,
2218 self.transform.__name__),
2219 ' .details:']
2220 details = sorted(self.details.items())
2221 for key, value in details:
2222 if isinstance(value, Node):
2223 internals.append('%7s%s:' % ('', key))
2224 internals.extend(['%9s%s' % ('', line)
2225 for line in value.pformat().splitlines()])
2226 elif (value
2227 and isinstance(value, list)
2228 and isinstance(value[0], Node)):
2229 internals.append('%7s%s:' % ('', key))
2230 for v in value:
2231 internals.extend(['%9s%s' % ('', line)
2232 for line in v.pformat().splitlines()])
2233 else:
2234 internals.append('%7s%s: %r' % ('', key, value))
2235 return (Element.pformat(self, indent, level)
2236 + ''.join((' %s%s\n' % (indent * level, line))
2237 for line in internals))
2239 def copy(self):
2240 obj = self.__class__(self.transform, self.details, self.rawsource,
2241 **self.attributes)
2242 obj._document = self._document
2243 obj.source = self.source
2244 obj.line = self.line
2245 return obj
2248class raw(Special, Inline, PreBibliographic,
2249 FixedTextElement, PureTextElement):
2250 """Raw data that is to be passed untouched to the Writer.
2251 """
2252 valid_attributes = Element.valid_attributes + ('format', 'xml:space')
2255# =================
2256# Inline Elements
2257# =================
2259class emphasis(Inline, TextElement): pass
2260class strong(Inline, TextElement): pass
2261class literal(Inline, TextElement): pass
2264class reference(General, Inline, Referential, TextElement):
2265 valid_attributes = Element.valid_attributes + (
2266 'anonymous', 'name', 'refid', 'refname', 'refuri')
2269class footnote_reference(Inline, Referential, PureTextElement):
2270 valid_attributes = Element.valid_attributes + ('auto', 'refid', 'refname')
2273class citation_reference(Inline, Referential, PureTextElement):
2274 valid_attributes = Element.valid_attributes + ('refid', 'refname')
2277class substitution_reference(Inline, TextElement):
2278 valid_attributes = Element.valid_attributes + ('refname',)
2281class title_reference(Inline, TextElement): pass
2282class abbreviation(Inline, TextElement): pass
2283class acronym(Inline, TextElement): pass
2284class superscript(Inline, TextElement): pass
2285class subscript(Inline, TextElement): pass
2288class math(Inline, PureTextElement):
2289 """Mathematical notation in running text."""
2292class inline(Inline, TextElement): pass
2295class problematic(Inline, TextElement):
2296 valid_attributes = Element.valid_attributes + (
2297 'refid', 'refname', 'refuri')
2300class generated(Inline, TextElement): pass
2303# ========================================
2304# Auxiliary Classes, Functions, and Data
2305# ========================================
2307node_class_names = """
2308 Text
2309 abbreviation acronym address admonition attention attribution author
2310 authors
2311 block_quote bullet_list
2312 caption caution citation citation_reference classifier colspec comment
2313 compound contact container copyright
2314 danger date decoration definition definition_list definition_list_item
2315 description docinfo doctest_block document
2316 emphasis entry enumerated_list error
2317 field field_body field_list field_name figure footer
2318 footnote footnote_reference
2319 generated
2320 header hint
2321 image important inline
2322 label legend line line_block list_item literal literal_block
2323 math math_block meta
2324 note
2325 option option_argument option_group option_list option_list_item
2326 option_string organization
2327 paragraph pending problematic
2328 raw reference revision row rubric
2329 section sidebar status strong subscript substitution_definition
2330 substitution_reference subtitle superscript system_message
2331 table target tbody term tgroup thead tip title title_reference topic
2332 transition
2333 version
2334 warning""".split()
2335"""A list of names of all concrete Node subclasses."""
2338class NodeVisitor:
2339 """
2340 "Visitor" pattern [GoF95]_ abstract superclass implementation for
2341 document tree traversals.
2343 Each node class has corresponding methods, doing nothing by
2344 default; override individual methods for specific and useful
2345 behaviour. The `dispatch_visit()` method is called by
2346 `Node.walk()` upon entering a node. `Node.walkabout()` also calls
2347 the `dispatch_departure()` method before exiting a node.
2349 The dispatch methods call "``visit_`` + node class name" or
2350 "``depart_`` + node class name", resp.
2352 This is a base class for visitors whose ``visit_...`` & ``depart_...``
2353 methods must be implemented for *all* compulsory node types encountered
2354 (such as for `docutils.writers.Writer` subclasses).
2355 Unimplemented methods will raise exceptions (except for optional nodes).
2357 For sparse traversals, where only certain node types are of interest, use
2358 subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform
2359 processing is desired, subclass `GenericNodeVisitor`.
2361 .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
2362 Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
2363 1995.
2364 """
2366 optional = ('meta',)
2367 """
2368 Tuple containing node class names (as strings).
2370 No exception will be raised if writers do not implement visit
2371 or departure functions for these node classes.
2373 Used to ensure transitional compatibility with existing 3rd-party writers.
2374 """
2376 def __init__(self, document):
2377 self.document = document
2379 def dispatch_visit(self, node):
2380 """
2381 Call self."``visit_`` + node class name" with `node` as
2382 parameter. If the ``visit_...`` method does not exist, call
2383 self.unknown_visit.
2384 """
2385 node_name = node.__class__.__name__
2386 method = getattr(self, 'visit_' + node_name, self.unknown_visit)
2387 self.document.reporter.debug(
2388 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s'
2389 % (method.__name__, node_name))
2390 return method(node)
2392 def dispatch_departure(self, node):
2393 """
2394 Call self."``depart_`` + node class name" with `node` as
2395 parameter. If the ``depart_...`` method does not exist, call
2396 self.unknown_departure.
2397 """
2398 node_name = node.__class__.__name__
2399 method = getattr(self, 'depart_' + node_name, self.unknown_departure)
2400 self.document.reporter.debug(
2401 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s'
2402 % (method.__name__, node_name))
2403 return method(node)
2405 def unknown_visit(self, node):
2406 """
2407 Called when entering unknown `Node` types.
2409 Raise an exception unless overridden.
2410 """
2411 if (self.document.settings.strict_visitor
2412 or node.__class__.__name__ not in self.optional):
2413 raise NotImplementedError(
2414 '%s visiting unknown node type: %s'
2415 % (self.__class__, node.__class__.__name__))
2417 def unknown_departure(self, node):
2418 """
2419 Called before exiting unknown `Node` types.
2421 Raise exception unless overridden.
2422 """
2423 if (self.document.settings.strict_visitor
2424 or node.__class__.__name__ not in self.optional):
2425 raise NotImplementedError(
2426 '%s departing unknown node type: %s'
2427 % (self.__class__, node.__class__.__name__))
2430class SparseNodeVisitor(NodeVisitor):
2431 """
2432 Base class for sparse traversals, where only certain node types are of
2433 interest. When ``visit_...`` & ``depart_...`` methods should be
2434 implemented for *all* node types (such as for `docutils.writers.Writer`
2435 subclasses), subclass `NodeVisitor` instead.
2436 """
2439class GenericNodeVisitor(NodeVisitor):
2440 """
2441 Generic "Visitor" abstract superclass, for simple traversals.
2443 Unless overridden, each ``visit_...`` method calls `default_visit()`, and
2444 each ``depart_...`` method (when using `Node.walkabout()`) calls
2445 `default_departure()`. `default_visit()` (and `default_departure()`) must
2446 be overridden in subclasses.
2448 Define fully generic visitors by overriding `default_visit()` (and
2449 `default_departure()`) only. Define semi-generic visitors by overriding
2450 individual ``visit_...()`` (and ``depart_...()``) methods also.
2452 `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should
2453 be overridden for default behavior.
2454 """
2456 def default_visit(self, node):
2457 """Override for generic, uniform traversals."""
2458 raise NotImplementedError
2460 def default_departure(self, node):
2461 """Override for generic, uniform traversals."""
2462 raise NotImplementedError
2465def _call_default_visit(self, node):
2466 self.default_visit(node)
2469def _call_default_departure(self, node):
2470 self.default_departure(node)
2473def _nop(self, node):
2474 pass
2477def _add_node_class_names(names):
2478 """Save typing with dynamic assignments:"""
2479 for _name in names:
2480 setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
2481 setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
2482 setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
2483 setattr(SparseNodeVisitor, 'depart_' + _name, _nop)
2486_add_node_class_names(node_class_names)
2489class TreeCopyVisitor(GenericNodeVisitor):
2490 """
2491 Make a complete copy of a tree or branch, including element attributes.
2492 """
2494 def __init__(self, document):
2495 GenericNodeVisitor.__init__(self, document)
2496 self.parent_stack = []
2497 self.parent = []
2499 def get_tree_copy(self):
2500 return self.parent[0]
2502 def default_visit(self, node):
2503 """Copy the current node, and make it the new acting parent."""
2504 newnode = node.copy()
2505 self.parent.append(newnode)
2506 self.parent_stack.append(self.parent)
2507 self.parent = newnode
2509 def default_departure(self, node):
2510 """Restore the previous acting parent."""
2511 self.parent = self.parent_stack.pop()
2514class TreePruningException(Exception):
2515 """
2516 Base class for `NodeVisitor`-related tree pruning exceptions.
2518 Raise subclasses from within ``visit_...`` or ``depart_...`` methods
2519 called from `Node.walk()` and `Node.walkabout()` tree traversals to prune
2520 the tree traversed.
2521 """
2524class SkipChildren(TreePruningException):
2525 """
2526 Do not visit any children of the current node. The current node's
2527 siblings and ``depart_...`` method are not affected.
2528 """
2531class SkipSiblings(TreePruningException):
2532 """
2533 Do not visit any more siblings (to the right) of the current node. The
2534 current node's children and its ``depart_...`` method are not affected.
2535 """
2538class SkipNode(TreePruningException):
2539 """
2540 Do not visit the current node's children, and do not call the current
2541 node's ``depart_...`` method.
2542 """
2545class SkipDeparture(TreePruningException):
2546 """
2547 Do not call the current node's ``depart_...`` method. The current node's
2548 children and siblings are not affected.
2549 """
2552class NodeFound(TreePruningException):
2553 """
2554 Raise to indicate that the target of a search has been found. This
2555 exception must be caught by the client; it is not caught by the traversal
2556 code.
2557 """
2560class StopTraversal(TreePruningException):
2561 """
2562 Stop the traversal altogether. The current node's ``depart_...`` method
2563 is not affected. The parent nodes ``depart_...`` methods are also called
2564 as usual. No other nodes are visited. This is an alternative to
2565 NodeFound that does not cause exception handling to trickle up to the
2566 caller.
2567 """
2570# definition moved here from `utils` to avoid circular import dependency
2571def unescape(text, restore_backslashes=False, respect_whitespace=False):
2572 """
2573 Return a string with nulls removed or restored to backslashes.
2574 Backslash-escaped spaces are also removed.
2575 """
2576 # `respect_whitespace` is ignored (since introduction 2016-12-16)
2577 if restore_backslashes:
2578 return text.replace('\x00', '\\')
2579 else:
2580 for sep in ['\x00 ', '\x00\n', '\x00']:
2581 text = ''.join(text.split(sep))
2582 return text
2585def make_id(string):
2586 """
2587 Convert `string` into an identifier and return it.
2589 Docutils identifiers will conform to the regular expression
2590 ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class"
2591 and "id" attributes) should have no underscores, colons, or periods.
2592 Hyphens may be used.
2594 - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
2596 ID and NAME tokens must begin with a letter ([A-Za-z]) and may be
2597 followed by any number of letters, digits ([0-9]), hyphens ("-"),
2598 underscores ("_"), colons (":"), and periods (".").
2600 - However the `CSS1 spec`_ defines identifiers based on the "name" token,
2601 a tighter interpretation ("flex" tokenizer notation; "latin1" and
2602 "escape" 8-bit characters have been replaced with entities)::
2604 unicode \\[0-9a-f]{1,4}
2605 latin1 [¡-ÿ]
2606 escape {unicode}|\\[ -~¡-ÿ]
2607 nmchar [-a-z0-9]|{latin1}|{escape}
2608 name {nmchar}+
2610 The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"),
2611 or periods ("."), therefore "class" and "id" attributes should not contain
2612 these characters. They should be replaced with hyphens ("-"). Combined
2613 with HTML's requirements (the first character must be a letter; no
2614 "unicode", "latin1", or "escape" characters), this results in the
2615 ``[a-z](-?[a-z0-9]+)*`` pattern.
2617 .. _HTML 4.01 spec: https://www.w3.org/TR/html401
2618 .. _CSS1 spec: https://www.w3.org/TR/REC-CSS1
2619 """
2620 id = string.lower()
2621 id = id.translate(_non_id_translate_digraphs)
2622 id = id.translate(_non_id_translate)
2623 # get rid of non-ascii characters.
2624 # 'ascii' lowercase to prevent problems with turkish locale.
2625 id = unicodedata.normalize(
2626 'NFKD', id).encode('ascii', 'ignore').decode('ascii')
2627 # shrink runs of whitespace and replace by hyphen
2628 id = _non_id_chars.sub('-', ' '.join(id.split()))
2629 id = _non_id_at_ends.sub('', id)
2630 return str(id)
2633_non_id_chars = re.compile('[^a-z0-9]+')
2634_non_id_at_ends = re.compile('^[-0-9]+|-+$')
2635_non_id_translate = {
2636 0x00f8: 'o', # o with stroke
2637 0x0111: 'd', # d with stroke
2638 0x0127: 'h', # h with stroke
2639 0x0131: 'i', # dotless i
2640 0x0142: 'l', # l with stroke
2641 0x0167: 't', # t with stroke
2642 0x0180: 'b', # b with stroke
2643 0x0183: 'b', # b with topbar
2644 0x0188: 'c', # c with hook
2645 0x018c: 'd', # d with topbar
2646 0x0192: 'f', # f with hook
2647 0x0199: 'k', # k with hook
2648 0x019a: 'l', # l with bar
2649 0x019e: 'n', # n with long right leg
2650 0x01a5: 'p', # p with hook
2651 0x01ab: 't', # t with palatal hook
2652 0x01ad: 't', # t with hook
2653 0x01b4: 'y', # y with hook
2654 0x01b6: 'z', # z with stroke
2655 0x01e5: 'g', # g with stroke
2656 0x0225: 'z', # z with hook
2657 0x0234: 'l', # l with curl
2658 0x0235: 'n', # n with curl
2659 0x0236: 't', # t with curl
2660 0x0237: 'j', # dotless j
2661 0x023c: 'c', # c with stroke
2662 0x023f: 's', # s with swash tail
2663 0x0240: 'z', # z with swash tail
2664 0x0247: 'e', # e with stroke
2665 0x0249: 'j', # j with stroke
2666 0x024b: 'q', # q with hook tail
2667 0x024d: 'r', # r with stroke
2668 0x024f: 'y', # y with stroke
2669}
2670_non_id_translate_digraphs = {
2671 0x00df: 'sz', # ligature sz
2672 0x00e6: 'ae', # ae
2673 0x0153: 'oe', # ligature oe
2674 0x0238: 'db', # db digraph
2675 0x0239: 'qp', # qp digraph
2676}
2679def dupname(node, name):
2680 node['dupnames'].append(name)
2681 node['names'].remove(name)
2682 # Assume that `node` is referenced, even though it isn't;
2683 # we don't want to throw unnecessary system_messages.
2684 node.referenced = True
2687def fully_normalize_name(name):
2688 """Return a case- and whitespace-normalized name."""
2689 return ' '.join(name.lower().split())
2692def whitespace_normalize_name(name):
2693 """Return a whitespace-normalized name."""
2694 return ' '.join(name.split())
2697def serial_escape(value):
2698 """Escape string values that are elements of a list, for serialization."""
2699 return value.replace('\\', r'\\').replace(' ', r'\ ')
2702def split_name_list(s):
2703 r"""Split a string at non-escaped whitespace.
2705 Backslashes escape internal whitespace (cf. `serial_escape()`).
2706 Return list of "names" (after removing escaping backslashes).
2708 >>> split_name_list(r'a\ n\ame two\\ n\\ames'),
2709 ['a name', 'two\\', r'n\ames']
2711 Provisional.
2712 """
2713 s = s.replace('\\', '\x00') # escape with NULL char
2714 s = s.replace('\x00\x00', '\\') # unescape backslashes
2715 s = s.replace('\x00 ', '\x00\x00') # escaped spaces -> NULL NULL
2716 names = s.split(' ')
2717 # restore internal spaces, drop other escaping characters
2718 return [name.replace('\x00\x00', ' ').replace('\x00', '')
2719 for name in names]
2722def pseudo_quoteattr(value):
2723 """Quote attributes for pseudo-xml"""
2724 return '"%s"' % value
2727# Methods to validate `Element attribute`__ values.
2729# Ensure the expected Python `data type`__, normalize, and check for
2730# restrictions.
2731#
2732# The methods can be used to convert `str` values (eg. from an XML
2733# representation) or to validate an existing document tree or node.
2734#
2735# Cf. `Element.validate_attributes()`, `docutils.parsers.docutils_xml`,
2736# and the `attribute_validating_functions` mapping below.
2737#
2738# __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-reference
2739# __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-types
2741def validate_enumerated_type(*keywords):
2742 """
2743 Return a function that validates a `str` against given `keywords`.
2745 Provisional.
2746 """
2747 def validate_keywords(value):
2748 if value not in keywords:
2749 allowed = '", \"'.join(keywords)
2750 raise ValueError(f'"{value}" is not one of "{allowed}".')
2751 return value
2752 return validate_keywords
2755def validate_identifier(value):
2756 """
2757 Validate identifier key or class name.
2759 Used in `idref.type`__ and for the tokens in `validate_identifier_list()`.
2761 __ https://docutils.sourceforge.io/docs/ref/doctree.html#idref-type
2763 Provisional.
2764 """
2765 if value != make_id(value):
2766 raise ValueError(f'"{value}" is no valid id or class name.')
2767 return value
2770def validate_identifier_list(value):
2771 """
2772 A (space-separated) list of ids or class names.
2774 `value` may be a `list` or a `str` with space separated
2775 ids or class names (cf. `validate_identifier()`).
2777 Used in `classnames.type`__, `ids.type`__, and `idrefs.type`__.
2779 __ https://docutils.sourceforge.io/docs/ref/doctree.html#classnames-type
2780 __ https://docutils.sourceforge.io/docs/ref/doctree.html#ids-type
2781 __ https://docutils.sourceforge.io/docs/ref/doctree.html#idrefs-type
2783 Provisional.
2784 """
2785 if isinstance(value, str):
2786 value = value.split()
2787 for token in value:
2788 validate_identifier(token)
2789 return value
2792def validate_measure(value):
2793 """
2794 Validate a length measure__ (number + recognized unit).
2796 __ https://docutils.sourceforge.io/docs/ref/doctree.html#measure
2798 Provisional.
2799 """
2800 units = 'em|ex|px|in|cm|mm|pt|pc|%'
2801 if not re.fullmatch(f'[-0-9.]+ *({units}?)', value):
2802 raise ValueError(f'"{value}" is no valid measure. '
2803 f'Valid units: {units.replace("|", " ")}.')
2804 return value.replace(' ', '').strip()
2807def validate_NMTOKEN(value):
2808 """
2809 Validate a "name token": a `str` of letters, digits, and [-._].
2811 Provisional.
2812 """
2813 if not re.fullmatch('[-._A-Za-z0-9]+', value):
2814 raise ValueError(f'"{value}" is no NMTOKEN.')
2815 return value
2818def validate_NMTOKENS(value):
2819 """
2820 Validate a list of "name tokens".
2822 Provisional.
2823 """
2824 if isinstance(value, str):
2825 value = value.split()
2826 for token in value:
2827 validate_NMTOKEN(token)
2828 return value
2831def validate_refname_list(value):
2832 """
2833 Validate a list of `reference names`__.
2835 Reference names may contain all characters;
2836 whitespace is normalized (cf, `whitespace_normalize_name()`).
2838 `value` may be either a `list` of names or a `str` with
2839 space separated names (with internal spaces backslash escaped
2840 and literal backslashes doubled cf. `serial_escape()`).
2842 Return a list of whitespace-normalized, unescaped reference names.
2844 Provisional.
2846 __ https://docutils.sourceforge.io/docs/ref/doctree.html#reference-name
2847 """
2848 if isinstance(value, str):
2849 value = split_name_list(value)
2850 return [whitespace_normalize_name(name) for name in value]
2853def validate_yesorno(value):
2854 if value == "0":
2855 return False
2856 return bool(value)
2859ATTRIBUTE_VALIDATORS = {
2860 'alt': str, # CDATA
2861 'align': str,
2862 'anonymous': validate_yesorno,
2863 'auto': str, # CDATA (only '1' or '*' are used in rST)
2864 'backrefs': validate_identifier_list,
2865 'bullet': str, # CDATA (only '-', '+', or '*' are used in rST)
2866 'classes': validate_identifier_list,
2867 'char': str, # from Exchange Table Model (CALS), currently ignored
2868 'charoff': validate_NMTOKEN, # from CALS, currently ignored
2869 'colname': validate_NMTOKEN, # from CALS, currently ignored
2870 'colnum': int, # from CALS, currently ignored
2871 'cols': int, # from CALS: "NMTOKEN, […] must be an integer > 0".
2872 'colsep': validate_yesorno,
2873 'colwidth': int, # sic! CALS: CDATA (measure or number+'*')
2874 'content': str, # <meta>
2875 'delimiter': str,
2876 'depth': int,
2877 'dir': validate_enumerated_type('ltr', 'rtl', 'auto'), # <meta>
2878 'dupnames': validate_refname_list,
2879 'enumtype': validate_enumerated_type('arabic', 'loweralpha', 'lowerroman',
2880 'upperalpha', 'upperroman'),
2881 'format': str, # CDATA (space separated format names)
2882 'frame': validate_enumerated_type('top', 'bottom', 'topbot', 'all',
2883 'sides', 'none'), # from CALS, ignored
2884 'height': validate_measure,
2885 'http-equiv': str, # <meta>
2886 'ids': validate_identifier_list,
2887 'lang': str, # <meta>
2888 'level': int,
2889 'line': int,
2890 'local': validate_yesorno,
2891 'ltrim': validate_yesorno,
2892 'loading': validate_enumerated_type('embed', 'link', 'lazy'),
2893 'media': str, # <meta>
2894 'morecols': int,
2895 'morerows': int,
2896 'name': whitespace_normalize_name, # in <reference> (deprecated)
2897 # 'name': node_attributes.validate_NMTOKEN, # in <meta>
2898 'names': validate_refname_list,
2899 'namest': validate_NMTOKEN, # start of span, from CALS, currently ignored
2900 'nameend': validate_NMTOKEN, # end of span, from CALS, currently ignored
2901 'pgwide': validate_yesorno, # from CALS, currently ignored
2902 'prefix': str,
2903 'refid': validate_identifier,
2904 'refname': whitespace_normalize_name,
2905 'refuri': str,
2906 'rowsep': validate_yesorno,
2907 'rtrim': validate_yesorno,
2908 'scale': int,
2909 'scheme': str,
2910 'source': str,
2911 'start': int,
2912 'stub': validate_yesorno,
2913 'suffix': str,
2914 'title': str,
2915 'type': validate_NMTOKEN,
2916 'uri': str,
2917 'valign': validate_enumerated_type('top', 'middle', 'bottom'), # from CALS
2918 'width': validate_measure,
2919 'xml:space': validate_enumerated_type('default', 'preserve'),
2920 }
2921"""
2922Mapping of `attribute names`__ to validating functions.
2924Provisional.
2926__ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-reference
2927"""