SF.net SVN: docutils:[9878 ] trunk/docutils/docutils/n odes.py
milde--- via Docutils-checkins <[email protected]>
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 9878
http://sourceforge.net/p/docutils/code/9878
Author: milde
Date: 2024-08-09 08:51:14 +0000 (Fri, 09 Aug 2024)
Log Message:
-----------
Fixups for nodes.py.
Alias defs for the auxiliary `update_fun` function, and
the Node subclasses (Element | Text). # TODO: replace this just using ``Node``?
Simplify type hint for Element.findall() and similar:
"condition" can be any "type" (although only Node subclasses make sense).
Fix type hint for Node.asdom() and document.asdom():
the "dom" argument may be module (`xml.dom.minidom` or compatible) or None.
Adapt formatting.
Modified Paths:
--------------
trunk/docutils/docutils/nodes.py
Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py 2024-08-09 07:52:36 UTC (rev 9877)
+++ trunk/docutils/docutils/nodes.py 2024-08-09 08:51:14 UTC (rev 9878)
@@ -31,25 +31,17 @@
import warnings
from collections import Counter
from typing import TYPE_CHECKING, overload
+# import xml.dom.minidom as dom # -> conditional import in Node.asdom()
+# and document.asdom()
+# import docutils.transforms # -> conditional import in document.__init__()
+
if TYPE_CHECKING:
- from collections.abc import (
- Callable,
- Iterable,
- Iterator,
- Mapping,
- Sequence,
- )
- from typing import (
- Any,
- ClassVar,
- Final,
- Literal,
- Self,
- SupportsIndex,
- TypeAlias,
- TypeVar,
- )
+ from collections.abc import (Callable, Iterable, Iterator,
+ Mapping, Sequence)
+ from types import ModuleType
+ from typing import (Any, ClassVar, Final, Literal, Self,
+ SupportsIndex, TypeAlias, TypeVar)
from xml.dom import minidom
from docutils.frontend import Values
@@ -56,23 +48,18 @@
from docutils.transforms import Transformer, Transform
from docutils.utils import Reporter
- _NodeT = TypeVar('_NodeT', bound='Node')
_DefaultT = TypeVar('_DefaultT')
_ContentModelCategory: TypeAlias = tuple['Element' | tuple['Element', ...]]
_ContentModelQuantifier = Literal['.', '?', '+', '*']
- _ContentModelItem: TypeAlias = tuple[
- _ContentModelCategory, _ContentModelQuantifier,
- ]
+ _ContentModelItem: TypeAlias = tuple[_ContentModelCategory,
+ _ContentModelQuantifier]
_ContentModelTuple: TypeAlias = tuple[_ContentModelItem, ...]
+ _UpdateFun: TypeAlias = Callable[[str, Any, bool], None]
+ _ElementT: TypeAlias = Element | Text # TODO: use Node instead?
-# import xml.dom.minidom as dom # -> conditional import in Node.asdom()
-# and document.asdom()
-# import docutils.transforms # -> conditional import in document.__init__()
-
-
# ==============================
# Functional Node Base Classes
# ==============================
@@ -83,7 +70,7 @@
parent: Element = None
"""Back-reference to the Node immediately containing this Node."""
- children: Sequence[Element | Text] # defined in subclasses
+ children: Sequence[_ElementT] # defined in subclasses
"""List of child nodes (Elements or Text)."""
source: str | os.PathLike[str] | None = None
@@ -120,9 +107,11 @@
"""
return True
- def asdom(
- self, dom: None = None,
- ) -> minidom.Document | minidom.Element | minidom.Text:
+ def asdom(self,
+ dom: ModuleType | None = None,
+ ) -> minidom.Document | minidom.Element | minidom.Text:
+ # TODO: minidom.Document is only returned by document.asdom()
+ # (which overwrites this base-class implementation)
"""Return a DOM **fragment** representation of this Node."""
if dom is None:
import xml.dom.minidom as dom
@@ -149,7 +138,7 @@
"""Return a string representation of this Node."""
raise NotImplementedError
- def setup_child(self, child: Element | Text) -> None:
+ def setup_child(self, child: _ElementT) -> None:
child.parent = self
if self.document:
child.document = self.document
@@ -247,7 +236,7 @@
visitor.dispatch_departure(self)
return stop
- def _fast_findall(self, cls: type[_NodeT]) -> Iterator[_NodeT]:
+ def _fast_findall(self, cls: type) -> Iterator[Node]:
"""Return iterator that only supports instance checks."""
if isinstance(self, cls):
yield self
@@ -254,7 +243,7 @@
for child in self.children:
yield from child._fast_findall(cls)
- def _superfast_findall(self) -> Iterator[Element | Text]:
+ def _superfast_findall(self) -> Iterator[Node]:
"""Return iterator that doesn't check for a condition."""
# This is different from ``iter(self)`` implemented via
# __getitem__() and __len__() in the Element subclass,
@@ -263,14 +252,13 @@
for child in self.children:
yield from child._superfast_findall()
- def findall(
- self,
- condition: type[_NodeT] | Callable[[Node], bool] | None = None,
- include_self: bool = True,
- descend: bool = True,
- siblings: bool = False,
- ascend: bool = False,
- ) -> Iterator[Element | Text]:
+ def findall(self,
+ condition: Callable[[Node], bool] | type | None = None,
+ include_self: bool = True,
+ descend: bool = True,
+ siblings: bool = False,
+ ascend: bool = False,
+ ) -> Iterator[Node]:
"""
Return an iterator yielding nodes following `self`:
@@ -283,7 +271,7 @@
If `condition` is not None, the iterator yields only nodes
for which ``condition(node)`` is true. If `condition` is a
- node class ``cls``, it is equivalent to a function consisting
+ type ``cls``, it is equivalent to a function consisting
of ``return isinstance(node, cls)``.
If `ascend` is true, assume `siblings` to be true as well.
@@ -352,14 +340,13 @@
else:
node = node.parent
- def traverse(
- self,
- condition: type[_NodeT] | Callable[[Node], bool] | None = None,
- include_self: bool = True,
- descend: bool = True,
- siblings: bool = False,
- ascend: bool = False,
- ) -> list[Element | Text]:
+ def traverse(self,
+ condition: Callable[[Node], bool] | type | None = None,
+ include_self: bool = True,
+ descend: bool = True,
+ siblings: bool = False,
+ ascend: bool = False,
+ ) -> list[Node]:
"""Return list of nodes following `self`.
For looping, Node.findall() is faster and more memory efficient.
@@ -370,14 +357,13 @@
return list(self.findall(condition, include_self, descend,
siblings, ascend))
- def next_node(
- self,
- condition: type[_NodeT] | Callable[[Node], bool] | None = None,
- include_self: bool = False,
- descend: bool = True,
- siblings: bool = False,
- ascend: bool = False,
- ) -> Element | Text | None:
+ def next_node(self,
+ condition: Callable[[Node], bool] | type | None = None,
+ include_self: bool = False,
+ descend: bool = True,
+ siblings: bool = False,
+ ascend: bool = False,
+ ) -> Node | None:
"""
Return the first node in the iterator returned by findall(),
or None if the iterable is empty.
@@ -577,12 +563,11 @@
child_text_separator: Final = '\n\n'
"""Separator for child nodes, used by `astext()` method."""
- def __init__(
- self,
- rawsource: str = '',
- *children: Element | Text,
- **attributes: Any,
- ) -> None:
+ def __init__(self,
+ rawsource: str = '',
+ *children: _ElementT,
+ **attributes: Any,
+ ) -> None:
self.rawsource = rawsource
"""The raw text from which this element was constructed.
@@ -593,7 +578,7 @@
if isinstance(rawsource, Element):
raise TypeError('First argument "rawsource" must be a string.')
- self.children: list[Element | Text] = []
+ self.children: list[_ElementT] = []
"""List of child nodes (elements and/or `Text`)."""
self.extend(children) # maintain parent info
@@ -684,7 +669,7 @@
def __len__(self) -> int:
return len(self.children)
- def __contains__(self, key: str | Element | Text) -> bool:
+ def __contains__(self, key: str | _ElementT) -> bool:
# Test for both, children and attributes with operator ``in``.
if isinstance(key, str):
return key in self.attributes
@@ -695,16 +680,16 @@
...
@overload
- def __getitem__(self, key: int) -> Element | Text:
+ def __getitem__(self, key: int) -> _ElementT:
...
@overload
- def __getitem__(self, key: slice) -> list[Element | Text]:
+ def __getitem__(self, key: slice) -> list[_ElementT]:
...
- def __getitem__(
- self, key: str | int | slice,
- ) -> Element | Text | list[Element | Text] | Any:
+ def __getitem__(self,
+ key: str | int | slice,
+ ) -> _ElementT | list[_ElementT] | Any:
if isinstance(key, str):
return self.attributes[key]
elif isinstance(key, int):
@@ -721,11 +706,11 @@
...
@overload
- def __setitem__(self, key: int, item: Element | Text) -> None:
+ def __setitem__(self, key: int, item: _ElementT) -> None:
...
@overload
- def __setitem__(self, key: slice, item: Iterable[Element | Text]) -> None:
+ def __setitem__(self, key: slice, item: Iterable[_ElementT]) -> None:
...
def __setitem__(self, key, item) -> None:
@@ -755,15 +740,13 @@
raise TypeError('element index must be an integer, a simple '
'slice, or an attribute name string')
- def __add__(self, other: list[Element | Text]) -> list[Element | Text]:
+ def __add__(self, other: list[_ElementT]) -> list[_ElementT]:
return self.children + other
- def __radd__(self, other: list[Element | Text]) -> list[Element | Text]:
+ def __radd__(self, other: list[_ElementT]) -> list[_ElementT]:
return other + self.children
- def __iadd__(
- self, other: Element | Text | Iterable[Element | Text],
- ) -> Self:
+ def __iadd__(self, other: _ElementT | Iterable[_ElementT]) -> Self:
"""Append a node or a list of nodes to `self.children`."""
if isinstance(other, Node):
self.append(other)
@@ -776,10 +759,8 @@
[child.astext() for child in self.children])
def non_default_attributes(self) -> dict[str, Any]:
- atts = {
- key: value for key, value in self.attributes.items()
- if self.is_not_default(key)
- }
+ atts = {key: value for key, value in self.attributes.items()
+ if self.is_not_default(key)}
return atts
def attlist(self) -> list[tuple[str, Any]]:
@@ -831,19 +812,18 @@
except AttributeError:
return fallback
- def append(self, item: Element | Text) -> None:
+ def append(self, item: _ElementT) -> None:
self.setup_child(item)
self.children.append(item)
- def extend(self, item: Iterable[Element | Text]) -> None:
+ def extend(self, item: Iterable[_ElementT]) -> None:
for node in item:
self.append(node)
- def insert(
- self,
- index: SupportsIndex,
- item: Element | Text | Iterable[Element | Text],
- ) -> None:
+ def insert(self,
+ index: SupportsIndex,
+ item: _ElementT | Iterable[_ElementT],
+ ) -> None:
if isinstance(item, Node):
self.setup_child(item)
self.children.insert(index, item)
@@ -850,18 +830,20 @@
elif item is not None:
self[index:index] = item
- def pop(self, i: int = -1) -> Element | Text:
+ def pop(self, i: int = -1) -> _ElementT:
return self.children.pop(i)
- def remove(self, item: Element | Text) -> None:
+ def remove(self, item: _ElementT) -> None:
self.children.remove(item)
- def index(
- self, item: Element | Text, start: int = 0, stop: int = sys.maxsize,
- ) -> int:
+ def index(self,
+ item: _ElementT,
+ start: int = 0,
+ stop: int = sys.maxsize,
+ ) -> int:
return self.children.index(item, start, stop)
- def previous_sibling(self) -> Element | Text | None:
+ def previous_sibling(self) -> _ElementT | None:
"""Return preceding sibling node or ``None``."""
try:
i = self.parent.index(self)
@@ -901,8 +883,7 @@
self[attr].append(value)
def coerce_append_attr_list(
- self, attr: str, value: list[Any] | Any,
- ) -> None:
+ self, attr: str, value: list[Any] | Any) -> None:
"""
First, convert both self[attr] and value to a non-string sequence
type; if either is not already a sequence, convert it to a list of one
@@ -927,8 +908,7 @@
self[attr] = value
def copy_attr_convert(
- self, attr: str, value: Any, replace: bool = True,
- ) -> None:
+ self, attr: str, value: Any, replace: bool = True) -> None:
"""
If attr is an attribute of self, set self[attr] to
[self[attr], value], otherwise set self[attr] to value.
@@ -956,8 +936,7 @@
self.replace_attr(attr, value, replace)
def copy_attr_concatenate(
- self, attr: str, value: Any, replace: bool,
- ) -> None:
+ self, attr: str, value: Any, replace: bool) -> None:
"""
If attr is an attribute of self and both self[attr] and value are
lists, concatenate the two sequences, setting the result to
@@ -973,8 +952,7 @@
self.replace_attr(attr, value, replace)
def copy_attr_consistent(
- self, attr: str, value: Any, replace: bool,
- ) -> None:
+ self, attr: str, value: Any, replace: bool) -> None:
"""
If replace is True or self[attr] is None, replace self[attr] with
value. Otherwise, do nothing.
@@ -982,13 +960,12 @@
if self.get(attr) is not value:
self.replace_attr(attr, value, replace)
- def update_all_atts(
- self,
- dict_: Mapping[str, Any] | Element,
- update_fun: Callable[[str, Any, bool], None] = copy_attr_consistent,
- replace: bool = True,
- and_source: bool = False,
- ) -> None:
+ def update_all_atts(self,
+ dict_: Mapping[str, Any] | Element,
+ update_fun: _UpdateFun = copy_attr_consistent,
+ replace: bool = True,
+ and_source: bool = False,
+ ) -> None:
"""
Updates all attributes from node or dictionary `dict_`.
@@ -1025,12 +1002,11 @@
for att in filter(filter_fun, dict_):
update_fun(self, att, dict_[att], replace)
- def update_all_atts_consistantly(
- self,
- dict_: Mapping[str, Any] | Element,
- replace: bool = True,
- and_source: bool = False,
- ) -> None:
+ def update_all_atts_consistantly(self,
+ dict_: Mapping[str, Any] | Element,
+ replace: bool = True,
+ and_source: bool = False,
+ ) -> None:
"""
Updates all attributes from node or dictionary `dict_`.
@@ -1050,12 +1026,11 @@
self.update_all_atts(dict_, Element.copy_attr_consistent, replace,
and_source)
- def update_all_atts_concatenating(
- self,
- dict_: Mapping[str, Any] | Element,
- replace: bool = True,
- and_source: bool = False,
- ) -> None:
+ def update_all_atts_concatenating(self,
+ dict_: Mapping[str, Any] | Element,
+ replace: bool = True,
+ and_source: bool = False,
+ ) -> None:
"""
Updates all attributes from node or dictionary `dict_`.
@@ -1078,12 +1053,11 @@
self.update_all_atts(dict_, Element.copy_attr_concatenate, replace,
and_source)
- def update_all_atts_coercion(
- self,
- dict_: Mapping[str, Any] | Element,
- replace: bool = True,
- and_source: bool = False,
- ) -> None:
+ def update_all_atts_coercion(self,
+ dict_: Mapping[str, Any] | Element,
+ replace: bool = True,
+ and_source: bool = False,
+ ) -> None:
"""
Updates all attributes from node or dictionary `dict_`.
@@ -1107,11 +1081,10 @@
self.update_all_atts(dict_, Element.copy_attr_coerce, replace,
and_source)
- def update_all_atts_convert(
- self,
- dict_: Mapping[str, Any] | Element,
- and_source: bool = False,
- ) -> None:
+ def update_all_atts_convert(self,
+ dict_: Mapping[str, Any] | Element,
+ and_source: bool = False,
+ ) -> None:
"""
Updates all attributes from node or dictionary `dict_`.
@@ -1135,11 +1108,10 @@
def clear(self) -> None:
self.children = []
- def replace(
- self,
- old: Element | Text,
- new: Element | Text | Iterable[Element | Text],
- ) -> None:
+ def replace(self,
+ old: _ElementT,
+ new: _ElementT | Iterable[_ElementT],
+ ) -> None:
"""Replace one child `Node` with another child or children."""
index = self.index(old)
if isinstance(new, Node):
@@ -1148,9 +1120,7 @@
elif new is not None:
self[index:index+1] = new
- def replace_self(
- self, new: Element | Text | Sequence[Element | Text],
- ) -> None:
+ def replace_self(self, new: _ElementT | Sequence[_ElementT]) -> None:
"""
Replace `self` node with `new`, where `new` is a node or a
list of nodes.
@@ -1174,13 +1144,12 @@
'Losing "%s" attribute: %s' % (att, self[att])
self.parent.replace(self, new)
- def first_child_matching_class(
- self,
- childclass: type[Element] | type[Text]
- | tuple[type[Element] | type[Text], ...],
- start: int = 0,
- end: int = sys.maxsize,
- ) -> int | None:
+ def first_child_matching_class(self,
+ childclass: type[Element] | type[Text]
+ | tuple[type[Element] | type[Text], ...],
+ start: int = 0,
+ end: int = sys.maxsize,
+ ) -> int | None:
"""
Return the index of the first child whose class exactly matches.
@@ -1200,12 +1169,12 @@
return None
def first_child_not_matching_class(
- self,
- childclass: type[Element] | type[Text]
- | tuple[type[Element] | type[Text], ...],
- start: int = 0,
- end: int = sys.maxsize,
- ) -> int | None:
+ self,
+ childclass: type[Element] | type[Text]
+ | tuple[type[Element] | type[Text], ...],
+ start: int = 0,
+ end: int = sys.maxsize,
+ ) -> int | None:
"""
Return the index of the first child whose class does *not* match.
@@ -1243,9 +1212,10 @@
copy.extend([child.deepcopy() for child in self.children])
return copy
- def note_referenced_by(
- self, name: str | None = None, id: str | None = None,
- ) -> None:
+ def note_referenced_by(self,
+ name: str | None = None,
+ id: str | None = None,
+ ) -> None:
"""Note that this Element has been referenced by its name
`name` or id `id`."""
self.referenced = True
@@ -1307,11 +1277,10 @@
+ '\n '.join(messages),
problematic_element=self)
- def validate_content(
- self,
- model: _ContentModelTuple | None = None,
- elements: Sequence[Element | Text] | None = None,
- ) -> list[Element | Text]:
+ def validate_content(self,
+ model: _ContentModelTuple | None = None,
+ elements: Sequence[_ElementT] | None = None,
+ ) -> list[_ElementT]:
"""Test compliance of `elements` with `model`.
:model: content model description, default `self.content_model`,
@@ -1350,11 +1319,10 @@
child = None
return [] if child is None else [child, *ichildren]
- def _report_child(
- self,
- child: Element | Text | None,
- category: Element | Iterable[Element],
- ) -> str:
+ def _report_child(self,
+ child: _ElementT | None,
+ category: Element | Iterable[Element],
+ ) -> str:
# Return a str reporting a missing child or child of wrong category.
try:
_type = category.__name__
@@ -1550,21 +1518,18 @@
If passing children to `__init__()`, make sure to set `text` to
``''`` or some other suitable value.
"""
- content_model: Final = (
- # (#PCDATA | %inline.elements;)*
- ((Text, Inline), '*'),
- )
+ content_model: Final = (((Text, Inline), '*'),)
+ # (#PCDATA | %inline.elements;)*
child_text_separator: Final = ''
"""Separator for child nodes, used by `astext()` method."""
- def __init__(
- self,
- rawsource: str = '',
- text: str = '',
- *children: Element | Text,
- **attributes: Any,
- ) -> None:
+ def __init__(self,
+ rawsource: str = '',
+ text: str = '',
+ *children: _ElementT,
+ **attributes: Any,
+ ) -> None:
if text:
textnode = Text(text)
Element.__init__(self, rawsource, textnode, *children,
@@ -1578,13 +1543,12 @@
valid_attributes: Final = Element.valid_attributes + ('xml:space',)
- def __init__(
- self,
- rawsource: str = '',
- text: str = '',
- *children: Element | Text,
- **attributes: Any,
- ) -> None:
+ def __init__(self,
+ rawsource: str = '',
+ text: str = '',
+ *children: _ElementT,
+ **attributes: Any,
+ ) -> None:
super().__init__(rawsource, text, *children, **attributes)
self.attributes['xml:space'] = 'preserve'
@@ -1635,19 +1599,16 @@
class docinfo(SubStructural, Element):
"""Container for displayed document meta-data."""
- content_model: Final = (
- # (%bibliographic.elements;)+
- (Bibliographic, '+'),
- )
+ content_model: Final = ((Bibliographic, '+'),)
+ # (%bibliographic.elements;)+
class decoration(PreBibliographic, SubStructural, Element):
"""Container for `header` and `footer`."""
- content_model: Final = (
- # (header?, footer?)
- (header, '?'), # Empty element does not make sense,
- (footer, '?'), # but is simpler to define.
- )
+ content_model: Final = ((header, '?'), # Empty element doesn't make sense,
+ (footer, '?'), # but is simpler to define.
+ )
+ # (header?, footer?)
def get_header(self) -> header:
if not len(self.children) or not isinstance(self.children[0], header):
@@ -1700,11 +1661,8 @@
__ https://docutils.sourceforge.io/docs/ref/doctree.html#topic
"""
- content_model: Final = (
- # (title?, (%body.elements;)+)
- (title, '?'),
- (Body, '+'),
- )
+ content_model: Final = ((title, '?'), (Body, '+'))
+ # (title?, (%body.elements;)+)
class sidebar(Structural, Element):
@@ -1716,12 +1674,11 @@
__ https://docutils.sourceforge.io/docs/ref/doctree.html#sidebar
"""
- content_model: Final = (
- # ((title, subtitle?)?, (%body.elements; | topic)+)
- (title, '?'),
- (subtitle, '?'),
- ((topic, Body), '+'),
- )
+ content_model: Final = ((title, '?'),
+ (subtitle, '?'),
+ ((topic, Body), '+'),
+ )
+ # ((title, subtitle?)?, (%body.elements; | topic)+)
# "subtitle only after title" is ensured in `subtitle.check_position()`.
@@ -1733,13 +1690,12 @@
# recursive content model, see below
-section.content_model = (
- # (title, subtitle?, %structure.model;)
- (title, '.'),
- (subtitle, '?'),
- ((Body, topic, sidebar, transition), '*'),
- ((section, transition), '*'),
-)
+section.content_model = ((title, '.'),
+ (subtitle, '?'),
+ ((Body, topic, sidebar, transition), '*'),
+ ((section, transition), '*'),
+ )
+# (title, subtitle?, %structure.model;)
# Correct transition placement is ensured in `transition.check_position()`.
@@ -1754,31 +1710,29 @@
`docutils.utils.new_document()` instead.
"""
valid_attributes: Final = Element.valid_attributes + ('title',)
- content_model: Final = (
- # ( (title, subtitle?)?,
- # meta*,
- # decoration?,
- # (docinfo, transition?)?,
- # %structure.model; )
- (title, '?'),
- (subtitle, '?'),
- (meta, '*'),
- (decoration, '?'),
- (docinfo, '?'),
- (transition, '?'),
- ((Body, topic, sidebar, transition), '*'),
- ((section, transition), '*'),
- )
+ content_model: Final = ((title, '?'),
+ (subtitle, '?'),
+ (meta, '*'),
+ (decoration, '?'),
+ (docinfo, '?'),
+ (transition, '?'),
+ ((Body, topic, sidebar, transition), '*'),
+ ((section, transition), '*'),
+ )
+ # ( (title, subtitle?)?,
+ # meta*,
+ # decoration?,
+ # (docinfo, transition?)?,
+ # %structure.model; )
# Additional restrictions for `subtitle` and `transition` are tested
# with the respective `check_position()` methods.
- def __init__(
- self,
- settings: Values,
- reporter: Reporter,
- *args: Element | Text,
- **kwargs: Any,
- ) -> None:
+ def __init__(self,
+ settings: Values,
+ reporter: Reporter,
+ *args: _ElementT,
+ **kwargs: Any,
+ ) -> None:
Element.__init__(self, *args, **kwargs)
self.current_source: str | os.PathLike[str] | None = None
@@ -1862,7 +1816,7 @@
self.transformer: Transformer = docutils.transforms.Transformer(self)
"""Storage for transforms to be applied to this document."""
- self.include_log: list[tuple[str | os.PathLike[str], tuple]] = []
+ self.include_log: list[tuple[str|os.PathLike[str], tuple]] = []
"""The current source's parents (to detect inclusion loops)."""
self.decoration: decoration | None = None
@@ -1879,7 +1833,7 @@
state['transformer'] = None
return state
- def asdom(self, dom: None = None) -> minidom.Document:
+ def asdom(self, dom: ModuleType | None = None) -> minidom.Document:
"""Return a DOM representation of this document."""
if dom is None:
import xml.dom.minidom as dom
@@ -1887,12 +1841,11 @@
domroot.appendChild(self._dom_node(domroot))
return domroot
- def set_id(
- self,
- node: Element,
- msgnode: Element | None = None,
- suggested_prefix: str = '',
- ) -> str:
+ def set_id(self,
+ node: Element,
+ msgnode: Element | None = None,
+ suggested_prefix: str = '',
+ ) -> str:
if node['ids']:
# register and check for duplicates
for id in node['ids']:
@@ -1937,13 +1890,12 @@
self.ids[id] = node
return id
- def set_name_id_map(
- self,
- node: Element,
- id: str,
- msgnode: Element | None = None,
- explicit: bool = False,
- ) -> None:
+ def set_name_id_map(self,
+ node: Element,
+ id: str,
+ msgnode: Element | None = None,
+ explicit: bool = False,
+ ) -> None:
"""
`self.nameids` maps names to IDs, while `self.nametypes` maps names to
booleans representing hyperlink type (True==explicit,
@@ -1984,14 +1936,13 @@
self.nameids[name] = id
self.nametypes[name] = explicit
- def set_duplicate_name_id(
- self,
- node: Element,
- id: str,
- name: str,
- msgnode: Element,
- explicit: bool,
- ) -> None:
+ def set_duplicate_name_id(self,
+ node: Element,
+ id: str,
+ name: str,
+ msgnode: Element,
+ explicit: bool,
+ ) -> None:
old_id = self.nameids[name]
old_explicit = self.nametypes[name]
self.nametypes[name] = old_explicit or explicit
@@ -2038,14 +1989,12 @@
# "note" here is an imperative verb: "take note of".
def note_implicit_target(
- self, target: Element, msgnode: Element | None = None,
- ) -> None:
+ self, target: Element, msgnode: Element | None = None) -> None:
id = self.set_id(target, msgnode)
self.set_name_id_map(target, id, msgnode, explicit=False)
def note_explicit_target(
- self, target: Element, msgnode: Element | None = None,
- ) -> None:
+ self, target: Element, msgnode: Element | None = None) -> None:
id = self.set_id(target, msgnode)
self.set_name_id_map(target, id, msgnode, explicit=True)
@@ -2096,12 +2045,11 @@
self.citation_refs.setdefault(ref['refname'], []).append(ref)
self.note_refname(ref)
- def note_substitution_def(
- self,
- subdef: substitution_definition,
- def_name: str,
- msgnode: Element | None = None,
- ) -> None:
+ def note_substitution_def(self,
+ subdef: substitution_definition,
+ def_name: str,
+ msgnode: Element | None = None,
+ ) -> None:
name = whitespace_normalize_name(def_name)
if name in self.substitution_defs:
msg = self.reporter.error(
@@ -2116,14 +2064,14 @@
# case-insensitive mapping:
self.substitution_names[fully_normalize_name(name)] = name
- def note_substitution_ref(
- self, subref: substitution_reference, refname: str,
- ) -> None:
+ def note_substitution_ref(self,
+ subref: substitution_reference,
+ refname: str,
+ ) -> None:
subref['refname'] = whitespace_normalize_name(refname)
def note_pending(
- self, pending: pending, priority: int | None = None,
- ) -> None:
+ self, pending: pending, priority: int | None = None) -> None:
self.transformer.add_pending(pending, priority)
def note_parse_message(self, message: system_message) -> None:
@@ -2132,11 +2080,10 @@
def note_transform_message(self, message: system_message) -> None:
self.transform_messages.append(message)
- def note_source(
- self,
- source: str | os.PathLike[str] | None,
- offset: int | None,
- ) -> None:
+ def note_source(self,
+ source: str | os.PathLike[str] | None,
+ offset: int | None,
+ ) -> None:
self.current_source = source and os.fspath(source)
if offset is None:
self.current_line = offset
@@ -2178,19 +2125,17 @@
class authors(Bibliographic, Element):
"""Container for author information for documents with multiple authors.
"""
- content_model: Final = (
- # (author, organization?, address?, contact?)+
- (author, '+'),
- (organization, '?'),
- (address, '?'),
- (contact, '?'),
- )
+ content_model: Final = ((author, '+'),
+ (organization, '?'),
+ (address, '?'),
+ (contact, '?'),
+ )
+ # (author, organization?, address?, contact?)+
- def validate_content(
- self,
- model: _ContentModelTuple | None = None,
- elements: Sequence[Element | Text] | None = None,
- ) -> list[Element | Text]:
+ def validate_content(self,
+ model: _ContentModelTuple | None = None,
+ elements: Sequence[_ElementT] | None = None,
+ ) -> list[_ElementT]:
"""Repeatedly test for children matching the content model.
Provisional.
@@ -2227,11 +2172,8 @@
class block_quote(General, Element):
"""An extended quotation, set off from the main text."""
- content_model: Final = (
- # ((%body.elements;)+, attribution?)
- (Body, '+'),
- (attribution, '?'),
- )
+ content_model: Final = ((Body, '+'), (attribution, '?'))
+ # ((%body.elements;)+, attribution?)
# Lists
@@ -2264,12 +2206,11 @@
class definition_list_item(Part, Element):
- content_model: Final = (
- # ((term, classifier*)+, definition)
- (term, '.'),
- ((classifier, term), '*'),
- (definition, '.'),
- )
+ content_model: Final = ((term, '.'),
+ ((classifier, term), '*'),
+ (definition, '.'),
+ )
+ # ((term, classifier*)+, definition)
class definition_list(Sequential, Element):
@@ -2278,10 +2219,8 @@
Can be used for glossaries or dictionaries, to describe or
classify things, for dialogues, or to itemize subtopics.
"""
- content_model: Final = (
- # (definition_list_item+)
- (definition_list_item, '+'),
- )
+ content_model: Final = ((definition_list_item, '+'),)
+ # (definition_list_item+)
class field_name(Part, TextElement): pass
@@ -2292,11 +2231,8 @@
class field(Part, Bibliographic, Element):
- content_model: Final = (
- # (field_name, field_body)
- (field_name, '.'),
- (field_body, '.'),
- )
+ content_model: Final = ((field_name, '.'), (field_body, '.'))
+ # (field_name, field_body)
class field_list(Sequential, Element):
@@ -2326,11 +2262,8 @@
Groups an option string with zero or more option argument placeholders.
"""
child_text_separator: Final = ''
- content_model: Final = (
- # (option_string, option_argument*)
- (option_string, '.'),
- (option_argument, '*'),
- )
+ content_model: Final = ((option_string, '.'), (option_argument, '*'))
+ # (option_string, option_argument*)
class option_group(Part, Element):
@@ -2348,11 +2281,8 @@
"""Container for a pair of `option_group` and `description` elements.
"""
child_text_separator: Final = ' '
- content_model: Final = (
- # (option_group, description)
- (option_group, '.'),
- (description, '.'),
- )
+ content_model: Final = ((option_group, '.'), (description, '.'))
+ # (option_group, description)
class option_list(Sequential, Element):
@@ -2401,11 +2331,8 @@
class admonition(Admonition, Element):
- content_model: Final = (
- # (title, (%body.elements;)+)
- (title, '.'),
- (Body, '+'),
- )
+ content_model: Final = ((title, '.'), (Body, '+'))
+ # (title, (%body.elements;)+)
# Footnote and citation
@@ -2418,36 +2345,14 @@
class footnote(General, BackLinkable, Element, Labeled, Targetable):
"""Labelled note providing additional context (footnote or endnote)."""
valid_attributes: Final = Element.valid_attributes + ('auto', 'backrefs')
- content_model: Final = (
- # (label?, (%body.elements;)+)
- (label, '?'),
- (Body, '+'),
- )
- # TODO: Why is the label optional and content required?
- # The rST specification says: "Each footnote consists of an
- # explicit markup start (".. "), a left square bracket,
- # the footnote label, a right square bracket, and whitespace,
- # followed by indented body elements."
- #
- # The `Labeled` parent class' docstring says:
- # "Contains a `label` as its first element."
- #
- # docutils.dtd requires both label and content but the rST parser
- # allows empty footnotes (see test_writers/test_latex2e.py).
- # Should the rST parser complain (info, warning or error)?
+ content_model: Final = ((label, '?'), (Body, '+'))
+ # (label?, (%body.elements;)+)
+ # The label will become required in Docutils 1.0.
class citation(General, BackLinkable, Element, Labeled, Targetable):
- content_model: Final = (
- # (label, (%body.elements;)+)
- (label, '.'),
- (Body, '+'),
- )
- # TODO: docutils.dtd requires both label and content but the rST parser
- # allows empty citation (see test_rst/test_citations.py).
- # Is this sensible?
- # The rST specification says: "Citations are identical to footnotes
- # except that they use only non-numeric labels such as [note] …"
+ content_model: Final = ((label, '.'), (Body, '+'))
+ # (label, (%body.elements;)+)
# Graphical elements
@@ -2476,15 +2381,14 @@
class figure(General, Element):
"""A formal figure, generally an illustration, with a title."""
valid_attributes: Final = Element.valid_attributes + ('align', 'width')
- content_model: Final = (
- # (image, ((caption, legend?) | legend))
- (image, '.'),
- (caption, '?'),
- (legend, '?'),
- )
+ content_model: Final = ((image, '.'),
+ (caption, '?'),
+ (legend, '?'),
+ )
+ # (image, ((caption, legend?) | legend))
# TODO: According to the DTD, a caption or legend is required
# but rST allows "bare" figures which are formatted differently from
- # images (floating in LaTeX, nested in a <figure> in HTML).
+ # images (floating in LaTeX, nested in a <figure> in HTML). [bugs: #489]
# Tables
@@ -2495,10 +2399,8 @@
valid_attributes: Final = Element.valid_attributes + (
'align', 'char', 'charoff', 'colname', 'colsep', 'morecols',
'morerows', 'namest', 'nameend', 'rowsep', 'valign')
- content_model: Final = (
- # %tbl.entry.mdl -> (%body.elements;)*
- (Body, '*'),
- )
+ content_model: Final = ((Body, '*'),)
+ # %tbl.entry.mdl -> (%body.elements;)*
class row(Part, Element):
@@ -2530,12 +2432,8 @@
"""A portion of a table. Most tables have just one `tgroup`."""
valid_attributes: Final = Element.valid_attributes + (
'align', 'cols', 'colsep', 'rowsep')
- content_model: Final = (
- # (colspec*, thead?, tbody)
- (colspec, '*'),
- (thead, '?'),
- (tbody, '.'),
- )
+ content_model: Final = ((colspec, '*'), (thead, '?'), (tbody, '.'))
+ # (colspec*, thead?, tbody)
class table(General, Element):
@@ -2542,11 +2440,8 @@
"""A data arrangement with rows and columns."""
valid_attributes: Final = Element.valid_attributes + (
'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width')
- content_model: Final = (
- # (title?, tgroup+)
- (title, '?'),
- (tgroup, '+'),
- )
+ content_model: Final = ((title, '?'), (tgroup, '+'))
+ # (title?, tgroup+)
# Special purpose elements
@@ -2577,12 +2472,11 @@
'level', 'line', 'type')
content_model: Final = ((Body, '+'),) # (%body.elements;)+
- def __init__(
- self,
- message: str | None = None,
- *children: Element | Text,
- **attributes: Any,
- ) -> None:
+ def __init__(self,
+ message: str | None = None,
+ *children: _ElementT,
+ **attributes: Any,
+ ) -> None:
rawsource = attributes.pop('rawsource', '')
if message:
p = paragraph('', message)
@@ -2628,14 +2522,13 @@
transforms.
"""
- def __init__(
- self,
- transform: Transform,
- details: Mapping[str, Any] | None = None,
- rawsource: str = '',
- *children: Element | Text,
- **attributes: Any,
- ) -> None:
+ def __init__(self,
+ transform: Transform,
+ details: Mapping[str, Any] | None = None,
+ rawsource: str = '',
+ *children: _ElementT,
+ **attributes: Any,
+ ) -> None:
Element.__init__(self, rawsource, *children, **attributes)
self.transform: Transform = transform
@@ -2806,7 +2699,7 @@
def __init__(self, document: document, /) -> None:
self.document: document = document
- def dispatch_visit(self, node: Element | Text) -> None:
+ def dispatch_visit(self, node: _ElementT) -> None:
"""
Call self."``visit_`` + node class name" with `node` as
parameter. If the ``visit_...`` method does not exist, call
@@ -2819,7 +2712,7 @@
% (method.__name__, node_name))
return method(node)
- def dispatch_departure(self, node: Element | Text) -> None:
+ def dispatch_departure(self, node: _ElementT) -> None:
"""
Call self."``depart_`` + node class name" with `node` as
parameter. If the ``depart_...`` method does not exist, call
@@ -2832,7 +2725,7 @@
% (method.__name__, node_name))
return method(node)
- def unknown_visit(self, node: Element | Text) -> None:
+ def unknown_visit(self, node: _ElementT) -> None:
"""
Called when entering unknown `Node` types.
@@ -2844,7 +2737,7 @@
'%s visiting unknown node type: %s'
% (self.__class__, node.__class__.__name__))
- def unknown_departure(self, node: Element | Text) -> None:
+ def unknown_departure(self, node: _ElementT) -> None:
"""
Called before exiting unknown `Node` types.
@@ -2883,33 +2776,24 @@
be overridden for default behavior.
"""
- def default_visit(self, node: Element | Text):
+ def default_visit(self, node: _ElementT):
"""Override for generic, uniform traversals."""
raise NotImplementedError
- def default_departure(self, node: Element | Text):
+ def default_departure(self, node: _ElementT):
"""Override for generic, uniform traversals."""
raise NotImplementedError
-def _call_default_visit(
- self: GenericNodeVisitor,
- node: Element | Text,
-) -> None:
+def _call_default_visit(self: GenericNodeVisitor, node: _ElementT) -> None:
self.default_visit(node)
-def _call_default_departure(
- self: GenericNodeVisitor,
- node: Element | Text,
-) -> None:
+def _call_default_departure(self: GenericNodeVisitor, node: _ElementT) -> None:
self.default_departure(node)
-def _nop(
- self: SparseNodeVisitor,
- node: Element | Text,
-) -> None:
+def _nop(self: SparseNodeVisitor, node: _ElementT) -> None:
pass
@@ -2932,13 +2816,13 @@
def __init__(self, document: document) -> None:
super().__init__(document)
- self.parent_stack: list[list[Element | Text]] = []
- self.parent: list[Element | Text] = []
+ self.parent_stack: list[list[_ElementT]] = []
+ self.parent: list[_ElementT] = []
- def get_tree_copy(self) -> Element | Text:
+ def get_tree_copy(self) -> _ElementT:
return self.parent[0]
- def default_visit(self, node: Element | Text) -> None:
+ def default_visit(self, node: _ElementT) -> None:
"""Copy the current node, and make it the new acting parent."""
newnode = node.copy()
self.parent.append(newnode)
@@ -2945,7 +2829,7 @@
self.parent_stack.append(self.parent)
self.parent = newnode
- def default_departure(self, node: Element | Text) -> None:
+ def default_departure(self, node: _ElementT) -> None:
"""Restore the previous acting parent."""
self.parent = self.parent_stack.pop()
@@ -3017,11 +2901,10 @@
# definition moved here from `utils` to avoid circular import dependency
-def unescape(
- text: str,
- restore_backslashes: bool = False,
- respect_whitespace: bool = False,
-) -> str:
+def unescape(text: str,
+ restore_backslashes: bool = False,
+ respect_whitespace: bool = False,
+ ) -> str:
"""
Return a string with nulls removed or restored to backslashes.
Backslash-escaped spaces are also removed.
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
_______________________________________________
Docutils-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/docutils-checkins