Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/docutils/nodes.py: 57%
954 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-14 06:25 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-03-14 06:25 +0000
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 warnings
29import unicodedata
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 traverse(self, condition=None, include_self=True, descend=True,
219 siblings=False, ascend=False):
220 """Return list of nodes following `self`.
222 For looping, Node.findall() is faster and more memory efficient.
223 """
224 # traverse() may be eventually removed:
225 warnings.warn('nodes.Node.traverse() is obsoleted by Node.findall().',
226 PendingDeprecationWarning, stacklevel=2)
227 return list(self.findall(condition, include_self, descend,
228 siblings, ascend))
230 def findall(self, condition=None, include_self=True, descend=True,
231 siblings=False, ascend=False):
232 """
233 Return an iterator yielding nodes following `self`:
235 * self (if `include_self` is true)
236 * all descendants in tree traversal order (if `descend` is true)
237 * the following siblings (if `siblings` is true) and their
238 descendants (if also `descend` is true)
239 * the following siblings of the parent (if `ascend` is true) and
240 their descendants (if also `descend` is true), and so on.
242 If `condition` is not None, the iterator yields only nodes
243 for which ``condition(node)`` is true. If `condition` is a
244 node class ``cls``, it is equivalent to a function consisting
245 of ``return isinstance(node, cls)``.
247 If `ascend` is true, assume `siblings` to be true as well.
249 If the tree structure is modified during iteration, the result
250 is undefined.
252 For example, given the following tree::
254 <paragraph>
255 <emphasis> <--- emphasis.traverse() and
256 <strong> <--- strong.traverse() are called.
257 Foo
258 Bar
259 <reference name="Baz" refid="baz">
260 Baz
262 Then tuple(emphasis.traverse()) equals ::
264 (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>)
266 and list(strong.traverse(ascend=True) equals ::
268 [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>]
269 """
270 if ascend:
271 siblings = True
272 # Check for special argument combinations that allow using an
273 # optimized version of traverse()
274 if include_self and descend and not siblings:
275 if condition is None:
276 yield from self._superfast_findall()
277 return
278 elif isinstance(condition, type):
279 yield from self._fast_findall(condition)
280 return
281 # Check if `condition` is a class (check for TypeType for Python
282 # implementations that use only new-style classes, like PyPy).
283 if isinstance(condition, type):
284 node_class = condition
286 def condition(node, node_class=node_class):
287 return isinstance(node, node_class)
289 if include_self and (condition is None or condition(self)):
290 yield self
291 if descend and len(self.children):
292 for child in self:
293 yield from child.findall(condition=condition,
294 include_self=True, descend=True,
295 siblings=False, ascend=False)
296 if siblings or ascend:
297 node = self
298 while node.parent:
299 index = node.parent.index(node)
300 # extra check since Text nodes have value-equality
301 while node.parent[index] is not node:
302 index = node.parent.index(node, index + 1)
303 for sibling in node.parent[index+1:]:
304 yield from sibling.findall(
305 condition=condition,
306 include_self=True, descend=descend,
307 siblings=False, ascend=False)
308 if not ascend:
309 break
310 else:
311 node = node.parent
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
329# definition moved here from `utils` to avoid circular import dependency
330def unescape(text, restore_backslashes=False, respect_whitespace=False):
331 """
332 Return a string with nulls removed or restored to backslashes.
333 Backslash-escaped spaces are also removed.
334 """
335 # `respect_whitespace` is ignored (since introduction 2016-12-16)
336 if restore_backslashes:
337 return text.replace('\x00', '\\')
338 else:
339 for sep in ['\x00 ', '\x00\n', '\x00']:
340 text = ''.join(text.split(sep))
341 return text
344class Text(Node, str):
346 """
347 Instances are terminal nodes (leaves) containing text only; no child
348 nodes or attributes. Initialize by passing a string to the constructor.
350 Access the raw (null-escaped) text with ``str(<instance>)``
351 and unescaped text with ``<instance>.astext()``.
352 """
354 tagname = '#text'
356 children = ()
357 """Text nodes have no children, and cannot have children."""
359 def __new__(cls, data, rawsource=None):
360 """Assert that `data` is not an array of bytes
361 and warn if the deprecated `rawsource` argument is used.
362 """
363 if isinstance(data, bytes):
364 raise TypeError('expecting str data, not bytes')
365 if rawsource is not None:
366 warnings.warn('nodes.Text: initialization argument "rawsource" '
367 'is ignored and will be removed in Docutils 2.0.',
368 DeprecationWarning, stacklevel=2)
369 return str.__new__(cls, data)
371 def shortrepr(self, maxlen=18):
372 data = self
373 if len(data) > maxlen:
374 data = data[:maxlen-4] + ' ...'
375 return '<%s: %r>' % (self.tagname, str(data))
377 def __repr__(self):
378 return self.shortrepr(maxlen=68)
380 def _dom_node(self, domroot):
381 return domroot.createTextNode(str(self))
383 def astext(self):
384 return str(unescape(self))
386 def copy(self):
387 return self.__class__(str(self))
389 def deepcopy(self):
390 return self.copy()
392 def pformat(self, indent=' ', level=0):
393 try:
394 if self.document.settings.detailed:
395 tag = '%s%s' % (indent*level, '<#text>')
396 lines = (indent*(level+1) + repr(line)
397 for line in self.splitlines(True))
398 return '\n'.join((tag, *lines)) + '\n'
399 except AttributeError:
400 pass
401 indent = indent * level
402 lines = [indent+line for line in self.astext().splitlines()]
403 if not lines:
404 return ''
405 return '\n'.join(lines) + '\n'
407 # rstrip and lstrip are used by substitution definitions where
408 # they are expected to return a Text instance, this was formerly
409 # taken care of by UserString.
411 def rstrip(self, chars=None):
412 return self.__class__(str.rstrip(self, chars))
414 def lstrip(self, chars=None):
415 return self.__class__(str.lstrip(self, chars))
418class Element(Node):
420 """
421 `Element` is the superclass to all specific elements.
423 Elements contain attributes and child nodes.
424 They can be described as a cross between a list and a dictionary.
426 Elements emulate dictionaries for external [#]_ attributes, indexing by
427 attribute name (a string). To set the attribute 'att' to 'value', do::
429 element['att'] = 'value'
431 .. [#] External attributes correspond to the XML element attributes.
432 From its `Node` superclass, Element also inherits "internal"
433 class attributes that are accessed using the standard syntax, e.g.
434 ``element.parent``.
436 There are two special attributes: 'ids' and 'names'. Both are
437 lists of unique identifiers: 'ids' conform to the regular expression
438 ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function for rationale and
439 details). 'names' serve as user-friendly interfaces to IDs; they are
440 case- and whitespace-normalized (see the fully_normalize_name() function).
442 Elements emulate lists for child nodes (element nodes and/or text
443 nodes), indexing by integer. To get the first child node, use::
445 element[0]
447 to iterate over the child nodes (without descending), use::
449 for child in element:
450 ...
452 Elements may be constructed using the ``+=`` operator. To add one new
453 child node to element, do::
455 element += node
457 This is equivalent to ``element.append(node)``.
459 To add a list of multiple child nodes at once, use the same ``+=``
460 operator::
462 element += [node1, node2]
464 This is equivalent to ``element.extend([node1, node2])``.
465 """
467 basic_attributes = ('ids', 'classes', 'names', 'dupnames')
468 """Tuple of attributes which are defined for every Element-derived class
469 instance and can be safely transferred to a different node."""
471 local_attributes = ('backrefs',)
472 """Tuple of class-specific attributes that should not be copied with the
473 standard attributes when replacing a node.
475 NOTE: Derived classes should override this value to prevent any of its
476 attributes being copied by adding to the value in its parent class."""
478 list_attributes = basic_attributes + local_attributes
479 """Tuple of attributes that are automatically initialized to empty lists
480 for all nodes."""
482 known_attributes = list_attributes + ('source',)
483 """Tuple of attributes that are known to the Element base class."""
485 tagname = None
486 """The element generic identifier. If None, it is set as an instance
487 attribute to the name of the class."""
489 child_text_separator = '\n\n'
490 """Separator for child nodes, used by `astext()` method."""
492 def __init__(self, rawsource='', *children, **attributes):
493 self.rawsource = rawsource
494 """The raw text from which this element was constructed.
496 NOTE: some elements do not set this value (default '').
497 """
499 self.children = []
500 """List of child nodes (elements and/or `Text`)."""
502 self.extend(children) # maintain parent info
504 self.attributes = {}
505 """Dictionary of attribute {name: value}."""
507 # Initialize list attributes.
508 for att in self.list_attributes:
509 self.attributes[att] = []
511 for att, value in attributes.items():
512 att = att.lower()
513 if att in self.list_attributes:
514 # mutable list; make a copy for this node
515 self.attributes[att] = value[:]
516 else:
517 self.attributes[att] = value
519 if self.tagname is None:
520 self.tagname = self.__class__.__name__
522 def _dom_node(self, domroot):
523 element = domroot.createElement(self.tagname)
524 for attribute, value in self.attlist():
525 if isinstance(value, list):
526 value = ' '.join(serial_escape('%s' % (v,)) for v in value)
527 element.setAttribute(attribute, '%s' % value)
528 for child in self.children:
529 element.appendChild(child._dom_node(domroot))
530 return element
532 def __repr__(self):
533 data = ''
534 for c in self.children:
535 data += c.shortrepr()
536 if len(data) > 60:
537 data = data[:56] + ' ...'
538 break
539 if self['names']:
540 return '<%s "%s": %s>' % (self.__class__.__name__,
541 '; '.join(self['names']), data)
542 else:
543 return '<%s: %s>' % (self.__class__.__name__, data)
545 def shortrepr(self):
546 if self['names']:
547 return '<%s "%s"...>' % (self.__class__.__name__,
548 '; '.join(self['names']))
549 else:
550 return '<%s...>' % self.tagname
552 def __str__(self):
553 if self.children:
554 return '%s%s%s' % (self.starttag(),
555 ''.join(str(c) for c in self.children),
556 self.endtag())
557 else:
558 return self.emptytag()
560 def starttag(self, quoteattr=None):
561 # the optional arg is used by the docutils_xml writer
562 if quoteattr is None:
563 quoteattr = pseudo_quoteattr
564 parts = [self.tagname]
565 for name, value in self.attlist():
566 if value is None: # boolean attribute
567 parts.append('%s="True"' % name)
568 continue
569 if isinstance(value, list):
570 values = [serial_escape('%s' % (v,)) for v in value]
571 value = ' '.join(values)
572 else:
573 value = str(value)
574 value = quoteattr(value)
575 parts.append('%s=%s' % (name, value))
576 return '<%s>' % ' '.join(parts)
578 def endtag(self):
579 return '</%s>' % self.tagname
581 def emptytag(self):
582 attributes = ('%s="%s"' % (n, v) for n, v in self.attlist())
583 return '<%s/>' % ' '.join((self.tagname, *attributes))
585 def __len__(self):
586 return len(self.children)
588 def __contains__(self, key):
589 # Test for both, children and attributes with operator ``in``.
590 if isinstance(key, str):
591 return key in self.attributes
592 return key in self.children
594 def __getitem__(self, key):
595 if isinstance(key, str):
596 return self.attributes[key]
597 elif isinstance(key, int):
598 return self.children[key]
599 elif isinstance(key, slice):
600 assert key.step in (None, 1), 'cannot handle slice with stride'
601 return self.children[key.start:key.stop]
602 else:
603 raise TypeError('element index must be an integer, a slice, or '
604 'an attribute name string')
606 def __setitem__(self, key, item):
607 if isinstance(key, str):
608 self.attributes[str(key)] = item
609 elif isinstance(key, int):
610 self.setup_child(item)
611 self.children[key] = item
612 elif isinstance(key, slice):
613 assert key.step in (None, 1), 'cannot handle slice with stride'
614 for node in item:
615 self.setup_child(node)
616 self.children[key.start:key.stop] = item
617 else:
618 raise TypeError('element index must be an integer, a slice, or '
619 'an attribute name string')
621 def __delitem__(self, key):
622 if isinstance(key, str):
623 del self.attributes[key]
624 elif isinstance(key, int):
625 del self.children[key]
626 elif isinstance(key, slice):
627 assert key.step in (None, 1), 'cannot handle slice with stride'
628 del self.children[key.start:key.stop]
629 else:
630 raise TypeError('element index must be an integer, a simple '
631 'slice, or an attribute name string')
633 def __add__(self, other):
634 return self.children + other
636 def __radd__(self, other):
637 return other + self.children
639 def __iadd__(self, other):
640 """Append a node or a list of nodes to `self.children`."""
641 if isinstance(other, Node):
642 self.append(other)
643 elif other is not None:
644 self.extend(other)
645 return self
647 def astext(self):
648 return self.child_text_separator.join(
649 [child.astext() for child in self.children])
651 def non_default_attributes(self):
652 atts = {}
653 for key, value in self.attributes.items():
654 if self.is_not_default(key):
655 atts[key] = value
656 return atts
658 def attlist(self):
659 return sorted(self.non_default_attributes().items())
661 def get(self, key, failobj=None):
662 return self.attributes.get(key, failobj)
664 def hasattr(self, attr):
665 return attr in self.attributes
667 def delattr(self, attr):
668 if attr in self.attributes:
669 del self.attributes[attr]
671 def setdefault(self, key, failobj=None):
672 return self.attributes.setdefault(key, failobj)
674 has_key = hasattr
676 def get_language_code(self, fallback=''):
677 """Return node's language tag.
679 Look iteratively in self and parents for a class argument
680 starting with ``language-`` and return the remainder of it
681 (which should be a `BCP49` language tag) or the `fallback`.
682 """
683 for cls in self.get('classes', []):
684 if cls.startswith('language-'):
685 return cls[9:]
686 try:
687 return self.parent.get_language(fallback)
688 except AttributeError:
689 return fallback
691 def append(self, item):
692 self.setup_child(item)
693 self.children.append(item)
695 def extend(self, item):
696 for node in item:
697 self.append(node)
699 def insert(self, index, item):
700 if isinstance(item, Node):
701 self.setup_child(item)
702 self.children.insert(index, item)
703 elif item is not None:
704 self[index:index] = item
706 def pop(self, i=-1):
707 return self.children.pop(i)
709 def remove(self, item):
710 self.children.remove(item)
712 def index(self, item, start=0, stop=sys.maxsize):
713 return self.children.index(item, start, stop)
715 def previous_sibling(self):
716 """Return preceding sibling node or ``None``."""
717 try:
718 i = self.parent.index(self)
719 except (AttributeError):
720 return None
721 return self.parent[i-1] if i > 0 else None
723 def is_not_default(self, key):
724 if self[key] == [] and key in self.list_attributes:
725 return 0
726 else:
727 return 1
729 def update_basic_atts(self, dict_):
730 """
731 Update basic attributes ('ids', 'names', 'classes',
732 'dupnames', but not 'source') from node or dictionary `dict_`.
733 """
734 if isinstance(dict_, Node):
735 dict_ = dict_.attributes
736 for att in self.basic_attributes:
737 self.append_attr_list(att, dict_.get(att, []))
739 def append_attr_list(self, attr, values):
740 """
741 For each element in values, if it does not exist in self[attr], append
742 it.
744 NOTE: Requires self[attr] and values to be sequence type and the
745 former should specifically be a list.
746 """
747 # List Concatenation
748 for value in values:
749 if value not in self[attr]:
750 self[attr].append(value)
752 def coerce_append_attr_list(self, attr, value):
753 """
754 First, convert both self[attr] and value to a non-string sequence
755 type; if either is not already a sequence, convert it to a list of one
756 element. Then call append_attr_list.
758 NOTE: self[attr] and value both must not be None.
759 """
760 # List Concatenation
761 if not isinstance(self.get(attr), list):
762 self[attr] = [self[attr]]
763 if not isinstance(value, list):
764 value = [value]
765 self.append_attr_list(attr, value)
767 def replace_attr(self, attr, value, force=True):
768 """
769 If self[attr] does not exist or force is True or omitted, set
770 self[attr] to value, otherwise do nothing.
771 """
772 # One or the other
773 if force or self.get(attr) is None:
774 self[attr] = value
776 def copy_attr_convert(self, attr, value, replace=True):
777 """
778 If attr is an attribute of self, set self[attr] to
779 [self[attr], value], otherwise set self[attr] to value.
781 NOTE: replace is not used by this function and is kept only for
782 compatibility with the other copy functions.
783 """
784 if self.get(attr) is not value:
785 self.coerce_append_attr_list(attr, value)
787 def copy_attr_coerce(self, attr, value, replace):
788 """
789 If attr is an attribute of self and either self[attr] or value is a
790 list, convert all non-sequence values to a sequence of 1 element and
791 then concatenate the two sequence, setting the result to self[attr].
792 If both self[attr] and value are non-sequences and replace is True or
793 self[attr] is None, replace self[attr] with value. Otherwise, do
794 nothing.
795 """
796 if self.get(attr) is not value:
797 if isinstance(self.get(attr), list) or \
798 isinstance(value, list):
799 self.coerce_append_attr_list(attr, value)
800 else:
801 self.replace_attr(attr, value, replace)
803 def copy_attr_concatenate(self, attr, value, replace):
804 """
805 If attr is an attribute of self and both self[attr] and value are
806 lists, concatenate the two sequences, setting the result to
807 self[attr]. If either self[attr] or value are non-sequences and
808 replace is True or self[attr] is None, replace self[attr] with value.
809 Otherwise, do nothing.
810 """
811 if self.get(attr) is not value:
812 if isinstance(self.get(attr), list) and \
813 isinstance(value, list):
814 self.append_attr_list(attr, value)
815 else:
816 self.replace_attr(attr, value, replace)
818 def copy_attr_consistent(self, attr, value, replace):
819 """
820 If replace is True or self[attr] is None, replace self[attr] with
821 value. Otherwise, do nothing.
822 """
823 if self.get(attr) is not value:
824 self.replace_attr(attr, value, replace)
826 def update_all_atts(self, dict_, update_fun=copy_attr_consistent,
827 replace=True, and_source=False):
828 """
829 Updates all attributes from node or dictionary `dict_`.
831 Appends the basic attributes ('ids', 'names', 'classes',
832 'dupnames', but not 'source') and then, for all other attributes in
833 dict_, updates the same attribute in self. When attributes with the
834 same identifier appear in both self and dict_, the two values are
835 merged based on the value of update_fun. Generally, when replace is
836 True, the values in self are replaced or merged with the values in
837 dict_; otherwise, the values in self may be preserved or merged. When
838 and_source is True, the 'source' attribute is included in the copy.
840 NOTE: When replace is False, and self contains a 'source' attribute,
841 'source' is not replaced even when dict_ has a 'source'
842 attribute, though it may still be merged into a list depending
843 on the value of update_fun.
844 NOTE: It is easier to call the update-specific methods then to pass
845 the update_fun method to this function.
846 """
847 if isinstance(dict_, Node):
848 dict_ = dict_.attributes
850 # Include the source attribute when copying?
851 if and_source:
852 filter_fun = self.is_not_list_attribute
853 else:
854 filter_fun = self.is_not_known_attribute
856 # Copy the basic attributes
857 self.update_basic_atts(dict_)
859 # Grab other attributes in dict_ not in self except the
860 # (All basic attributes should be copied already)
861 for att in filter(filter_fun, dict_):
862 update_fun(self, att, dict_[att], replace)
864 def update_all_atts_consistantly(self, dict_, replace=True,
865 and_source=False):
866 """
867 Updates all attributes from node or dictionary `dict_`.
869 Appends the basic attributes ('ids', 'names', 'classes',
870 'dupnames', but not 'source') and then, for all other attributes in
871 dict_, updates the same attribute in self. When attributes with the
872 same identifier appear in both self and dict_ and replace is True, the
873 values in self are replaced with the values in dict_; otherwise, the
874 values in self are preserved. When and_source is True, the 'source'
875 attribute is included in the copy.
877 NOTE: When replace is False, and self contains a 'source' attribute,
878 'source' is not replaced even when dict_ has a 'source'
879 attribute, though it may still be merged into a list depending
880 on the value of update_fun.
881 """
882 self.update_all_atts(dict_, Element.copy_attr_consistent, replace,
883 and_source)
885 def update_all_atts_concatenating(self, dict_, replace=True,
886 and_source=False):
887 """
888 Updates all attributes from node or dictionary `dict_`.
890 Appends the basic attributes ('ids', 'names', 'classes',
891 'dupnames', but not 'source') and then, for all other attributes in
892 dict_, updates the same attribute in self. When attributes with the
893 same identifier appear in both self and dict_ whose values aren't each
894 lists and replace is True, the values in self are replaced with the
895 values in dict_; if the values from self and dict_ for the given
896 identifier are both of list type, then the two lists are concatenated
897 and the result stored in self; otherwise, the values in self are
898 preserved. When and_source is True, the 'source' attribute is
899 included in the copy.
901 NOTE: When replace is False, and self contains a 'source' attribute,
902 'source' is not replaced even when dict_ has a 'source'
903 attribute, though it may still be merged into a list depending
904 on the value of update_fun.
905 """
906 self.update_all_atts(dict_, Element.copy_attr_concatenate, replace,
907 and_source)
909 def update_all_atts_coercion(self, dict_, replace=True,
910 and_source=False):
911 """
912 Updates all attributes from node or dictionary `dict_`.
914 Appends the basic attributes ('ids', 'names', 'classes',
915 'dupnames', but not 'source') and then, for all other attributes in
916 dict_, updates the same attribute in self. When attributes with the
917 same identifier appear in both self and dict_ whose values are both
918 not lists and replace is True, the values in self are replaced with
919 the values in dict_; if either of the values from self and dict_ for
920 the given identifier are of list type, then first any non-lists are
921 converted to 1-element lists and then the two lists are concatenated
922 and the result stored in self; otherwise, the values in self are
923 preserved. When and_source is True, the 'source' attribute is
924 included in the copy.
926 NOTE: When replace is False, and self contains a 'source' attribute,
927 'source' is not replaced even when dict_ has a 'source'
928 attribute, though it may still be merged into a list depending
929 on the value of update_fun.
930 """
931 self.update_all_atts(dict_, Element.copy_attr_coerce, replace,
932 and_source)
934 def update_all_atts_convert(self, dict_, and_source=False):
935 """
936 Updates all attributes from node or dictionary `dict_`.
938 Appends the basic attributes ('ids', 'names', 'classes',
939 'dupnames', but not 'source') and then, for all other attributes in
940 dict_, updates the same attribute in self. When attributes with the
941 same identifier appear in both self and dict_ then first any non-lists
942 are converted to 1-element lists and then the two lists are
943 concatenated and the result stored in self; otherwise, the values in
944 self are preserved. When and_source is True, the 'source' attribute
945 is included in the copy.
947 NOTE: When replace is False, and self contains a 'source' attribute,
948 'source' is not replaced even when dict_ has a 'source'
949 attribute, though it may still be merged into a list depending
950 on the value of update_fun.
951 """
952 self.update_all_atts(dict_, Element.copy_attr_convert,
953 and_source=and_source)
955 def clear(self):
956 self.children = []
958 def replace(self, old, new):
959 """Replace one child `Node` with another child or children."""
960 index = self.index(old)
961 if isinstance(new, Node):
962 self.setup_child(new)
963 self[index] = new
964 elif new is not None:
965 self[index:index+1] = new
967 def replace_self(self, new):
968 """
969 Replace `self` node with `new`, where `new` is a node or a
970 list of nodes.
971 """
972 update = new
973 if not isinstance(new, Node):
974 # `new` is a list; update first child.
975 try:
976 update = new[0]
977 except IndexError:
978 update = None
979 if isinstance(update, Element):
980 update.update_basic_atts(self)
981 else:
982 # `update` is a Text node or `new` is an empty list.
983 # Assert that we aren't losing any attributes.
984 for att in self.basic_attributes:
985 assert not self[att], \
986 'Losing "%s" attribute: %s' % (att, self[att])
987 self.parent.replace(self, new)
989 def first_child_matching_class(self, childclass, start=0, end=sys.maxsize):
990 """
991 Return the index of the first child whose class exactly matches.
993 Parameters:
995 - `childclass`: A `Node` subclass to search for, or a tuple of `Node`
996 classes. If a tuple, any of the classes may match.
997 - `start`: Initial index to check.
998 - `end`: Initial index to *not* check.
999 """
1000 if not isinstance(childclass, tuple):
1001 childclass = (childclass,)
1002 for index in range(start, min(len(self), end)):
1003 for c in childclass:
1004 if isinstance(self[index], c):
1005 return index
1006 return None
1008 def first_child_not_matching_class(self, childclass, start=0,
1009 end=sys.maxsize):
1010 """
1011 Return the index of the first child whose class does *not* match.
1013 Parameters:
1015 - `childclass`: A `Node` subclass to skip, or a tuple of `Node`
1016 classes. If a tuple, none of the classes may match.
1017 - `start`: Initial index to check.
1018 - `end`: Initial index to *not* check.
1019 """
1020 if not isinstance(childclass, tuple):
1021 childclass = (childclass,)
1022 for index in range(start, min(len(self), end)):
1023 for c in childclass:
1024 if isinstance(self.children[index], c):
1025 break
1026 else:
1027 return index
1028 return None
1030 def pformat(self, indent=' ', level=0):
1031 tagline = '%s%s\n' % (indent*level, self.starttag())
1032 childreps = (c.pformat(indent, level+1) for c in self.children)
1033 return ''.join((tagline, *childreps))
1035 def copy(self):
1036 obj = self.__class__(rawsource=self.rawsource, **self.attributes)
1037 obj._document = self._document
1038 obj.source = self.source
1039 obj.line = self.line
1040 return obj
1042 def deepcopy(self):
1043 copy = self.copy()
1044 copy.extend([child.deepcopy() for child in self.children])
1045 return copy
1047 def set_class(self, name):
1048 """Add a new class to the "classes" attribute."""
1049 warnings.warn('docutils.nodes.Element.set_class() is deprecated; '
1050 ' and will be removed in Docutils 0.21 or later.'
1051 "Append to Element['classes'] list attribute directly",
1052 DeprecationWarning, stacklevel=2)
1053 assert ' ' not in name
1054 self['classes'].append(name.lower())
1056 def note_referenced_by(self, name=None, id=None):
1057 """Note that this Element has been referenced by its name
1058 `name` or id `id`."""
1059 self.referenced = 1
1060 # Element.expect_referenced_by_* dictionaries map names or ids
1061 # to nodes whose ``referenced`` attribute is set to true as
1062 # soon as this node is referenced by the given name or id.
1063 # Needed for target propagation.
1064 by_name = getattr(self, 'expect_referenced_by_name', {}).get(name)
1065 by_id = getattr(self, 'expect_referenced_by_id', {}).get(id)
1066 if by_name:
1067 assert name is not None
1068 by_name.referenced = 1
1069 if by_id:
1070 assert id is not None
1071 by_id.referenced = 1
1073 @classmethod
1074 def is_not_list_attribute(cls, attr):
1075 """
1076 Returns True if and only if the given attribute is NOT one of the
1077 basic list attributes defined for all Elements.
1078 """
1079 return attr not in cls.list_attributes
1081 @classmethod
1082 def is_not_known_attribute(cls, attr):
1083 """
1084 Returns True if and only if the given attribute is NOT recognized by
1085 this class.
1086 """
1087 return attr not in cls.known_attributes
1090class TextElement(Element):
1092 """
1093 An element which directly contains text.
1095 Its children are all `Text` or `Inline` subclass nodes. You can
1096 check whether an element's context is inline simply by checking whether
1097 its immediate parent is a `TextElement` instance (including subclasses).
1098 This is handy for nodes like `image` that can appear both inline and as
1099 standalone body elements.
1101 If passing children to `__init__()`, make sure to set `text` to
1102 ``''`` or some other suitable value.
1103 """
1105 child_text_separator = ''
1106 """Separator for child nodes, used by `astext()` method."""
1108 def __init__(self, rawsource='', text='', *children, **attributes):
1109 if text != '':
1110 textnode = Text(text)
1111 Element.__init__(self, rawsource, textnode, *children,
1112 **attributes)
1113 else:
1114 Element.__init__(self, rawsource, *children, **attributes)
1117class FixedTextElement(TextElement):
1119 """An element which directly contains preformatted text."""
1121 def __init__(self, rawsource='', text='', *children, **attributes):
1122 super().__init__(rawsource, text, *children, **attributes)
1123 self.attributes['xml:space'] = 'preserve'
1126# TODO: PureTextElement(TextElement):
1127# """An element which only contains text, no children."""
1128# For elements in the DTD that directly employ #PCDATA in their definition:
1129# citation_reference, comment, footnote_reference, label, math, math_block,
1130# option_argument, option_string, raw,
1133# ========
1134# Mixins
1135# ========
1137class Resolvable:
1139 resolved = 0
1142class BackLinkable:
1144 def add_backref(self, refid):
1145 self['backrefs'].append(refid)
1148# ====================
1149# Element Categories
1150# ====================
1152class Root:
1153 pass
1156class Titular:
1157 pass
1160class PreBibliographic:
1161 """Category of Node which may occur before Bibliographic Nodes."""
1164class Bibliographic:
1165 pass
1168class Decorative(PreBibliographic):
1169 pass
1172class Structural:
1173 pass
1176class Body:
1177 pass
1180class General(Body):
1181 pass
1184class Sequential(Body):
1185 """List-like elements."""
1188class Admonition(Body): pass
1191class Special(Body):
1192 """Special internal body elements."""
1195class Invisible(PreBibliographic):
1196 """Internal elements that don't appear in output."""
1199class Part:
1200 pass
1203class Inline:
1204 pass
1207class Referential(Resolvable):
1208 pass
1211class Targetable(Resolvable):
1213 referenced = 0
1215 indirect_reference_name = None
1216 """Holds the whitespace_normalized_name (contains mixed case) of a target.
1217 Required for MoinMoin/reST compatibility."""
1220class Labeled:
1221 """Contains a `label` as its first element."""
1224# ==============
1225# Root Element
1226# ==============
1228class document(Root, Structural, Element):
1230 """
1231 The document root element.
1233 Do not instantiate this class directly; use
1234 `docutils.utils.new_document()` instead.
1235 """
1237 def __init__(self, settings, reporter, *args, **kwargs):
1238 Element.__init__(self, *args, **kwargs)
1240 self.current_source = None
1241 """Path to or description of the input source being processed."""
1243 self.current_line = None
1244 """Line number (1-based) of `current_source`."""
1246 self.settings = settings
1247 """Runtime settings data record."""
1249 self.reporter = reporter
1250 """System message generator."""
1252 self.indirect_targets = []
1253 """List of indirect target nodes."""
1255 self.substitution_defs = {}
1256 """Mapping of substitution names to substitution_definition nodes."""
1258 self.substitution_names = {}
1259 """Mapping of case-normalized substitution names to case-sensitive
1260 names."""
1262 self.refnames = {}
1263 """Mapping of names to lists of referencing nodes."""
1265 self.refids = {}
1266 """Mapping of ids to lists of referencing nodes."""
1268 self.nameids = {}
1269 """Mapping of names to unique id's."""
1271 self.nametypes = {}
1272 """Mapping of names to hyperlink type (boolean: True => explicit,
1273 False => implicit."""
1275 self.ids = {}
1276 """Mapping of ids to nodes."""
1278 self.footnote_refs = {}
1279 """Mapping of footnote labels to lists of footnote_reference nodes."""
1281 self.citation_refs = {}
1282 """Mapping of citation labels to lists of citation_reference nodes."""
1284 self.autofootnotes = []
1285 """List of auto-numbered footnote nodes."""
1287 self.autofootnote_refs = []
1288 """List of auto-numbered footnote_reference nodes."""
1290 self.symbol_footnotes = []
1291 """List of symbol footnote nodes."""
1293 self.symbol_footnote_refs = []
1294 """List of symbol footnote_reference nodes."""
1296 self.footnotes = []
1297 """List of manually-numbered footnote nodes."""
1299 self.citations = []
1300 """List of citation nodes."""
1302 self.autofootnote_start = 1
1303 """Initial auto-numbered footnote number."""
1305 self.symbol_footnote_start = 0
1306 """Initial symbol footnote symbol index."""
1308 self.id_counter = Counter()
1309 """Numbers added to otherwise identical IDs."""
1311 self.parse_messages = []
1312 """System messages generated while parsing."""
1314 self.transform_messages = []
1315 """System messages generated while applying transforms."""
1317 import docutils.transforms
1318 self.transformer = docutils.transforms.Transformer(self)
1319 """Storage for transforms to be applied to this document."""
1321 self.include_log = []
1322 """The current source's parents (to detect inclusion loops)."""
1324 self.decoration = None
1325 """Document's `decoration` node."""
1327 self._document = self
1329 def __getstate__(self):
1330 """
1331 Return dict with unpicklable references removed.
1332 """
1333 state = self.__dict__.copy()
1334 state['reporter'] = None
1335 state['transformer'] = None
1336 return state
1338 def asdom(self, dom=None):
1339 """Return a DOM representation of this document."""
1340 if dom is None:
1341 import xml.dom.minidom as dom
1342 domroot = dom.Document()
1343 domroot.appendChild(self._dom_node(domroot))
1344 return domroot
1346 def set_id(self, node, msgnode=None, suggested_prefix=''):
1347 if node['ids']:
1348 # register and check for duplicates
1349 for id in node['ids']:
1350 self.ids.setdefault(id, node)
1351 if self.ids[id] is not node:
1352 msg = self.reporter.severe('Duplicate ID: "%s".' % id)
1353 if msgnode is not None:
1354 msgnode += msg
1355 return id
1356 # generate and set id
1357 id_prefix = self.settings.id_prefix
1358 auto_id_prefix = self.settings.auto_id_prefix
1359 base_id = ''
1360 id = ''
1361 for name in node['names']:
1362 if id_prefix:
1363 # allow names starting with numbers if `id_prefix`
1364 base_id = make_id('x'+name)[1:]
1365 else:
1366 base_id = make_id(name)
1367 # TODO: normalize id-prefix? (would make code simpler)
1368 id = id_prefix + base_id
1369 if base_id and id not in self.ids:
1370 break
1371 else:
1372 if base_id and auto_id_prefix.endswith('%'):
1373 # disambiguate name-derived ID
1374 # TODO: remove second condition after announcing change
1375 prefix = id + '-'
1376 else:
1377 prefix = id_prefix + auto_id_prefix
1378 if prefix.endswith('%'):
1379 prefix = '%s%s-' % (prefix[:-1],
1380 suggested_prefix
1381 or make_id(node.tagname))
1382 while True:
1383 self.id_counter[prefix] += 1
1384 id = '%s%d' % (prefix, self.id_counter[prefix])
1385 if id not in self.ids:
1386 break
1387 node['ids'].append(id)
1388 self.ids[id] = node
1389 return id
1391 def set_name_id_map(self, node, id, msgnode=None, explicit=None):
1392 """
1393 `self.nameids` maps names to IDs, while `self.nametypes` maps names to
1394 booleans representing hyperlink type (True==explicit,
1395 False==implicit). This method updates the mappings.
1397 The following state transition table shows how `self.nameids` items
1398 ("id") and `self.nametypes` items ("type") change with new input
1399 (a call to this method), and what actions are performed
1400 ("implicit"-type system messages are INFO/1, and
1401 "explicit"-type system messages are ERROR/3):
1403 ==== ===== ======== ======== ======= ==== ===== =====
1404 Old State Input Action New State Notes
1405 ----------- -------- ----------------- ----------- -----
1406 id type new type sys.msg. dupname id type
1407 ==== ===== ======== ======== ======= ==== ===== =====
1408 - - explicit - - new True
1409 - - implicit - - new False
1410 - False explicit - - new True
1411 old False explicit implicit old new True
1412 - True explicit explicit new - True
1413 old True explicit explicit new,old - True [#]_
1414 - False implicit implicit new - False
1415 old False implicit implicit new,old - False
1416 - True implicit implicit new - True
1417 old True implicit implicit new old True
1418 ==== ===== ======== ======== ======= ==== ===== =====
1420 .. [#] Do not clear the name-to-id map or invalidate the old target if
1421 both old and new targets are external and refer to identical URIs.
1422 The new target is invalidated regardless.
1423 """
1424 for name in tuple(node['names']):
1425 if name in self.nameids:
1426 self.set_duplicate_name_id(node, id, name, msgnode, explicit)
1427 # attention: modifies node['names']
1428 else:
1429 self.nameids[name] = id
1430 self.nametypes[name] = explicit
1432 def set_duplicate_name_id(self, node, id, name, msgnode, explicit):
1433 old_id = self.nameids[name]
1434 old_explicit = self.nametypes[name]
1435 self.nametypes[name] = old_explicit or explicit
1436 if explicit:
1437 if old_explicit:
1438 level = 2
1439 if old_id is not None:
1440 old_node = self.ids[old_id]
1441 if 'refuri' in node:
1442 refuri = node['refuri']
1443 if (old_node['names']
1444 and 'refuri' in old_node
1445 and old_node['refuri'] == refuri):
1446 level = 1 # just inform if refuri's identical
1447 if level > 1:
1448 dupname(old_node, name)
1449 self.nameids[name] = None
1450 msg = self.reporter.system_message(
1451 level, 'Duplicate explicit target name: "%s".' % name,
1452 backrefs=[id], base_node=node)
1453 if msgnode is not None:
1454 msgnode += msg
1455 dupname(node, name)
1456 else:
1457 self.nameids[name] = id
1458 if old_id is not None:
1459 old_node = self.ids[old_id]
1460 dupname(old_node, name)
1461 else:
1462 if old_id is not None and not old_explicit:
1463 self.nameids[name] = None
1464 old_node = self.ids[old_id]
1465 dupname(old_node, name)
1466 dupname(node, name)
1467 if not explicit or (not old_explicit and old_id is not None):
1468 msg = self.reporter.info(
1469 'Duplicate implicit target name: "%s".' % name,
1470 backrefs=[id], base_node=node)
1471 if msgnode is not None:
1472 msgnode += msg
1474 def has_name(self, name):
1475 return name in self.nameids
1477 # "note" here is an imperative verb: "take note of".
1478 def note_implicit_target(self, target, msgnode=None):
1479 id = self.set_id(target, msgnode)
1480 self.set_name_id_map(target, id, msgnode, explicit=False)
1482 def note_explicit_target(self, target, msgnode=None):
1483 id = self.set_id(target, msgnode)
1484 self.set_name_id_map(target, id, msgnode, explicit=True)
1486 def note_refname(self, node):
1487 self.refnames.setdefault(node['refname'], []).append(node)
1489 def note_refid(self, node):
1490 self.refids.setdefault(node['refid'], []).append(node)
1492 def note_indirect_target(self, target):
1493 self.indirect_targets.append(target)
1494 if target['names']:
1495 self.note_refname(target)
1497 def note_anonymous_target(self, target):
1498 self.set_id(target)
1500 def note_autofootnote(self, footnote):
1501 self.set_id(footnote)
1502 self.autofootnotes.append(footnote)
1504 def note_autofootnote_ref(self, ref):
1505 self.set_id(ref)
1506 self.autofootnote_refs.append(ref)
1508 def note_symbol_footnote(self, footnote):
1509 self.set_id(footnote)
1510 self.symbol_footnotes.append(footnote)
1512 def note_symbol_footnote_ref(self, ref):
1513 self.set_id(ref)
1514 self.symbol_footnote_refs.append(ref)
1516 def note_footnote(self, footnote):
1517 self.set_id(footnote)
1518 self.footnotes.append(footnote)
1520 def note_footnote_ref(self, ref):
1521 self.set_id(ref)
1522 self.footnote_refs.setdefault(ref['refname'], []).append(ref)
1523 self.note_refname(ref)
1525 def note_citation(self, citation):
1526 self.citations.append(citation)
1528 def note_citation_ref(self, ref):
1529 self.set_id(ref)
1530 self.citation_refs.setdefault(ref['refname'], []).append(ref)
1531 self.note_refname(ref)
1533 def note_substitution_def(self, subdef, def_name, msgnode=None):
1534 name = whitespace_normalize_name(def_name)
1535 if name in self.substitution_defs:
1536 msg = self.reporter.error(
1537 'Duplicate substitution definition name: "%s".' % name,
1538 base_node=subdef)
1539 if msgnode is not None:
1540 msgnode += msg
1541 oldnode = self.substitution_defs[name]
1542 dupname(oldnode, name)
1543 # keep only the last definition:
1544 self.substitution_defs[name] = subdef
1545 # case-insensitive mapping:
1546 self.substitution_names[fully_normalize_name(name)] = name
1548 def note_substitution_ref(self, subref, refname):
1549 subref['refname'] = whitespace_normalize_name(refname)
1551 def note_pending(self, pending, priority=None):
1552 self.transformer.add_pending(pending, priority)
1554 def note_parse_message(self, message):
1555 self.parse_messages.append(message)
1557 def note_transform_message(self, message):
1558 self.transform_messages.append(message)
1560 def note_source(self, source, offset):
1561 self.current_source = source
1562 if offset is None:
1563 self.current_line = offset
1564 else:
1565 self.current_line = offset + 1
1567 def copy(self):
1568 obj = self.__class__(self.settings, self.reporter,
1569 **self.attributes)
1570 obj.source = self.source
1571 obj.line = self.line
1572 return obj
1574 def get_decoration(self):
1575 if not self.decoration:
1576 self.decoration = decoration()
1577 index = self.first_child_not_matching_class((Titular, meta))
1578 if index is None:
1579 self.append(self.decoration)
1580 else:
1581 self.insert(index, self.decoration)
1582 return self.decoration
1585# ================
1586# Title Elements
1587# ================
1589class title(Titular, PreBibliographic, TextElement): pass
1590class subtitle(Titular, PreBibliographic, TextElement): pass
1591class rubric(Titular, TextElement): pass
1594# ==================
1595# Meta-Data Element
1596# ==================
1598class meta(PreBibliographic, Element):
1599 """Container for "invisible" bibliographic data, or meta-data."""
1602# ========================
1603# Bibliographic Elements
1604# ========================
1606class docinfo(Bibliographic, Element): pass
1607class author(Bibliographic, TextElement): pass
1608class authors(Bibliographic, Element): pass
1609class organization(Bibliographic, TextElement): pass
1610class address(Bibliographic, FixedTextElement): pass
1611class contact(Bibliographic, TextElement): pass
1612class version(Bibliographic, TextElement): pass
1613class revision(Bibliographic, TextElement): pass
1614class status(Bibliographic, TextElement): pass
1615class date(Bibliographic, TextElement): pass
1616class copyright(Bibliographic, TextElement): pass
1619# =====================
1620# Decorative Elements
1621# =====================
1623class decoration(Decorative, Element):
1625 def get_header(self):
1626 if not len(self.children) or not isinstance(self.children[0], header):
1627 self.insert(0, header())
1628 return self.children[0]
1630 def get_footer(self):
1631 if not len(self.children) or not isinstance(self.children[-1], footer):
1632 self.append(footer())
1633 return self.children[-1]
1636class header(Decorative, Element): pass
1637class footer(Decorative, Element): pass
1640# =====================
1641# Structural Elements
1642# =====================
1644class section(Structural, Element): pass
1647class topic(Structural, Element):
1649 """
1650 Topics are terminal, "leaf" mini-sections, like block quotes with titles,
1651 or textual figures. A topic is just like a section, except that it has no
1652 subsections, and it doesn't have to conform to section placement rules.
1654 Topics are allowed wherever body elements (list, table, etc.) are allowed,
1655 but only at the top level of a section or document. Topics cannot nest
1656 inside topics, sidebars, or body elements; you can't have a topic inside a
1657 table, list, block quote, etc.
1658 """
1661class sidebar(Structural, Element):
1663 """
1664 Sidebars are like miniature, parallel documents that occur inside other
1665 documents, providing related or reference material. A sidebar is
1666 typically offset by a border and "floats" to the side of the page; the
1667 document's main text may flow around it. Sidebars can also be likened to
1668 super-footnotes; their content is outside of the flow of the document's
1669 main text.
1671 Sidebars are allowed wherever body elements (list, table, etc.) are
1672 allowed, but only at the top level of a section or document. Sidebars
1673 cannot nest inside sidebars, topics, or body elements; you can't have a
1674 sidebar inside a table, list, block quote, etc.
1675 """
1678class transition(Structural, Element): pass
1681# ===============
1682# Body Elements
1683# ===============
1685class paragraph(General, TextElement): pass
1686class compound(General, Element): pass
1687class container(General, Element): pass
1688class bullet_list(Sequential, Element): pass
1689class enumerated_list(Sequential, Element): pass
1690class list_item(Part, Element): pass
1691class definition_list(Sequential, Element): pass
1692class definition_list_item(Part, Element): pass
1693class term(Part, TextElement): pass
1694class classifier(Part, TextElement): pass
1695class definition(Part, Element): pass
1696class field_list(Sequential, Element): pass
1697class field(Part, Element): pass
1698class field_name(Part, TextElement): pass
1699class field_body(Part, Element): pass
1702class option(Part, Element):
1704 child_text_separator = ''
1707class option_argument(Part, TextElement):
1709 def astext(self):
1710 return self.get('delimiter', ' ') + TextElement.astext(self)
1713class option_group(Part, Element):
1715 child_text_separator = ', '
1718class option_list(Sequential, Element): pass
1721class option_list_item(Part, Element):
1723 child_text_separator = ' '
1726class option_string(Part, TextElement): pass
1727class description(Part, Element): pass
1728class literal_block(General, FixedTextElement): pass
1729class doctest_block(General, FixedTextElement): pass
1730class math_block(General, FixedTextElement): pass
1731class line_block(General, Element): pass
1734class line(Part, TextElement):
1736 indent = None
1739class block_quote(General, Element): pass
1740class attribution(Part, TextElement): pass
1741class attention(Admonition, Element): pass
1742class caution(Admonition, Element): pass
1743class danger(Admonition, Element): pass
1744class error(Admonition, Element): pass
1745class important(Admonition, Element): pass
1746class note(Admonition, Element): pass
1747class tip(Admonition, Element): pass
1748class hint(Admonition, Element): pass
1749class warning(Admonition, Element): pass
1750class admonition(Admonition, Element): pass
1751class comment(Special, Invisible, FixedTextElement): pass
1752class substitution_definition(Special, Invisible, TextElement): pass
1753class target(Special, Invisible, Inline, TextElement, Targetable): pass
1754class footnote(General, BackLinkable, Element, Labeled, Targetable): pass
1755class citation(General, BackLinkable, Element, Labeled, Targetable): pass
1756class label(Part, TextElement): pass
1757class figure(General, Element): pass
1758class caption(Part, TextElement): pass
1759class legend(Part, Element): pass
1760class table(General, Element): pass
1761class tgroup(Part, Element): pass
1762class colspec(Part, Element): pass
1763class thead(Part, Element): pass
1764class tbody(Part, Element): pass
1765class row(Part, Element): pass
1766class entry(Part, Element): pass
1769class system_message(Special, BackLinkable, PreBibliographic, Element):
1771 """
1772 System message element.
1774 Do not instantiate this class directly; use
1775 ``document.reporter.info/warning/error/severe()`` instead.
1776 """
1778 def __init__(self, message=None, *children, **attributes):
1779 rawsource = attributes.pop('rawsource', '')
1780 if message:
1781 p = paragraph('', message)
1782 children = (p,) + children
1783 try:
1784 Element.__init__(self, rawsource, *children, **attributes)
1785 except: # noqa catchall
1786 print('system_message: children=%r' % (children,))
1787 raise
1789 def astext(self):
1790 line = self.get('line', '')
1791 return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
1792 self['level'], Element.astext(self))
1795class pending(Special, Invisible, Element):
1797 """
1798 The "pending" element is used to encapsulate a pending operation: the
1799 operation (transform), the point at which to apply it, and any data it
1800 requires. Only the pending operation's location within the document is
1801 stored in the public document tree (by the "pending" object itself); the
1802 operation and its data are stored in the "pending" object's internal
1803 instance attributes.
1805 For example, say you want a table of contents in your reStructuredText
1806 document. The easiest way to specify where to put it is from within the
1807 document, with a directive::
1809 .. contents::
1811 But the "contents" directive can't do its work until the entire document
1812 has been parsed and possibly transformed to some extent. So the directive
1813 code leaves a placeholder behind that will trigger the second phase of its
1814 processing, something like this::
1816 <pending ...public attributes...> + internal attributes
1818 Use `document.note_pending()` so that the
1819 `docutils.transforms.Transformer` stage of processing can run all pending
1820 transforms.
1821 """
1823 def __init__(self, transform, details=None,
1824 rawsource='', *children, **attributes):
1825 Element.__init__(self, rawsource, *children, **attributes)
1827 self.transform = transform
1828 """The `docutils.transforms.Transform` class implementing the pending
1829 operation."""
1831 self.details = details or {}
1832 """Detail data (dictionary) required by the pending operation."""
1834 def pformat(self, indent=' ', level=0):
1835 internals = ['.. internal attributes:',
1836 ' .transform: %s.%s' % (self.transform.__module__,
1837 self.transform.__name__),
1838 ' .details:']
1839 details = sorted(self.details.items())
1840 for key, value in details:
1841 if isinstance(value, Node):
1842 internals.append('%7s%s:' % ('', key))
1843 internals.extend(['%9s%s' % ('', line)
1844 for line in value.pformat().splitlines()])
1845 elif (value
1846 and isinstance(value, list)
1847 and isinstance(value[0], Node)):
1848 internals.append('%7s%s:' % ('', key))
1849 for v in value:
1850 internals.extend(['%9s%s' % ('', line)
1851 for line in v.pformat().splitlines()])
1852 else:
1853 internals.append('%7s%s: %r' % ('', key, value))
1854 return (Element.pformat(self, indent, level)
1855 + ''.join((' %s%s\n' % (indent * level, line))
1856 for line in internals))
1858 def copy(self):
1859 obj = self.__class__(self.transform, self.details, self.rawsource,
1860 **self.attributes)
1861 obj._document = self._document
1862 obj.source = self.source
1863 obj.line = self.line
1864 return obj
1867class raw(Special, Inline, PreBibliographic, FixedTextElement):
1869 """
1870 Raw data that is to be passed untouched to the Writer.
1871 """
1874# =================
1875# Inline Elements
1876# =================
1878class emphasis(Inline, TextElement): pass
1879class strong(Inline, TextElement): pass
1880class literal(Inline, TextElement): pass
1881class reference(General, Inline, Referential, TextElement): pass
1882class footnote_reference(Inline, Referential, TextElement): pass
1883class citation_reference(Inline, Referential, TextElement): pass
1884class substitution_reference(Inline, TextElement): pass
1885class title_reference(Inline, TextElement): pass
1886class abbreviation(Inline, TextElement): pass
1887class acronym(Inline, TextElement): pass
1888class superscript(Inline, TextElement): pass
1889class subscript(Inline, TextElement): pass
1890class math(Inline, TextElement): pass
1893class image(General, Inline, Element):
1895 def astext(self):
1896 return self.get('alt', '')
1899class inline(Inline, TextElement): pass
1900class problematic(Inline, TextElement): pass
1901class generated(Inline, TextElement): pass
1904# ========================================
1905# Auxiliary Classes, Functions, and Data
1906# ========================================
1908node_class_names = """
1909 Text
1910 abbreviation acronym address admonition attention attribution author
1911 authors
1912 block_quote bullet_list
1913 caption caution citation citation_reference classifier colspec comment
1914 compound contact container copyright
1915 danger date decoration definition definition_list definition_list_item
1916 description docinfo doctest_block document
1917 emphasis entry enumerated_list error
1918 field field_body field_list field_name figure footer
1919 footnote footnote_reference
1920 generated
1921 header hint
1922 image important inline
1923 label legend line line_block list_item literal literal_block
1924 math math_block meta
1925 note
1926 option option_argument option_group option_list option_list_item
1927 option_string organization
1928 paragraph pending problematic
1929 raw reference revision row rubric
1930 section sidebar status strong subscript substitution_definition
1931 substitution_reference subtitle superscript system_message
1932 table target tbody term tgroup thead tip title title_reference topic
1933 transition
1934 version
1935 warning""".split()
1936"""A list of names of all concrete Node subclasses."""
1939class NodeVisitor:
1941 """
1942 "Visitor" pattern [GoF95]_ abstract superclass implementation for
1943 document tree traversals.
1945 Each node class has corresponding methods, doing nothing by
1946 default; override individual methods for specific and useful
1947 behaviour. The `dispatch_visit()` method is called by
1948 `Node.walk()` upon entering a node. `Node.walkabout()` also calls
1949 the `dispatch_departure()` method before exiting a node.
1951 The dispatch methods call "``visit_`` + node class name" or
1952 "``depart_`` + node class name", resp.
1954 This is a base class for visitors whose ``visit_...`` & ``depart_...``
1955 methods must be implemented for *all* compulsory node types encountered
1956 (such as for `docutils.writers.Writer` subclasses).
1957 Unimplemented methods will raise exceptions (except for optional nodes).
1959 For sparse traversals, where only certain node types are of interest, use
1960 subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform
1961 processing is desired, subclass `GenericNodeVisitor`.
1963 .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
1964 Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
1965 1995.
1966 """
1968 optional = ('meta',)
1969 """
1970 Tuple containing node class names (as strings).
1972 No exception will be raised if writers do not implement visit
1973 or departure functions for these node classes.
1975 Used to ensure transitional compatibility with existing 3rd-party writers.
1976 """
1978 def __init__(self, document):
1979 self.document = document
1981 def dispatch_visit(self, node):
1982 """
1983 Call self."``visit_`` + node class name" with `node` as
1984 parameter. If the ``visit_...`` method does not exist, call
1985 self.unknown_visit.
1986 """
1987 node_name = node.__class__.__name__
1988 method = getattr(self, 'visit_' + node_name, self.unknown_visit)
1989 self.document.reporter.debug(
1990 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s'
1991 % (method.__name__, node_name))
1992 return method(node)
1994 def dispatch_departure(self, node):
1995 """
1996 Call self."``depart_`` + node class name" with `node` as
1997 parameter. If the ``depart_...`` method does not exist, call
1998 self.unknown_departure.
1999 """
2000 node_name = node.__class__.__name__
2001 method = getattr(self, 'depart_' + node_name, self.unknown_departure)
2002 self.document.reporter.debug(
2003 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s'
2004 % (method.__name__, node_name))
2005 return method(node)
2007 def unknown_visit(self, node):
2008 """
2009 Called when entering unknown `Node` types.
2011 Raise an exception unless overridden.
2012 """
2013 if (self.document.settings.strict_visitor
2014 or node.__class__.__name__ not in self.optional):
2015 raise NotImplementedError(
2016 '%s visiting unknown node type: %s'
2017 % (self.__class__, node.__class__.__name__))
2019 def unknown_departure(self, node):
2020 """
2021 Called before exiting unknown `Node` types.
2023 Raise exception unless overridden.
2024 """
2025 if (self.document.settings.strict_visitor
2026 or node.__class__.__name__ not in self.optional):
2027 raise NotImplementedError(
2028 '%s departing unknown node type: %s'
2029 % (self.__class__, node.__class__.__name__))
2032class SparseNodeVisitor(NodeVisitor):
2034 """
2035 Base class for sparse traversals, where only certain node types are of
2036 interest. When ``visit_...`` & ``depart_...`` methods should be
2037 implemented for *all* node types (such as for `docutils.writers.Writer`
2038 subclasses), subclass `NodeVisitor` instead.
2039 """
2042class GenericNodeVisitor(NodeVisitor):
2044 """
2045 Generic "Visitor" abstract superclass, for simple traversals.
2047 Unless overridden, each ``visit_...`` method calls `default_visit()`, and
2048 each ``depart_...`` method (when using `Node.walkabout()`) calls
2049 `default_departure()`. `default_visit()` (and `default_departure()`) must
2050 be overridden in subclasses.
2052 Define fully generic visitors by overriding `default_visit()` (and
2053 `default_departure()`) only. Define semi-generic visitors by overriding
2054 individual ``visit_...()`` (and ``depart_...()``) methods also.
2056 `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should
2057 be overridden for default behavior.
2058 """
2060 def default_visit(self, node):
2061 """Override for generic, uniform traversals."""
2062 raise NotImplementedError
2064 def default_departure(self, node):
2065 """Override for generic, uniform traversals."""
2066 raise NotImplementedError
2069def _call_default_visit(self, node):
2070 self.default_visit(node)
2073def _call_default_departure(self, node):
2074 self.default_departure(node)
2077def _nop(self, node):
2078 pass
2081def _add_node_class_names(names):
2082 """Save typing with dynamic assignments:"""
2083 for _name in names:
2084 setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
2085 setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
2086 setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
2087 setattr(SparseNodeVisitor, 'depart_' + _name, _nop)
2090_add_node_class_names(node_class_names)
2093class TreeCopyVisitor(GenericNodeVisitor):
2095 """
2096 Make a complete copy of a tree or branch, including element attributes.
2097 """
2099 def __init__(self, document):
2100 GenericNodeVisitor.__init__(self, document)
2101 self.parent_stack = []
2102 self.parent = []
2104 def get_tree_copy(self):
2105 return self.parent[0]
2107 def default_visit(self, node):
2108 """Copy the current node, and make it the new acting parent."""
2109 newnode = node.copy()
2110 self.parent.append(newnode)
2111 self.parent_stack.append(self.parent)
2112 self.parent = newnode
2114 def default_departure(self, node):
2115 """Restore the previous acting parent."""
2116 self.parent = self.parent_stack.pop()
2119class TreePruningException(Exception):
2121 """
2122 Base class for `NodeVisitor`-related tree pruning exceptions.
2124 Raise subclasses from within ``visit_...`` or ``depart_...`` methods
2125 called from `Node.walk()` and `Node.walkabout()` tree traversals to prune
2126 the tree traversed.
2127 """
2130class SkipChildren(TreePruningException):
2132 """
2133 Do not visit any children of the current node. The current node's
2134 siblings and ``depart_...`` method are not affected.
2135 """
2138class SkipSiblings(TreePruningException):
2140 """
2141 Do not visit any more siblings (to the right) of the current node. The
2142 current node's children and its ``depart_...`` method are not affected.
2143 """
2146class SkipNode(TreePruningException):
2148 """
2149 Do not visit the current node's children, and do not call the current
2150 node's ``depart_...`` method.
2151 """
2154class SkipDeparture(TreePruningException):
2156 """
2157 Do not call the current node's ``depart_...`` method. The current node's
2158 children and siblings are not affected.
2159 """
2162class NodeFound(TreePruningException):
2164 """
2165 Raise to indicate that the target of a search has been found. This
2166 exception must be caught by the client; it is not caught by the traversal
2167 code.
2168 """
2171class StopTraversal(TreePruningException):
2173 """
2174 Stop the traversal altogether. The current node's ``depart_...`` method
2175 is not affected. The parent nodes ``depart_...`` methods are also called
2176 as usual. No other nodes are visited. This is an alternative to
2177 NodeFound that does not cause exception handling to trickle up to the
2178 caller.
2179 """
2182def make_id(string):
2183 """
2184 Convert `string` into an identifier and return it.
2186 Docutils identifiers will conform to the regular expression
2187 ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class"
2188 and "id" attributes) should have no underscores, colons, or periods.
2189 Hyphens may be used.
2191 - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
2193 ID and NAME tokens must begin with a letter ([A-Za-z]) and may be
2194 followed by any number of letters, digits ([0-9]), hyphens ("-"),
2195 underscores ("_"), colons (":"), and periods (".").
2197 - However the `CSS1 spec`_ defines identifiers based on the "name" token,
2198 a tighter interpretation ("flex" tokenizer notation; "latin1" and
2199 "escape" 8-bit characters have been replaced with entities)::
2201 unicode \\[0-9a-f]{1,4}
2202 latin1 [¡-ÿ]
2203 escape {unicode}|\\[ -~¡-ÿ]
2204 nmchar [-a-z0-9]|{latin1}|{escape}
2205 name {nmchar}+
2207 The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"),
2208 or periods ("."), therefore "class" and "id" attributes should not contain
2209 these characters. They should be replaced with hyphens ("-"). Combined
2210 with HTML's requirements (the first character must be a letter; no
2211 "unicode", "latin1", or "escape" characters), this results in the
2212 ``[a-z](-?[a-z0-9]+)*`` pattern.
2214 .. _HTML 4.01 spec: https://www.w3.org/TR/html401
2215 .. _CSS1 spec: https://www.w3.org/TR/REC-CSS1
2216 """
2217 id = string.lower()
2218 id = id.translate(_non_id_translate_digraphs)
2219 id = id.translate(_non_id_translate)
2220 # get rid of non-ascii characters.
2221 # 'ascii' lowercase to prevent problems with turkish locale.
2222 id = unicodedata.normalize(
2223 'NFKD', id).encode('ascii', 'ignore').decode('ascii')
2224 # shrink runs of whitespace and replace by hyphen
2225 id = _non_id_chars.sub('-', ' '.join(id.split()))
2226 id = _non_id_at_ends.sub('', id)
2227 return str(id)
2230_non_id_chars = re.compile('[^a-z0-9]+')
2231_non_id_at_ends = re.compile('^[-0-9]+|-+$')
2232_non_id_translate = {
2233 0x00f8: 'o', # o with stroke
2234 0x0111: 'd', # d with stroke
2235 0x0127: 'h', # h with stroke
2236 0x0131: 'i', # dotless i
2237 0x0142: 'l', # l with stroke
2238 0x0167: 't', # t with stroke
2239 0x0180: 'b', # b with stroke
2240 0x0183: 'b', # b with topbar
2241 0x0188: 'c', # c with hook
2242 0x018c: 'd', # d with topbar
2243 0x0192: 'f', # f with hook
2244 0x0199: 'k', # k with hook
2245 0x019a: 'l', # l with bar
2246 0x019e: 'n', # n with long right leg
2247 0x01a5: 'p', # p with hook
2248 0x01ab: 't', # t with palatal hook
2249 0x01ad: 't', # t with hook
2250 0x01b4: 'y', # y with hook
2251 0x01b6: 'z', # z with stroke
2252 0x01e5: 'g', # g with stroke
2253 0x0225: 'z', # z with hook
2254 0x0234: 'l', # l with curl
2255 0x0235: 'n', # n with curl
2256 0x0236: 't', # t with curl
2257 0x0237: 'j', # dotless j
2258 0x023c: 'c', # c with stroke
2259 0x023f: 's', # s with swash tail
2260 0x0240: 'z', # z with swash tail
2261 0x0247: 'e', # e with stroke
2262 0x0249: 'j', # j with stroke
2263 0x024b: 'q', # q with hook tail
2264 0x024d: 'r', # r with stroke
2265 0x024f: 'y', # y with stroke
2266}
2267_non_id_translate_digraphs = {
2268 0x00df: 'sz', # ligature sz
2269 0x00e6: 'ae', # ae
2270 0x0153: 'oe', # ligature oe
2271 0x0238: 'db', # db digraph
2272 0x0239: 'qp', # qp digraph
2273}
2276def dupname(node, name):
2277 node['dupnames'].append(name)
2278 node['names'].remove(name)
2279 # Assume that this method is referenced, even though it isn't; we
2280 # don't want to throw unnecessary system_messages.
2281 node.referenced = 1
2284def fully_normalize_name(name):
2285 """Return a case- and whitespace-normalized name."""
2286 return ' '.join(name.lower().split())
2289def whitespace_normalize_name(name):
2290 """Return a whitespace-normalized name."""
2291 return ' '.join(name.split())
2294def serial_escape(value):
2295 """Escape string values that are elements of a list, for serialization."""
2296 return value.replace('\\', r'\\').replace(' ', r'\ ')
2299def pseudo_quoteattr(value):
2300 """Quote attributes for pseudo-xml"""
2301 return '"%s"' % value