Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/docutils/nodes.py: 69%
961 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:06 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:06 +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 traverse. 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 reprunicode(str):
330 """
331 Deprecated backwards compatibility stub. Use the standard `str` instead.
332 """
333 def __init__(self, s):
334 warnings.warn('nodes.reprunicode() is not required with Python 3'
335 ' and will be removed in Docutils 0.21 or later.',
336 DeprecationWarning, stacklevel=2)
337 super().__init__()
340def ensure_str(s):
341 """
342 Deprecated backwards compatibility stub returning `s`.
343 """
344 warnings.warn('nodes.ensure_str() is not required with Python 3'
345 ' and will be removed in Docutils 0.21 or later.',
346 DeprecationWarning, stacklevel=2)
347 return s
350# definition moved here from `utils` to avoid circular import dependency
351def unescape(text, restore_backslashes=False, respect_whitespace=False):
352 """
353 Return a string with nulls removed or restored to backslashes.
354 Backslash-escaped spaces are also removed.
355 """
356 # `respect_whitespace` is ignored (since introduction 2016-12-16)
357 if restore_backslashes:
358 return text.replace('\x00', '\\')
359 else:
360 for sep in ['\x00 ', '\x00\n', '\x00']:
361 text = ''.join(text.split(sep))
362 return text
365class Text(Node, str):
367 """
368 Instances are terminal nodes (leaves) containing text only; no child
369 nodes or attributes. Initialize by passing a string to the constructor.
371 Access the raw (null-escaped) text with ``str(<instance>)``
372 and unescaped text with ``<instance>.astext()``.
373 """
375 tagname = '#text'
377 children = ()
378 """Text nodes have no children, and cannot have children."""
380 def __new__(cls, data, rawsource=None):
381 """Assert that `data` is not an array of bytes
382 and warn if the deprecated `rawsource` argument is used.
383 """
384 if isinstance(data, bytes):
385 raise TypeError('expecting str data, not bytes')
386 if rawsource is not None:
387 warnings.warn('nodes.Text: initialization argument "rawsource" '
388 'is ignored and will be removed in Docutils 2.0.',
389 DeprecationWarning, stacklevel=2)
390 return str.__new__(cls, data)
392 def shortrepr(self, maxlen=18):
393 data = self
394 if len(data) > maxlen:
395 data = data[:maxlen-4] + ' ...'
396 return '<%s: %r>' % (self.tagname, str(data))
398 def __repr__(self):
399 return self.shortrepr(maxlen=68)
401 def _dom_node(self, domroot):
402 return domroot.createTextNode(str(self))
404 def astext(self):
405 return str(unescape(self))
407 def copy(self):
408 return self.__class__(str(self))
410 def deepcopy(self):
411 return self.copy()
413 def pformat(self, indent=' ', level=0):
414 try:
415 if self.document.settings.detailed:
416 tag = '%s%s' % (indent*level, '<#text>')
417 lines = (indent*(level+1) + repr(line)
418 for line in self.splitlines(True))
419 return '\n'.join((tag, *lines)) + '\n'
420 except AttributeError:
421 pass
422 indent = indent * level
423 lines = [indent+line for line in self.astext().splitlines()]
424 if not lines:
425 return ''
426 return '\n'.join(lines) + '\n'
428 # rstrip and lstrip are used by substitution definitions where
429 # they are expected to return a Text instance, this was formerly
430 # taken care of by UserString.
432 def rstrip(self, chars=None):
433 return self.__class__(str.rstrip(self, chars))
435 def lstrip(self, chars=None):
436 return self.__class__(str.lstrip(self, chars))
439class Element(Node):
441 """
442 `Element` is the superclass to all specific elements.
444 Elements contain attributes and child nodes.
445 They can be described as a cross between a list and a dictionary.
447 Elements emulate dictionaries for external [#]_ attributes, indexing by
448 attribute name (a string). To set the attribute 'att' to 'value', do::
450 element['att'] = 'value'
452 .. [#] External attributes correspond to the XML element attributes.
453 From its `Node` superclass, Element also inherits "internal"
454 class attributes that are accessed using the standard syntax, e.g.
455 ``element.parent``.
457 There are two special attributes: 'ids' and 'names'. Both are
458 lists of unique identifiers: 'ids' conform to the regular expression
459 ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function for rationale and
460 details). 'names' serve as user-friendly interfaces to IDs; they are
461 case- and whitespace-normalized (see the fully_normalize_name() function).
463 Elements emulate lists for child nodes (element nodes and/or text
464 nodes), indexing by integer. To get the first child node, use::
466 element[0]
468 to iterate over the child nodes (without descending), use::
470 for child in element:
471 ...
473 Elements may be constructed using the ``+=`` operator. To add one new
474 child node to element, do::
476 element += node
478 This is equivalent to ``element.append(node)``.
480 To add a list of multiple child nodes at once, use the same ``+=``
481 operator::
483 element += [node1, node2]
485 This is equivalent to ``element.extend([node1, node2])``.
486 """
488 basic_attributes = ('ids', 'classes', 'names', 'dupnames')
489 """Tuple of attributes which are defined for every Element-derived class
490 instance and can be safely transferred to a different node."""
492 local_attributes = ('backrefs',)
493 """Tuple of class-specific attributes that should not be copied with the
494 standard attributes when replacing a node.
496 NOTE: Derived classes should override this value to prevent any of its
497 attributes being copied by adding to the value in its parent class."""
499 list_attributes = basic_attributes + local_attributes
500 """Tuple of attributes that are automatically initialized to empty lists
501 for all nodes."""
503 known_attributes = list_attributes + ('source',)
504 """Tuple of attributes that are known to the Element base class."""
506 tagname = None
507 """The element generic identifier. If None, it is set as an instance
508 attribute to the name of the class."""
510 child_text_separator = '\n\n'
511 """Separator for child nodes, used by `astext()` method."""
513 def __init__(self, rawsource='', *children, **attributes):
514 self.rawsource = rawsource
515 """The raw text from which this element was constructed.
517 NOTE: some elements do not set this value (default '').
518 """
520 self.children = []
521 """List of child nodes (elements and/or `Text`)."""
523 self.extend(children) # maintain parent info
525 self.attributes = {}
526 """Dictionary of attribute {name: value}."""
528 # Initialize list attributes.
529 for att in self.list_attributes:
530 self.attributes[att] = []
532 for att, value in attributes.items():
533 att = att.lower()
534 if att in self.list_attributes:
535 # mutable list; make a copy for this node
536 self.attributes[att] = value[:]
537 else:
538 self.attributes[att] = value
540 if self.tagname is None:
541 self.tagname = self.__class__.__name__
543 def _dom_node(self, domroot):
544 element = domroot.createElement(self.tagname)
545 for attribute, value in self.attlist():
546 if isinstance(value, list):
547 value = ' '.join(serial_escape('%s' % (v,)) for v in value)
548 element.setAttribute(attribute, '%s' % value)
549 for child in self.children:
550 element.appendChild(child._dom_node(domroot))
551 return element
553 def __repr__(self):
554 data = ''
555 for c in self.children:
556 data += c.shortrepr()
557 if len(data) > 60:
558 data = data[:56] + ' ...'
559 break
560 if self['names']:
561 return '<%s "%s": %s>' % (self.__class__.__name__,
562 '; '.join(self['names']), data)
563 else:
564 return '<%s: %s>' % (self.__class__.__name__, data)
566 def shortrepr(self):
567 if self['names']:
568 return '<%s "%s"...>' % (self.__class__.__name__,
569 '; '.join(self['names']))
570 else:
571 return '<%s...>' % self.tagname
573 def __str__(self):
574 if self.children:
575 return '%s%s%s' % (self.starttag(),
576 ''.join(str(c) for c in self.children),
577 self.endtag())
578 else:
579 return self.emptytag()
581 def starttag(self, quoteattr=None):
582 # the optional arg is used by the docutils_xml writer
583 if quoteattr is None:
584 quoteattr = pseudo_quoteattr
585 parts = [self.tagname]
586 for name, value in self.attlist():
587 if value is None: # boolean attribute
588 parts.append('%s="True"' % name)
589 continue
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_`.
754 """
755 if isinstance(dict_, Node):
756 dict_ = dict_.attributes
757 for att in self.basic_attributes:
758 self.append_attr_list(att, dict_.get(att, []))
760 def append_attr_list(self, attr, values):
761 """
762 For each element in values, if it does not exist in self[attr], append
763 it.
765 NOTE: Requires self[attr] and values to be sequence type and the
766 former should specifically be a list.
767 """
768 # List Concatenation
769 for value in values:
770 if value not in self[attr]:
771 self[attr].append(value)
773 def coerce_append_attr_list(self, attr, value):
774 """
775 First, convert both self[attr] and value to a non-string sequence
776 type; if either is not already a sequence, convert it to a list of one
777 element. Then call append_attr_list.
779 NOTE: self[attr] and value both must not be None.
780 """
781 # List Concatenation
782 if not isinstance(self.get(attr), list):
783 self[attr] = [self[attr]]
784 if not isinstance(value, list):
785 value = [value]
786 self.append_attr_list(attr, value)
788 def replace_attr(self, attr, value, force=True):
789 """
790 If self[attr] does not exist or force is True or omitted, set
791 self[attr] to value, otherwise do nothing.
792 """
793 # One or the other
794 if force or self.get(attr) is None:
795 self[attr] = value
797 def copy_attr_convert(self, attr, value, replace=True):
798 """
799 If attr is an attribute of self, set self[attr] to
800 [self[attr], value], otherwise set self[attr] to value.
802 NOTE: replace is not used by this function and is kept only for
803 compatibility with the other copy functions.
804 """
805 if self.get(attr) is not value:
806 self.coerce_append_attr_list(attr, value)
808 def copy_attr_coerce(self, attr, value, replace):
809 """
810 If attr is an attribute of self and either self[attr] or value is a
811 list, convert all non-sequence values to a sequence of 1 element and
812 then concatenate the two sequence, setting the result to self[attr].
813 If both self[attr] and value are non-sequences and replace is True or
814 self[attr] is None, replace self[attr] with value. Otherwise, do
815 nothing.
816 """
817 if self.get(attr) is not value:
818 if isinstance(self.get(attr), list) or \
819 isinstance(value, list):
820 self.coerce_append_attr_list(attr, value)
821 else:
822 self.replace_attr(attr, value, replace)
824 def copy_attr_concatenate(self, attr, value, replace):
825 """
826 If attr is an attribute of self and both self[attr] and value are
827 lists, concatenate the two sequences, setting the result to
828 self[attr]. If either self[attr] or value are non-sequences and
829 replace is True or self[attr] is None, replace self[attr] with value.
830 Otherwise, do nothing.
831 """
832 if self.get(attr) is not value:
833 if isinstance(self.get(attr), list) and \
834 isinstance(value, list):
835 self.append_attr_list(attr, value)
836 else:
837 self.replace_attr(attr, value, replace)
839 def copy_attr_consistent(self, attr, value, replace):
840 """
841 If replace is True or self[attr] is None, replace self[attr] with
842 value. Otherwise, do nothing.
843 """
844 if self.get(attr) is not value:
845 self.replace_attr(attr, value, replace)
847 def update_all_atts(self, dict_, update_fun=copy_attr_consistent,
848 replace=True, and_source=False):
849 """
850 Updates all attributes from node or dictionary `dict_`.
852 Appends the basic attributes ('ids', 'names', 'classes',
853 'dupnames', but not 'source') and then, for all other attributes in
854 dict_, updates the same attribute in self. When attributes with the
855 same identifier appear in both self and dict_, the two values are
856 merged based on the value of update_fun. Generally, when replace is
857 True, the values in self are replaced or merged with the values in
858 dict_; otherwise, the values in self may be preserved or merged. When
859 and_source is True, the 'source' attribute is included in the copy.
861 NOTE: When replace is False, and self contains a 'source' attribute,
862 'source' is not replaced even when dict_ has a 'source'
863 attribute, though it may still be merged into a list depending
864 on the value of update_fun.
865 NOTE: It is easier to call the update-specific methods then to pass
866 the update_fun method to this function.
867 """
868 if isinstance(dict_, Node):
869 dict_ = dict_.attributes
871 # Include the source attribute when copying?
872 if and_source:
873 filter_fun = self.is_not_list_attribute
874 else:
875 filter_fun = self.is_not_known_attribute
877 # Copy the basic attributes
878 self.update_basic_atts(dict_)
880 # Grab other attributes in dict_ not in self except the
881 # (All basic attributes should be copied already)
882 for att in filter(filter_fun, dict_):
883 update_fun(self, att, dict_[att], replace)
885 def update_all_atts_consistantly(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_ and replace is True, the
894 values in self are replaced with the values in dict_; otherwise, the
895 values in self are preserved. When and_source is True, the 'source'
896 attribute is included in the copy.
898 NOTE: When replace is False, and self contains a 'source' attribute,
899 'source' is not replaced even when dict_ has a 'source'
900 attribute, though it may still be merged into a list depending
901 on the value of update_fun.
902 """
903 self.update_all_atts(dict_, Element.copy_attr_consistent, replace,
904 and_source)
906 def update_all_atts_concatenating(self, dict_, replace=True,
907 and_source=False):
908 """
909 Updates all attributes from node or dictionary `dict_`.
911 Appends the basic attributes ('ids', 'names', 'classes',
912 'dupnames', but not 'source') and then, for all other attributes in
913 dict_, updates the same attribute in self. When attributes with the
914 same identifier appear in both self and dict_ whose values aren't each
915 lists and replace is True, the values in self are replaced with the
916 values in dict_; if the values from self and dict_ for the given
917 identifier are both of list type, then the two lists are concatenated
918 and the result stored in self; otherwise, the values in self are
919 preserved. When and_source is True, the 'source' attribute is
920 included in the copy.
922 NOTE: When replace is False, and self contains a 'source' attribute,
923 'source' is not replaced even when dict_ has a 'source'
924 attribute, though it may still be merged into a list depending
925 on the value of update_fun.
926 """
927 self.update_all_atts(dict_, Element.copy_attr_concatenate, replace,
928 and_source)
930 def update_all_atts_coercion(self, dict_, replace=True,
931 and_source=False):
932 """
933 Updates all attributes from node or dictionary `dict_`.
935 Appends the basic attributes ('ids', 'names', 'classes',
936 'dupnames', but not 'source') and then, for all other attributes in
937 dict_, updates the same attribute in self. When attributes with the
938 same identifier appear in both self and dict_ whose values are both
939 not lists and replace is True, the values in self are replaced with
940 the values in dict_; if either of the values from self and dict_ for
941 the given identifier are of list type, then first any non-lists are
942 converted to 1-element lists and then the two lists are concatenated
943 and the result stored in self; otherwise, the values in self are
944 preserved. When and_source is True, the 'source' attribute is
945 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_coerce, replace,
953 and_source)
955 def update_all_atts_convert(self, dict_, and_source=False):
956 """
957 Updates all attributes from node or dictionary `dict_`.
959 Appends the basic attributes ('ids', 'names', 'classes',
960 'dupnames', but not 'source') and then, for all other attributes in
961 dict_, updates the same attribute in self. When attributes with the
962 same identifier appear in both self and dict_ then first any non-lists
963 are converted to 1-element lists and then the two lists are
964 concatenated and the result stored in self; otherwise, the values in
965 self are preserved. When and_source is True, the 'source' attribute
966 is included in the copy.
968 NOTE: When replace is False, and self contains a 'source' attribute,
969 'source' is not replaced even when dict_ has a 'source'
970 attribute, though it may still be merged into a list depending
971 on the value of update_fun.
972 """
973 self.update_all_atts(dict_, Element.copy_attr_convert,
974 and_source=and_source)
976 def clear(self):
977 self.children = []
979 def replace(self, old, new):
980 """Replace one child `Node` with another child or children."""
981 index = self.index(old)
982 if isinstance(new, Node):
983 self.setup_child(new)
984 self[index] = new
985 elif new is not None:
986 self[index:index+1] = new
988 def replace_self(self, new):
989 """
990 Replace `self` node with `new`, where `new` is a node or a
991 list of nodes.
992 """
993 update = new
994 if not isinstance(new, Node):
995 # `new` is a list; update first child.
996 try:
997 update = new[0]
998 except IndexError:
999 update = None
1000 if isinstance(update, Element):
1001 update.update_basic_atts(self)
1002 else:
1003 # `update` is a Text node or `new` is an empty list.
1004 # Assert that we aren't losing any attributes.
1005 for att in self.basic_attributes:
1006 assert not self[att], \
1007 'Losing "%s" attribute: %s' % (att, self[att])
1008 self.parent.replace(self, new)
1010 def first_child_matching_class(self, childclass, start=0, end=sys.maxsize):
1011 """
1012 Return the index of the first child whose class exactly matches.
1014 Parameters:
1016 - `childclass`: A `Node` subclass to search for, or a tuple of `Node`
1017 classes. If a tuple, any of the classes may match.
1018 - `start`: Initial index to check.
1019 - `end`: Initial index to *not* check.
1020 """
1021 if not isinstance(childclass, tuple):
1022 childclass = (childclass,)
1023 for index in range(start, min(len(self), end)):
1024 for c in childclass:
1025 if isinstance(self[index], c):
1026 return index
1027 return None
1029 def first_child_not_matching_class(self, childclass, start=0,
1030 end=sys.maxsize):
1031 """
1032 Return the index of the first child whose class does *not* match.
1034 Parameters:
1036 - `childclass`: A `Node` subclass to skip, or a tuple of `Node`
1037 classes. If a tuple, none of the classes may match.
1038 - `start`: Initial index to check.
1039 - `end`: Initial index to *not* check.
1040 """
1041 if not isinstance(childclass, tuple):
1042 childclass = (childclass,)
1043 for index in range(start, min(len(self), end)):
1044 for c in childclass:
1045 if isinstance(self.children[index], c):
1046 break
1047 else:
1048 return index
1049 return None
1051 def pformat(self, indent=' ', level=0):
1052 tagline = '%s%s\n' % (indent*level, self.starttag())
1053 childreps = (c.pformat(indent, level+1) for c in self.children)
1054 return ''.join((tagline, *childreps))
1056 def copy(self):
1057 obj = self.__class__(rawsource=self.rawsource, **self.attributes)
1058 obj._document = self._document
1059 obj.source = self.source
1060 obj.line = self.line
1061 return obj
1063 def deepcopy(self):
1064 copy = self.copy()
1065 copy.extend([child.deepcopy() for child in self.children])
1066 return copy
1068 def set_class(self, name):
1069 """Add a new class to the "classes" attribute."""
1070 warnings.warn('docutils.nodes.Element.set_class() is deprecated; '
1071 ' and will be removed in Docutils 0.21 or later.'
1072 "Append to Element['classes'] list attribute directly",
1073 DeprecationWarning, stacklevel=2)
1074 assert ' ' not in name
1075 self['classes'].append(name.lower())
1077 def note_referenced_by(self, name=None, id=None):
1078 """Note that this Element has been referenced by its name
1079 `name` or id `id`."""
1080 self.referenced = 1
1081 # Element.expect_referenced_by_* dictionaries map names or ids
1082 # to nodes whose ``referenced`` attribute is set to true as
1083 # soon as this node is referenced by the given name or id.
1084 # Needed for target propagation.
1085 by_name = getattr(self, 'expect_referenced_by_name', {}).get(name)
1086 by_id = getattr(self, 'expect_referenced_by_id', {}).get(id)
1087 if by_name:
1088 assert name is not None
1089 by_name.referenced = 1
1090 if by_id:
1091 assert id is not None
1092 by_id.referenced = 1
1094 @classmethod
1095 def is_not_list_attribute(cls, attr):
1096 """
1097 Returns True if and only if the given attribute is NOT one of the
1098 basic list attributes defined for all Elements.
1099 """
1100 return attr not in cls.list_attributes
1102 @classmethod
1103 def is_not_known_attribute(cls, attr):
1104 """
1105 Returns True if and only if the given attribute is NOT recognized by
1106 this class.
1107 """
1108 return attr not in cls.known_attributes
1111class TextElement(Element):
1113 """
1114 An element which directly contains text.
1116 Its children are all `Text` or `Inline` subclass nodes. You can
1117 check whether an element's context is inline simply by checking whether
1118 its immediate parent is a `TextElement` instance (including subclasses).
1119 This is handy for nodes like `image` that can appear both inline and as
1120 standalone body elements.
1122 If passing children to `__init__()`, make sure to set `text` to
1123 ``''`` or some other suitable value.
1124 """
1126 child_text_separator = ''
1127 """Separator for child nodes, used by `astext()` method."""
1129 def __init__(self, rawsource='', text='', *children, **attributes):
1130 if text != '':
1131 textnode = Text(text)
1132 Element.__init__(self, rawsource, textnode, *children,
1133 **attributes)
1134 else:
1135 Element.__init__(self, rawsource, *children, **attributes)
1138class FixedTextElement(TextElement):
1140 """An element which directly contains preformatted text."""
1142 def __init__(self, rawsource='', text='', *children, **attributes):
1143 TextElement.__init__(self, rawsource, text, *children, **attributes)
1144 self.attributes['xml:space'] = 'preserve'
1147# ========
1148# Mixins
1149# ========
1151class Resolvable:
1153 resolved = 0
1156class BackLinkable:
1158 def add_backref(self, refid):
1159 self['backrefs'].append(refid)
1162# ====================
1163# Element Categories
1164# ====================
1166class Root:
1167 pass
1170class Titular:
1171 pass
1174class PreBibliographic:
1175 """Category of Node which may occur before Bibliographic Nodes."""
1178class Bibliographic:
1179 pass
1182class Decorative(PreBibliographic):
1183 pass
1186class Structural:
1187 pass
1190class Body:
1191 pass
1194class General(Body):
1195 pass
1198class Sequential(Body):
1199 """List-like elements."""
1202class Admonition(Body): pass
1205class Special(Body):
1206 """Special internal body elements."""
1209class Invisible(PreBibliographic):
1210 """Internal elements that don't appear in output."""
1213class Part:
1214 pass
1217class Inline:
1218 pass
1221class Referential(Resolvable):
1222 pass
1225class Targetable(Resolvable):
1227 referenced = 0
1229 indirect_reference_name = None
1230 """Holds the whitespace_normalized_name (contains mixed case) of a target.
1231 Required for MoinMoin/reST compatibility."""
1234class Labeled:
1235 """Contains a `label` as its first element."""
1238# ==============
1239# Root Element
1240# ==============
1242class document(Root, Structural, Element):
1244 """
1245 The document root element.
1247 Do not instantiate this class directly; use
1248 `docutils.utils.new_document()` instead.
1249 """
1251 def __init__(self, settings, reporter, *args, **kwargs):
1252 Element.__init__(self, *args, **kwargs)
1254 self.current_source = None
1255 """Path to or description of the input source being processed."""
1257 self.current_line = None
1258 """Line number (1-based) of `current_source`."""
1260 self.settings = settings
1261 """Runtime settings data record."""
1263 self.reporter = reporter
1264 """System message generator."""
1266 self.indirect_targets = []
1267 """List of indirect target nodes."""
1269 self.substitution_defs = {}
1270 """Mapping of substitution names to substitution_definition nodes."""
1272 self.substitution_names = {}
1273 """Mapping of case-normalized substitution names to case-sensitive
1274 names."""
1276 self.refnames = {}
1277 """Mapping of names to lists of referencing nodes."""
1279 self.refids = {}
1280 """Mapping of ids to lists of referencing nodes."""
1282 self.nameids = {}
1283 """Mapping of names to unique id's."""
1285 self.nametypes = {}
1286 """Mapping of names to hyperlink type (boolean: True => explicit,
1287 False => implicit."""
1289 self.ids = {}
1290 """Mapping of ids to nodes."""
1292 self.footnote_refs = {}
1293 """Mapping of footnote labels to lists of footnote_reference nodes."""
1295 self.citation_refs = {}
1296 """Mapping of citation labels to lists of citation_reference nodes."""
1298 self.autofootnotes = []
1299 """List of auto-numbered footnote nodes."""
1301 self.autofootnote_refs = []
1302 """List of auto-numbered footnote_reference nodes."""
1304 self.symbol_footnotes = []
1305 """List of symbol footnote nodes."""
1307 self.symbol_footnote_refs = []
1308 """List of symbol footnote_reference nodes."""
1310 self.footnotes = []
1311 """List of manually-numbered footnote nodes."""
1313 self.citations = []
1314 """List of citation nodes."""
1316 self.autofootnote_start = 1
1317 """Initial auto-numbered footnote number."""
1319 self.symbol_footnote_start = 0
1320 """Initial symbol footnote symbol index."""
1322 self.id_counter = Counter()
1323 """Numbers added to otherwise identical IDs."""
1325 self.parse_messages = []
1326 """System messages generated while parsing."""
1328 self.transform_messages = []
1329 """System messages generated while applying transforms."""
1331 import docutils.transforms
1332 self.transformer = docutils.transforms.Transformer(self)
1333 """Storage for transforms to be applied to this document."""
1335 self.include_log = []
1336 """The current source's parents (to detect inclusion loops)."""
1338 self.decoration = None
1339 """Document's `decoration` node."""
1341 self._document = self
1343 def __getstate__(self):
1344 """
1345 Return dict with unpicklable references removed.
1346 """
1347 state = self.__dict__.copy()
1348 state['reporter'] = None
1349 state['transformer'] = None
1350 return state
1352 def asdom(self, dom=None):
1353 """Return a DOM representation of this document."""
1354 if dom is None:
1355 import xml.dom.minidom as dom
1356 domroot = dom.Document()
1357 domroot.appendChild(self._dom_node(domroot))
1358 return domroot
1360 def set_id(self, node, msgnode=None, suggested_prefix=''):
1361 if node['ids']:
1362 # register and check for duplicates
1363 for id in node['ids']:
1364 self.ids.setdefault(id, node)
1365 if self.ids[id] is not node:
1366 msg = self.reporter.severe('Duplicate ID: "%s".' % id)
1367 if msgnode is not None:
1368 msgnode += msg
1369 return id
1370 # generate and set id
1371 id_prefix = self.settings.id_prefix
1372 auto_id_prefix = self.settings.auto_id_prefix
1373 base_id = ''
1374 id = ''
1375 for name in node['names']:
1376 if id_prefix:
1377 # allow names starting with numbers if `id_prefix`
1378 base_id = make_id('x'+name)[1:]
1379 else:
1380 base_id = make_id(name)
1381 # TODO: normalize id-prefix? (would make code simpler)
1382 id = id_prefix + base_id
1383 if base_id and id not in self.ids:
1384 break
1385 else:
1386 if base_id and auto_id_prefix.endswith('%'):
1387 # disambiguate name-derived ID
1388 # TODO: remove second condition after announcing change
1389 prefix = id + '-'
1390 else:
1391 prefix = id_prefix + auto_id_prefix
1392 if prefix.endswith('%'):
1393 prefix = '%s%s-' % (prefix[:-1],
1394 suggested_prefix
1395 or make_id(node.tagname))
1396 while True:
1397 self.id_counter[prefix] += 1
1398 id = '%s%d' % (prefix, self.id_counter[prefix])
1399 if id not in self.ids:
1400 break
1401 node['ids'].append(id)
1402 self.ids[id] = node
1403 return id
1405 def set_name_id_map(self, node, id, msgnode=None, explicit=None):
1406 """
1407 `self.nameids` maps names to IDs, while `self.nametypes` maps names to
1408 booleans representing hyperlink type (True==explicit,
1409 False==implicit). This method updates the mappings.
1411 The following state transition table shows how `self.nameids` items
1412 ("id") and `self.nametypes` items ("type") change with new input
1413 (a call to this method), and what actions are performed
1414 ("implicit"-type system messages are INFO/1, and
1415 "explicit"-type system messages are ERROR/3):
1417 ==== ===== ======== ======== ======= ==== ===== =====
1418 Old State Input Action New State Notes
1419 ----------- -------- ----------------- ----------- -----
1420 id type new type sys.msg. dupname id type
1421 ==== ===== ======== ======== ======= ==== ===== =====
1422 - - explicit - - new True
1423 - - implicit - - new False
1424 - False explicit - - new True
1425 old False explicit implicit old new True
1426 - True explicit explicit new - True
1427 old True explicit explicit new,old - True [#]_
1428 - False implicit implicit new - False
1429 old False implicit implicit new,old - False
1430 - True implicit implicit new - True
1431 old True implicit implicit new old True
1432 ==== ===== ======== ======== ======= ==== ===== =====
1434 .. [#] Do not clear the name-to-id map or invalidate the old target if
1435 both old and new targets are external and refer to identical URIs.
1436 The new target is invalidated regardless.
1437 """
1438 for name in tuple(node['names']):
1439 if name in self.nameids:
1440 self.set_duplicate_name_id(node, id, name, msgnode, explicit)
1441 # attention: modifies node['names']
1442 else:
1443 self.nameids[name] = id
1444 self.nametypes[name] = explicit
1446 def set_duplicate_name_id(self, node, id, name, msgnode, explicit):
1447 old_id = self.nameids[name]
1448 old_explicit = self.nametypes[name]
1449 self.nametypes[name] = old_explicit or explicit
1450 if explicit:
1451 if old_explicit:
1452 level = 2
1453 if old_id is not None:
1454 old_node = self.ids[old_id]
1455 if 'refuri' in node:
1456 refuri = node['refuri']
1457 if (old_node['names']
1458 and 'refuri' in old_node
1459 and old_node['refuri'] == refuri):
1460 level = 1 # just inform if refuri's identical
1461 if level > 1:
1462 dupname(old_node, name)
1463 self.nameids[name] = None
1464 msg = self.reporter.system_message(
1465 level, 'Duplicate explicit target name: "%s".' % name,
1466 backrefs=[id], base_node=node)
1467 if msgnode is not None:
1468 msgnode += msg
1469 dupname(node, name)
1470 else:
1471 self.nameids[name] = id
1472 if old_id is not None:
1473 old_node = self.ids[old_id]
1474 dupname(old_node, name)
1475 else:
1476 if old_id is not None and not old_explicit:
1477 self.nameids[name] = None
1478 old_node = self.ids[old_id]
1479 dupname(old_node, name)
1480 dupname(node, name)
1481 if not explicit or (not old_explicit and old_id is not None):
1482 msg = self.reporter.info(
1483 'Duplicate implicit target name: "%s".' % name,
1484 backrefs=[id], base_node=node)
1485 if msgnode is not None:
1486 msgnode += msg
1488 def has_name(self, name):
1489 return name in self.nameids
1491 # "note" here is an imperative verb: "take note of".
1492 def note_implicit_target(self, target, msgnode=None):
1493 id = self.set_id(target, msgnode)
1494 self.set_name_id_map(target, id, msgnode, explicit=False)
1496 def note_explicit_target(self, target, msgnode=None):
1497 id = self.set_id(target, msgnode)
1498 self.set_name_id_map(target, id, msgnode, explicit=True)
1500 def note_refname(self, node):
1501 self.refnames.setdefault(node['refname'], []).append(node)
1503 def note_refid(self, node):
1504 self.refids.setdefault(node['refid'], []).append(node)
1506 def note_indirect_target(self, target):
1507 self.indirect_targets.append(target)
1508 if target['names']:
1509 self.note_refname(target)
1511 def note_anonymous_target(self, target):
1512 self.set_id(target)
1514 def note_autofootnote(self, footnote):
1515 self.set_id(footnote)
1516 self.autofootnotes.append(footnote)
1518 def note_autofootnote_ref(self, ref):
1519 self.set_id(ref)
1520 self.autofootnote_refs.append(ref)
1522 def note_symbol_footnote(self, footnote):
1523 self.set_id(footnote)
1524 self.symbol_footnotes.append(footnote)
1526 def note_symbol_footnote_ref(self, ref):
1527 self.set_id(ref)
1528 self.symbol_footnote_refs.append(ref)
1530 def note_footnote(self, footnote):
1531 self.set_id(footnote)
1532 self.footnotes.append(footnote)
1534 def note_footnote_ref(self, ref):
1535 self.set_id(ref)
1536 self.footnote_refs.setdefault(ref['refname'], []).append(ref)
1537 self.note_refname(ref)
1539 def note_citation(self, citation):
1540 self.citations.append(citation)
1542 def note_citation_ref(self, ref):
1543 self.set_id(ref)
1544 self.citation_refs.setdefault(ref['refname'], []).append(ref)
1545 self.note_refname(ref)
1547 def note_substitution_def(self, subdef, def_name, msgnode=None):
1548 name = whitespace_normalize_name(def_name)
1549 if name in self.substitution_defs:
1550 msg = self.reporter.error(
1551 'Duplicate substitution definition name: "%s".' % name,
1552 base_node=subdef)
1553 if msgnode is not None:
1554 msgnode += msg
1555 oldnode = self.substitution_defs[name]
1556 dupname(oldnode, name)
1557 # keep only the last definition:
1558 self.substitution_defs[name] = subdef
1559 # case-insensitive mapping:
1560 self.substitution_names[fully_normalize_name(name)] = name
1562 def note_substitution_ref(self, subref, refname):
1563 subref['refname'] = whitespace_normalize_name(refname)
1565 def note_pending(self, pending, priority=None):
1566 self.transformer.add_pending(pending, priority)
1568 def note_parse_message(self, message):
1569 self.parse_messages.append(message)
1571 def note_transform_message(self, message):
1572 self.transform_messages.append(message)
1574 def note_source(self, source, offset):
1575 self.current_source = source
1576 if offset is None:
1577 self.current_line = offset
1578 else:
1579 self.current_line = offset + 1
1581 def copy(self):
1582 obj = self.__class__(self.settings, self.reporter,
1583 **self.attributes)
1584 obj.source = self.source
1585 obj.line = self.line
1586 return obj
1588 def get_decoration(self):
1589 if not self.decoration:
1590 self.decoration = decoration()
1591 index = self.first_child_not_matching_class((Titular, meta))
1592 if index is None:
1593 self.append(self.decoration)
1594 else:
1595 self.insert(index, self.decoration)
1596 return self.decoration
1599# ================
1600# Title Elements
1601# ================
1603class title(Titular, PreBibliographic, TextElement): pass
1604class subtitle(Titular, PreBibliographic, TextElement): pass
1605class rubric(Titular, TextElement): pass
1608# ==================
1609# Meta-Data Element
1610# ==================
1612class meta(PreBibliographic, Element):
1613 """Container for "invisible" bibliographic data, or meta-data."""
1616# ========================
1617# Bibliographic Elements
1618# ========================
1620class docinfo(Bibliographic, Element): pass
1621class author(Bibliographic, TextElement): pass
1622class authors(Bibliographic, Element): pass
1623class organization(Bibliographic, TextElement): pass
1624class address(Bibliographic, FixedTextElement): pass
1625class contact(Bibliographic, TextElement): pass
1626class version(Bibliographic, TextElement): pass
1627class revision(Bibliographic, TextElement): pass
1628class status(Bibliographic, TextElement): pass
1629class date(Bibliographic, TextElement): pass
1630class copyright(Bibliographic, TextElement): pass
1633# =====================
1634# Decorative Elements
1635# =====================
1637class decoration(Decorative, Element):
1639 def get_header(self):
1640 if not len(self.children) or not isinstance(self.children[0], header):
1641 self.insert(0, header())
1642 return self.children[0]
1644 def get_footer(self):
1645 if not len(self.children) or not isinstance(self.children[-1], footer):
1646 self.append(footer())
1647 return self.children[-1]
1650class header(Decorative, Element): pass
1651class footer(Decorative, Element): pass
1654# =====================
1655# Structural Elements
1656# =====================
1658class section(Structural, Element): pass
1661class topic(Structural, Element):
1663 """
1664 Topics are terminal, "leaf" mini-sections, like block quotes with titles,
1665 or textual figures. A topic is just like a section, except that it has no
1666 subsections, and it doesn't have to conform to section placement rules.
1668 Topics are allowed wherever body elements (list, table, etc.) are allowed,
1669 but only at the top level of a section or document. Topics cannot nest
1670 inside topics, sidebars, or body elements; you can't have a topic inside a
1671 table, list, block quote, etc.
1672 """
1675class sidebar(Structural, Element):
1677 """
1678 Sidebars are like miniature, parallel documents that occur inside other
1679 documents, providing related or reference material. A sidebar is
1680 typically offset by a border and "floats" to the side of the page; the
1681 document's main text may flow around it. Sidebars can also be likened to
1682 super-footnotes; their content is outside of the flow of the document's
1683 main text.
1685 Sidebars are allowed wherever body elements (list, table, etc.) are
1686 allowed, but only at the top level of a section or document. Sidebars
1687 cannot nest inside sidebars, topics, or body elements; you can't have a
1688 sidebar inside a table, list, block quote, etc.
1689 """
1692class transition(Structural, Element): pass
1695# ===============
1696# Body Elements
1697# ===============
1699class paragraph(General, TextElement): pass
1700class compound(General, Element): pass
1701class container(General, Element): pass
1702class bullet_list(Sequential, Element): pass
1703class enumerated_list(Sequential, Element): pass
1704class list_item(Part, Element): pass
1705class definition_list(Sequential, Element): pass
1706class definition_list_item(Part, Element): pass
1707class term(Part, TextElement): pass
1708class classifier(Part, TextElement): pass
1709class definition(Part, Element): pass
1710class field_list(Sequential, Element): pass
1711class field(Part, Element): pass
1712class field_name(Part, TextElement): pass
1713class field_body(Part, Element): pass
1716class option(Part, Element):
1718 child_text_separator = ''
1721class option_argument(Part, TextElement):
1723 def astext(self):
1724 return self.get('delimiter', ' ') + TextElement.astext(self)
1727class option_group(Part, Element):
1729 child_text_separator = ', '
1732class option_list(Sequential, Element): pass
1735class option_list_item(Part, Element):
1737 child_text_separator = ' '
1740class option_string(Part, TextElement): pass
1741class description(Part, Element): pass
1742class literal_block(General, FixedTextElement): pass
1743class doctest_block(General, FixedTextElement): pass
1744class math_block(General, FixedTextElement): pass
1745class line_block(General, Element): pass
1748class line(Part, TextElement):
1750 indent = None
1753class block_quote(General, Element): pass
1754class attribution(Part, TextElement): pass
1755class attention(Admonition, Element): pass
1756class caution(Admonition, Element): pass
1757class danger(Admonition, Element): pass
1758class error(Admonition, Element): pass
1759class important(Admonition, Element): pass
1760class note(Admonition, Element): pass
1761class tip(Admonition, Element): pass
1762class hint(Admonition, Element): pass
1763class warning(Admonition, Element): pass
1764class admonition(Admonition, Element): pass
1765class comment(Special, Invisible, FixedTextElement): pass
1766class substitution_definition(Special, Invisible, TextElement): pass
1767class target(Special, Invisible, Inline, TextElement, Targetable): pass
1768class footnote(General, BackLinkable, Element, Labeled, Targetable): pass
1769class citation(General, BackLinkable, Element, Labeled, Targetable): pass
1770class label(Part, TextElement): pass
1771class figure(General, Element): pass
1772class caption(Part, TextElement): pass
1773class legend(Part, Element): pass
1774class table(General, Element): pass
1775class tgroup(Part, Element): pass
1776class colspec(Part, Element): pass
1777class thead(Part, Element): pass
1778class tbody(Part, Element): pass
1779class row(Part, Element): pass
1780class entry(Part, Element): pass
1783class system_message(Special, BackLinkable, PreBibliographic, Element):
1785 """
1786 System message element.
1788 Do not instantiate this class directly; use
1789 ``document.reporter.info/warning/error/severe()`` instead.
1790 """
1792 def __init__(self, message=None, *children, **attributes):
1793 rawsource = attributes.pop('rawsource', '')
1794 if message:
1795 p = paragraph('', message)
1796 children = (p,) + children
1797 try:
1798 Element.__init__(self, rawsource, *children, **attributes)
1799 except: # noqa catchall
1800 print('system_message: children=%r' % (children,))
1801 raise
1803 def astext(self):
1804 line = self.get('line', '')
1805 return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'],
1806 self['level'], Element.astext(self))
1809class pending(Special, Invisible, Element):
1811 """
1812 The "pending" element is used to encapsulate a pending operation: the
1813 operation (transform), the point at which to apply it, and any data it
1814 requires. Only the pending operation's location within the document is
1815 stored in the public document tree (by the "pending" object itself); the
1816 operation and its data are stored in the "pending" object's internal
1817 instance attributes.
1819 For example, say you want a table of contents in your reStructuredText
1820 document. The easiest way to specify where to put it is from within the
1821 document, with a directive::
1823 .. contents::
1825 But the "contents" directive can't do its work until the entire document
1826 has been parsed and possibly transformed to some extent. So the directive
1827 code leaves a placeholder behind that will trigger the second phase of its
1828 processing, something like this::
1830 <pending ...public attributes...> + internal attributes
1832 Use `document.note_pending()` so that the
1833 `docutils.transforms.Transformer` stage of processing can run all pending
1834 transforms.
1835 """
1837 def __init__(self, transform, details=None,
1838 rawsource='', *children, **attributes):
1839 Element.__init__(self, rawsource, *children, **attributes)
1841 self.transform = transform
1842 """The `docutils.transforms.Transform` class implementing the pending
1843 operation."""
1845 self.details = details or {}
1846 """Detail data (dictionary) required by the pending operation."""
1848 def pformat(self, indent=' ', level=0):
1849 internals = ['.. internal attributes:',
1850 ' .transform: %s.%s' % (self.transform.__module__,
1851 self.transform.__name__),
1852 ' .details:']
1853 details = sorted(self.details.items())
1854 for key, value in details:
1855 if isinstance(value, Node):
1856 internals.append('%7s%s:' % ('', key))
1857 internals.extend(['%9s%s' % ('', line)
1858 for line in value.pformat().splitlines()])
1859 elif (value
1860 and isinstance(value, list)
1861 and isinstance(value[0], Node)):
1862 internals.append('%7s%s:' % ('', key))
1863 for v in value:
1864 internals.extend(['%9s%s' % ('', line)
1865 for line in v.pformat().splitlines()])
1866 else:
1867 internals.append('%7s%s: %r' % ('', key, value))
1868 return (Element.pformat(self, indent, level)
1869 + ''.join((' %s%s\n' % (indent * level, line))
1870 for line in internals))
1872 def copy(self):
1873 obj = self.__class__(self.transform, self.details, self.rawsource,
1874 **self.attributes)
1875 obj._document = self._document
1876 obj.source = self.source
1877 obj.line = self.line
1878 return obj
1881class raw(Special, Inline, PreBibliographic, FixedTextElement):
1883 """
1884 Raw data that is to be passed untouched to the Writer.
1885 """
1888# =================
1889# Inline Elements
1890# =================
1892class emphasis(Inline, TextElement): pass
1893class strong(Inline, TextElement): pass
1894class literal(Inline, TextElement): pass
1895class reference(General, Inline, Referential, TextElement): pass
1896class footnote_reference(Inline, Referential, TextElement): pass
1897class citation_reference(Inline, Referential, TextElement): pass
1898class substitution_reference(Inline, TextElement): pass
1899class title_reference(Inline, TextElement): pass
1900class abbreviation(Inline, TextElement): pass
1901class acronym(Inline, TextElement): pass
1902class superscript(Inline, TextElement): pass
1903class subscript(Inline, TextElement): pass
1904class math(Inline, TextElement): pass
1907class image(General, Inline, Element):
1909 def astext(self):
1910 return self.get('alt', '')
1913class inline(Inline, TextElement): pass
1914class problematic(Inline, TextElement): pass
1915class generated(Inline, TextElement): pass
1918# ========================================
1919# Auxiliary Classes, Functions, and Data
1920# ========================================
1922node_class_names = """
1923 Text
1924 abbreviation acronym address admonition attention attribution author
1925 authors
1926 block_quote bullet_list
1927 caption caution citation citation_reference classifier colspec comment
1928 compound contact container copyright
1929 danger date decoration definition definition_list definition_list_item
1930 description docinfo doctest_block document
1931 emphasis entry enumerated_list error
1932 field field_body field_list field_name figure footer
1933 footnote footnote_reference
1934 generated
1935 header hint
1936 image important inline
1937 label legend line line_block list_item literal literal_block
1938 math math_block meta
1939 note
1940 option option_argument option_group option_list option_list_item
1941 option_string organization
1942 paragraph pending problematic
1943 raw reference revision row rubric
1944 section sidebar status strong subscript substitution_definition
1945 substitution_reference subtitle superscript system_message
1946 table target tbody term tgroup thead tip title title_reference topic
1947 transition
1948 version
1949 warning""".split()
1950"""A list of names of all concrete Node subclasses."""
1953class NodeVisitor:
1955 """
1956 "Visitor" pattern [GoF95]_ abstract superclass implementation for
1957 document tree traversals.
1959 Each node class has corresponding methods, doing nothing by
1960 default; override individual methods for specific and useful
1961 behaviour. The `dispatch_visit()` method is called by
1962 `Node.walk()` upon entering a node. `Node.walkabout()` also calls
1963 the `dispatch_departure()` method before exiting a node.
1965 The dispatch methods call "``visit_`` + node class name" or
1966 "``depart_`` + node class name", resp.
1968 This is a base class for visitors whose ``visit_...`` & ``depart_...``
1969 methods must be implemented for *all* compulsory node types encountered
1970 (such as for `docutils.writers.Writer` subclasses).
1971 Unimplemented methods will raise exceptions (except for optional nodes).
1973 For sparse traversals, where only certain node types are of interest, use
1974 subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform
1975 processing is desired, subclass `GenericNodeVisitor`.
1977 .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of
1978 Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA,
1979 1995.
1980 """
1982 optional = ('meta',)
1983 """
1984 Tuple containing node class names (as strings).
1986 No exception will be raised if writers do not implement visit
1987 or departure functions for these node classes.
1989 Used to ensure transitional compatibility with existing 3rd-party writers.
1990 """
1992 def __init__(self, document):
1993 self.document = document
1995 def dispatch_visit(self, node):
1996 """
1997 Call self."``visit_`` + node class name" with `node` as
1998 parameter. If the ``visit_...`` method does not exist, call
1999 self.unknown_visit.
2000 """
2001 node_name = node.__class__.__name__
2002 method = getattr(self, 'visit_' + node_name, self.unknown_visit)
2003 self.document.reporter.debug(
2004 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s'
2005 % (method.__name__, node_name))
2006 return method(node)
2008 def dispatch_departure(self, node):
2009 """
2010 Call self."``depart_`` + node class name" with `node` as
2011 parameter. If the ``depart_...`` method does not exist, call
2012 self.unknown_departure.
2013 """
2014 node_name = node.__class__.__name__
2015 method = getattr(self, 'depart_' + node_name, self.unknown_departure)
2016 self.document.reporter.debug(
2017 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s'
2018 % (method.__name__, node_name))
2019 return method(node)
2021 def unknown_visit(self, node):
2022 """
2023 Called when entering unknown `Node` types.
2025 Raise an exception unless overridden.
2026 """
2027 if (self.document.settings.strict_visitor
2028 or node.__class__.__name__ not in self.optional):
2029 raise NotImplementedError(
2030 '%s visiting unknown node type: %s'
2031 % (self.__class__, node.__class__.__name__))
2033 def unknown_departure(self, node):
2034 """
2035 Called before exiting unknown `Node` types.
2037 Raise exception unless overridden.
2038 """
2039 if (self.document.settings.strict_visitor
2040 or node.__class__.__name__ not in self.optional):
2041 raise NotImplementedError(
2042 '%s departing unknown node type: %s'
2043 % (self.__class__, node.__class__.__name__))
2046class SparseNodeVisitor(NodeVisitor):
2048 """
2049 Base class for sparse traversals, where only certain node types are of
2050 interest. When ``visit_...`` & ``depart_...`` methods should be
2051 implemented for *all* node types (such as for `docutils.writers.Writer`
2052 subclasses), subclass `NodeVisitor` instead.
2053 """
2056class GenericNodeVisitor(NodeVisitor):
2058 """
2059 Generic "Visitor" abstract superclass, for simple traversals.
2061 Unless overridden, each ``visit_...`` method calls `default_visit()`, and
2062 each ``depart_...`` method (when using `Node.walkabout()`) calls
2063 `default_departure()`. `default_visit()` (and `default_departure()`) must
2064 be overridden in subclasses.
2066 Define fully generic visitors by overriding `default_visit()` (and
2067 `default_departure()`) only. Define semi-generic visitors by overriding
2068 individual ``visit_...()`` (and ``depart_...()``) methods also.
2070 `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should
2071 be overridden for default behavior.
2072 """
2074 def default_visit(self, node):
2075 """Override for generic, uniform traversals."""
2076 raise NotImplementedError
2078 def default_departure(self, node):
2079 """Override for generic, uniform traversals."""
2080 raise NotImplementedError
2083def _call_default_visit(self, node):
2084 self.default_visit(node)
2087def _call_default_departure(self, node):
2088 self.default_departure(node)
2091def _nop(self, node):
2092 pass
2095def _add_node_class_names(names):
2096 """Save typing with dynamic assignments:"""
2097 for _name in names:
2098 setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit)
2099 setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure)
2100 setattr(SparseNodeVisitor, 'visit_' + _name, _nop)
2101 setattr(SparseNodeVisitor, 'depart_' + _name, _nop)
2104_add_node_class_names(node_class_names)
2107class TreeCopyVisitor(GenericNodeVisitor):
2109 """
2110 Make a complete copy of a tree or branch, including element attributes.
2111 """
2113 def __init__(self, document):
2114 GenericNodeVisitor.__init__(self, document)
2115 self.parent_stack = []
2116 self.parent = []
2118 def get_tree_copy(self):
2119 return self.parent[0]
2121 def default_visit(self, node):
2122 """Copy the current node, and make it the new acting parent."""
2123 newnode = node.copy()
2124 self.parent.append(newnode)
2125 self.parent_stack.append(self.parent)
2126 self.parent = newnode
2128 def default_departure(self, node):
2129 """Restore the previous acting parent."""
2130 self.parent = self.parent_stack.pop()
2133class TreePruningException(Exception):
2135 """
2136 Base class for `NodeVisitor`-related tree pruning exceptions.
2138 Raise subclasses from within ``visit_...`` or ``depart_...`` methods
2139 called from `Node.walk()` and `Node.walkabout()` tree traversals to prune
2140 the tree traversed.
2141 """
2144class SkipChildren(TreePruningException):
2146 """
2147 Do not visit any children of the current node. The current node's
2148 siblings and ``depart_...`` method are not affected.
2149 """
2152class SkipSiblings(TreePruningException):
2154 """
2155 Do not visit any more siblings (to the right) of the current node. The
2156 current node's children and its ``depart_...`` method are not affected.
2157 """
2160class SkipNode(TreePruningException):
2162 """
2163 Do not visit the current node's children, and do not call the current
2164 node's ``depart_...`` method.
2165 """
2168class SkipDeparture(TreePruningException):
2170 """
2171 Do not call the current node's ``depart_...`` method. The current node's
2172 children and siblings are not affected.
2173 """
2176class NodeFound(TreePruningException):
2178 """
2179 Raise to indicate that the target of a search has been found. This
2180 exception must be caught by the client; it is not caught by the traversal
2181 code.
2182 """
2185class StopTraversal(TreePruningException):
2187 """
2188 Stop the traversal altogether. The current node's ``depart_...`` method
2189 is not affected. The parent nodes ``depart_...`` methods are also called
2190 as usual. No other nodes are visited. This is an alternative to
2191 NodeFound that does not cause exception handling to trickle up to the
2192 caller.
2193 """
2196def make_id(string):
2197 """
2198 Convert `string` into an identifier and return it.
2200 Docutils identifiers will conform to the regular expression
2201 ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class"
2202 and "id" attributes) should have no underscores, colons, or periods.
2203 Hyphens may be used.
2205 - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens:
2207 ID and NAME tokens must begin with a letter ([A-Za-z]) and may be
2208 followed by any number of letters, digits ([0-9]), hyphens ("-"),
2209 underscores ("_"), colons (":"), and periods (".").
2211 - However the `CSS1 spec`_ defines identifiers based on the "name" token,
2212 a tighter interpretation ("flex" tokenizer notation; "latin1" and
2213 "escape" 8-bit characters have been replaced with entities)::
2215 unicode \\[0-9a-f]{1,4}
2216 latin1 [¡-ÿ]
2217 escape {unicode}|\\[ -~¡-ÿ]
2218 nmchar [-a-z0-9]|{latin1}|{escape}
2219 name {nmchar}+
2221 The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"),
2222 or periods ("."), therefore "class" and "id" attributes should not contain
2223 these characters. They should be replaced with hyphens ("-"). Combined
2224 with HTML's requirements (the first character must be a letter; no
2225 "unicode", "latin1", or "escape" characters), this results in the
2226 ``[a-z](-?[a-z0-9]+)*`` pattern.
2228 .. _HTML 4.01 spec: https://www.w3.org/TR/html401
2229 .. _CSS1 spec: https://www.w3.org/TR/REC-CSS1
2230 """
2231 id = string.lower()
2232 id = id.translate(_non_id_translate_digraphs)
2233 id = id.translate(_non_id_translate)
2234 # get rid of non-ascii characters.
2235 # 'ascii' lowercase to prevent problems with turkish locale.
2236 id = unicodedata.normalize(
2237 'NFKD', id).encode('ascii', 'ignore').decode('ascii')
2238 # shrink runs of whitespace and replace by hyphen
2239 id = _non_id_chars.sub('-', ' '.join(id.split()))
2240 id = _non_id_at_ends.sub('', id)
2241 return str(id)
2244_non_id_chars = re.compile('[^a-z0-9]+')
2245_non_id_at_ends = re.compile('^[-0-9]+|-+$')
2246_non_id_translate = {
2247 0x00f8: 'o', # o with stroke
2248 0x0111: 'd', # d with stroke
2249 0x0127: 'h', # h with stroke
2250 0x0131: 'i', # dotless i
2251 0x0142: 'l', # l with stroke
2252 0x0167: 't', # t with stroke
2253 0x0180: 'b', # b with stroke
2254 0x0183: 'b', # b with topbar
2255 0x0188: 'c', # c with hook
2256 0x018c: 'd', # d with topbar
2257 0x0192: 'f', # f with hook
2258 0x0199: 'k', # k with hook
2259 0x019a: 'l', # l with bar
2260 0x019e: 'n', # n with long right leg
2261 0x01a5: 'p', # p with hook
2262 0x01ab: 't', # t with palatal hook
2263 0x01ad: 't', # t with hook
2264 0x01b4: 'y', # y with hook
2265 0x01b6: 'z', # z with stroke
2266 0x01e5: 'g', # g with stroke
2267 0x0225: 'z', # z with hook
2268 0x0234: 'l', # l with curl
2269 0x0235: 'n', # n with curl
2270 0x0236: 't', # t with curl
2271 0x0237: 'j', # dotless j
2272 0x023c: 'c', # c with stroke
2273 0x023f: 's', # s with swash tail
2274 0x0240: 'z', # z with swash tail
2275 0x0247: 'e', # e with stroke
2276 0x0249: 'j', # j with stroke
2277 0x024b: 'q', # q with hook tail
2278 0x024d: 'r', # r with stroke
2279 0x024f: 'y', # y with stroke
2280}
2281_non_id_translate_digraphs = {
2282 0x00df: 'sz', # ligature sz
2283 0x00e6: 'ae', # ae
2284 0x0153: 'oe', # ligature oe
2285 0x0238: 'db', # db digraph
2286 0x0239: 'qp', # qp digraph
2287}
2290def dupname(node, name):
2291 node['dupnames'].append(name)
2292 node['names'].remove(name)
2293 # Assume that this method is referenced, even though it isn't; we
2294 # don't want to throw unnecessary system_messages.
2295 node.referenced = 1
2298def fully_normalize_name(name):
2299 """Return a case- and whitespace-normalized name."""
2300 return ' '.join(name.lower().split())
2303def whitespace_normalize_name(name):
2304 """Return a whitespace-normalized name."""
2305 return ' '.join(name.split())
2308def serial_escape(value):
2309 """Escape string values that are elements of a list, for serialization."""
2310 return value.replace('\\', r'\\').replace(' ', r'\ ')
2313def pseudo_quoteattr(value):
2314 """Quote attributes for pseudo-xml"""
2315 return '"%s"' % value