|
From: <mi...@us...> - 2021-11-11 16:29:19
|
Revision: 8885
http://sourceforge.net/p/docutils/code/8885
Author: milde
Date: 2021-11-11 16:29:16 +0000 (Thu, 11 Nov 2021)
Log Message:
-----------
Node.traverse() returns a list again to restore backwards compatibility.
Although the documented API only promised an "iterable" as return
value, the implementation returned a list.
Because Sphinx < 4.0.0 relies on this and Sphinx < 3.5.4 places no upper
limit to the Docutils dependency, this led to many errors "in the wild".
Fixes bug #431.
New method Node.findall(): like Node.traverse() but returns an iterator.
Modified Paths:
--------------
trunk/docutils/HISTORY.txt
trunk/docutils/RELEASE-NOTES.txt
trunk/docutils/docutils/nodes.py
trunk/docutils/docutils/parsers/recommonmark_wrapper.py
trunk/docutils/docutils/parsers/rst/states.py
trunk/docutils/docutils/transforms/frontmatter.py
trunk/docutils/docutils/transforms/misc.py
trunk/docutils/docutils/transforms/references.py
trunk/docutils/docutils/transforms/universal.py
trunk/docutils/docutils/transforms/writer_aux.py
Modified: trunk/docutils/HISTORY.txt
===================================================================
--- trunk/docutils/HISTORY.txt 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/HISTORY.txt 2021-11-11 16:29:16 UTC (rev 8885)
@@ -14,6 +14,13 @@
Changes Since 0.18
==================
+* docutils/nodes.py
+
+ - Node.traverse() returns a list again to restore backwards
+ compatibility. Fixes bug #431.
+
+ - New method Node.findall(): like Node.traverse() but returns an iterator.
+
* docutils/writers/_html_base.py
- Fix handling of ``footnote_backlinks==False`` (report Alan G Isaac).
@@ -84,7 +91,7 @@
- document.make_id(): Do not strip leading number and hyphen characters
from `name` if the id_prefix setting is non-empty.
- - ``Node.traverse()`` returns an iterator instead of a list.
+ - Node.traverse() returns an iterator instead of a list.
* docutils/parsers/rst/directives/html.py
@@ -460,9 +467,9 @@
- Docutils now supports Python 2.7 and Python 3.5+ natively
(without conversion by ``2to3``).
- Keep `backslash escapes`__ in the document tree. Backslash characters in
- text are be represented by NULL characters in the ``text`` attribute of
+ text are be represented by NULL characters in the `text` attribute of
Doctree nodes and removed in the writing stage by the node's
- ``astext()`` method.
+ astext() method.
__ http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#escaping-mechanism
@@ -669,7 +676,7 @@
* docutils/parsers/rst/directives/tables.py:
- - Rework patch [ 120 ] (revert change to ``Table.get_column_widths()``
+ - Rework patch [ 120 ] (revert change to Table.get_column_widths()
that led to problems in an application with a custom table directive).
* docutils/transforms/frontmatter.py:
Modified: trunk/docutils/RELEASE-NOTES.txt
===================================================================
--- trunk/docutils/RELEASE-NOTES.txt 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/RELEASE-NOTES.txt 2021-11-11 16:29:16 UTC (rev 8885)
@@ -83,14 +83,23 @@
.. _use_latex_citations: docs/user/config.html#use-latex-citations
-Release 0.18 (2021-10-26)
-=========================
+Release 0.18.1.dev
+==================
.. Note::
Docutils 0.18.x is the last version supporting Python 2.7, 3.5, and 3.6.
+* nodes.Node.traverse() returns a list again to restore
+ backwards compatibility (fixes bug #431).
+ It is deprecated in favour of the new method nodes.Node.findall().
+* Small bugfixes (see HISTORY_).
+
+
+Release 0.18 (2021-10-26)
+=========================
+
* Output changes:
Identifiers:
@@ -159,11 +168,11 @@
``iepngfix.htc`` and ``blank.gif`` (IE 6 workaround for `s5_html`).
* Removed sub-module:
- ``parsers.rst.directives.html``
+ ``parsers.rst.directives.html``
(Meta directive moved to ``parsers.rst.directives.misc``.)
-* Removed function: ``utils.unique_combinations``
- (obsoleted by ``itertools.combinations``).
+* Removed function: utils.unique_combinations()
+ (obsoleted by itertools.combinations()).
* Removed attribute: ``HTMLTranslator.topic_classes``
(check node.parent.classes instead).
@@ -173,11 +182,11 @@
``docutils/utils/math/latex2mathml.py``
(mathematical notation in HTML, cf. `LaTeX syntax for mathematics`_).
-* ``nodes.Node.traverse()`` returns an iterator instead of a list.
+* nodes.Node.traverse() returns an iterator instead of a list.
* Various bugfixes and improvements (see HISTORY_).
- Fix spelling errors in documentation and docstrings.
+ Fix spelling errors in documentation and docstrings.
Thanks to Dimitri Papadopoulos.
__ docs/ref/doctree.html#meta
Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/nodes.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -212,40 +212,58 @@
visitor.dispatch_departure(self)
return stop
- def _fast_traverse(self, cls):
+ def _fast_findall(self, cls):
"""Return iterator that only supports instance checks."""
if isinstance(self, cls):
yield self
for child in self.children:
- for subnode in child._fast_traverse(cls):
+ for subnode in child._fast_findall(cls):
yield subnode
- def _all_traverse(self):
+ def _superfast_findall(self):
"""Return iterator that doesn't check for a condition."""
+ # This is different from ``iter(self)`` implemented via
+ # __getitem__() and __len__() in the Element subclass,
+ # which yields only the direct children.
yield self
for child in self.children:
- for subnode in child._all_traverse():
+ for subnode in child._superfast_findall():
yield subnode
def traverse(self, condition=None, include_self=True, descend=True,
siblings=False, ascend=False):
+ """Return list of nodes following `self`.
+
+ For looping, Node.findall() is faster and more memory efficient.
"""
- Return an iterator yielding
+ # traverse() may be eventually removed:
+ warnings.warn('nodes.Node.traverse() is obsoleted by Node.findall().',
+ PendingDeprecationWarning, stacklevel=2)
+ return list(self.findall(condition, include_self, descend,
+ siblings, ascend))
- * self (if include_self is true)
- * all descendants in tree traversal order (if descend is true)
- * all siblings (if siblings is true) and their descendants (if
- also descend is true)
- * the siblings of the parent (if ascend is true) and their
- descendants (if also descend is true), and so on
+ def findall(self, condition=None, include_self=True, descend=True,
+ siblings=False, ascend=False):
+ """
+ Return an iterator yielding nodes following `self`.
+ * self (if `include_self` is true)
+ * all descendants in tree traversal order (if `descend` is true)
+ * all siblings (if `siblings` is true) and their descendants (if
+ also `descend` is true)
+ * the siblings of the parent (if `ascend` is true) and their
+ descendants (if also `descend` is true), and so on
+
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
of ``return isinstance(node, cls)``.
- If ascend is true, assume siblings to be true as well.
+ If `ascend` is true, assume `siblings` to be true as well.
+ To allow processing of the return value or modification while
+ iterating, wrap calls to findall() in a ``tuple()`` or ``list()``.
+
For example, given the following tree::
<paragraph>
@@ -256,15 +274,14 @@
<reference name="Baz" refid="baz">
Baz
- Then list(emphasis.traverse()) equals ::
+ Then tuple(emphasis.traverse()) equals ::
- [<emphasis>, <strong>, <#text: Foo>, <#text: Bar>]
+ (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>)
- and list(strong.traverse(ascend=True)) equals ::
+ and list(strong.traverse(ascend=True) equals ::
[<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>]
"""
-
if ascend:
siblings=True
# Check for special argument combinations that allow using an
@@ -271,11 +288,11 @@
# optimized version of traverse()
if include_self and descend and not siblings:
if condition is None:
- for subnode in self._all_traverse():
+ for subnode in self._superfast_findall():
yield subnode
return
elif isinstance(condition, type):
- for subnode in self._fast_traverse(condition):
+ for subnode in self._fast_findall(condition):
yield subnode
return
# Check if `condition` is a class (check for TypeType for Python
@@ -290,7 +307,7 @@
yield self
if descend and len(self.children):
for child in self:
- for subnode in child.traverse(condition=condition,
+ for subnode in child.findall(condition=condition,
include_self=True, descend=True,
siblings=False, ascend=False):
yield subnode
@@ -299,7 +316,7 @@
while node.parent:
index = node.parent.index(node)
for sibling in node.parent[index+1:]:
- for subnode in sibling.traverse(condition=condition,
+ for subnode in sibling.findall(condition=condition,
include_self=True, descend=descend,
siblings=False, ascend=False):
yield subnode
@@ -311,16 +328,15 @@
def next_node(self, condition=None, include_self=False, descend=True,
siblings=False, ascend=False):
"""
- Return the first node in the iterator returned by traverse(),
+ Return the first node in the iterator returned by findall(),
or None if the iterable is empty.
- Parameter list is the same as of traverse. Note that
- include_self defaults to False, though.
+ Parameter list is the same as of traverse. Note that `include_self`
+ defaults to False, though.
"""
- node_iterator = self.traverse(condition, include_self,
- descend, siblings, ascend)
try:
- return next(node_iterator)
+ return next(self.findall(condition, include_self,
+ descend, siblings, ascend))
except StopIteration:
return None
@@ -473,6 +489,11 @@
element[0]
+ to iterate over the child nodes (without descending), use::
+
+ for child in element:
+ ...
+
Elements may be constructed using the ``+=`` operator. To add one new
child node to element, do::
Modified: trunk/docutils/docutils/parsers/recommonmark_wrapper.py
===================================================================
--- trunk/docutils/docutils/parsers/recommonmark_wrapper.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/parsers/recommonmark_wrapper.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -84,7 +84,7 @@
# ---------------
# merge adjoining Text nodes:
- for node in document.traverse(nodes.TextElement):
+ for node in document.findall(nodes.TextElement):
children = node.children
i = 0
while i+1 < len(children):
@@ -95,18 +95,18 @@
else:
i += 1
- # add "code" class argument to inline literal (code spans)
- for node in document.traverse(lambda n: isinstance(n,
+ # add "code" class argument to literal elements (inline and block)
+ for node in document.findall(lambda n: isinstance(n,
(nodes.literal, nodes.literal_block))):
node['classes'].append('code')
# move "language" argument to classes
- for node in document.traverse(nodes.literal_block):
+ for node in document.findall(nodes.literal_block):
if 'language' in node.attributes:
node['classes'].append(node['language'])
del node['language']
# remove empty target nodes
- for node in list(document.traverse(nodes.target)):
+ for node in list(document.findall(nodes.target)):
# remove empty name
node['names'] = [v for v in node['names'] if v]
if node.children or [v for v in node.attributes.values() if v]:
@@ -115,12 +115,12 @@
# replace raw nodes if raw is not allowed
if not document.settings.raw_enabled:
- for node in document.traverse(nodes.raw):
+ for node in document.findall(nodes.raw):
warning = document.reporter.warning('Raw content disabled.')
node.parent.replace(node, warning)
# fix section nodes
- for node in document.traverse(nodes.section):
+ for node in document.findall(nodes.section):
# remove spurious IDs (first may be from duplicate name)
if len(node['ids']) > 1:
node['ids'].pop()
@@ -138,7 +138,7 @@
del node['level']
# drop pending_xref (Sphinx cross reference extension)
- for node in document.traverse(addnodes.pending_xref):
+ for node in document.findall(addnodes.pending_xref):
reference = node.children[0]
if 'name' not in reference:
reference['name'] = nodes.fully_normalize_name(
Modified: trunk/docutils/docutils/parsers/rst/states.py
===================================================================
--- trunk/docutils/docutils/parsers/rst/states.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/parsers/rst/states.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -2064,7 +2064,7 @@
del substitution_node[i]
else:
i += 1
- for node in substitution_node.traverse(nodes.Element):
+ for node in substitution_node.findall(nodes.Element):
if self.disallowed_inside_substitution_definitions(node):
pformat = nodes.literal_block('', node.pformat().rstrip())
msg = self.reporter.error(
Modified: trunk/docutils/docutils/transforms/frontmatter.py
===================================================================
--- trunk/docutils/docutils/transforms/frontmatter.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/transforms/frontmatter.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -281,10 +281,10 @@
def apply(self):
if not self.document.settings.setdefault('sectsubtitle_xform', True):
return
- for section in self.document.traverse(nodes.section):
+ for section in self.document.findall(nodes.section):
# On our way through the node tree, we are modifying it
# but only the not-yet-visited part, so that the iterator
- # returned by traverse() is not corrupted.
+ # returned by findall() is not corrupted.
self.promote_subtitle(section)
@@ -513,7 +513,7 @@
"""
# @@ keep original formatting? (e.g. ``:authors: A. Test, *et-al*``)
text = ''.join(unicode(node)
- for node in field[1].traverse(nodes.Text))
+ for node in field[1].findall(nodes.Text))
if not text:
raise TransformError
for authorsep in self.language.author_separators:
Modified: trunk/docutils/docutils/transforms/misc.py
===================================================================
--- trunk/docutils/docutils/transforms/misc.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/transforms/misc.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -94,7 +94,7 @@
default_priority = 830
def apply(self):
- for node in self.document.traverse(nodes.transition):
+ for node in self.document.findall(nodes.transition):
self.visit_transition(node)
def visit_transition(self, node):
Modified: trunk/docutils/docutils/transforms/references.py
===================================================================
--- trunk/docutils/docutils/transforms/references.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/transforms/references.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -41,7 +41,7 @@
default_priority = 260
def apply(self):
- for target in self.document.traverse(nodes.target):
+ for target in self.document.findall(nodes.target):
# Only block-level targets without reference (like ".. _target:"):
if (isinstance(target.parent, nodes.TextElement) or
(target.hasattr('refid') or target.hasattr('refuri') or
@@ -118,10 +118,10 @@
def apply(self):
anonymous_refs = []
anonymous_targets = []
- for node in self.document.traverse(nodes.reference):
+ for node in self.document.findall(nodes.reference):
if node.get('anonymous'):
anonymous_refs.append(node)
- for node in self.document.traverse(nodes.target):
+ for node in self.document.findall(nodes.target):
if node.get('anonymous'):
anonymous_targets.append(node)
if len(anonymous_refs) \
@@ -354,7 +354,7 @@
default_priority = 640
def apply(self):
- for target in self.document.traverse(nodes.target):
+ for target in self.document.findall(nodes.target):
if target.hasattr('refuri'):
refuri = target['refuri']
for name in target['names']:
@@ -374,7 +374,7 @@
default_priority = 660
def apply(self):
- for target in self.document.traverse(nodes.target):
+ for target in self.document.findall(nodes.target):
if not target.hasattr('refuri') and not target.hasattr('refid'):
self.resolve_reference_ids(target)
@@ -671,7 +671,7 @@
line_length_limit = getattr(self.document.settings,
"line_length_limit", 10000)
- subreflist = list(self.document.traverse(nodes.substitution_reference))
+ subreflist = list(self.document.findall(nodes.substitution_reference))
for ref in subreflist:
msg = ''
refname = ref['refname']
@@ -714,7 +714,7 @@
subdef_copy = subdef.deepcopy()
try:
# Take care of nested substitution references:
- for nested_ref in subdef_copy.traverse(
+ for nested_ref in subdef_copy.findall(
nodes.substitution_reference):
nested_name = normed[nested_ref['refname'].lower()]
if nested_name in nested.setdefault(nested_name, []):
@@ -777,7 +777,7 @@
def apply(self):
notes = {}
nodelist = []
- for target in self.document.traverse(nodes.target):
+ for target in self.document.findall(nodes.target):
# Only external targets.
if not target.hasattr('refuri'):
continue
@@ -793,7 +793,7 @@
notes[target['refuri']] = footnote
nodelist.append(footnote)
# Take care of anonymous references.
- for ref in self.document.traverse(nodes.reference):
+ for ref in self.document.findall(nodes.reference):
if not ref.get('anonymous'):
continue
if ref.hasattr('refuri'):
@@ -856,7 +856,7 @@
self.document.walk(visitor)
# *After* resolving all references, check for unreferenced
# targets:
- for target in self.document.traverse(nodes.target):
+ for target in self.document.findall(nodes.target):
if not target.referenced:
if target.get('anonymous'):
# If we have unreferenced anonymous targets, there
Modified: trunk/docutils/docutils/transforms/universal.py
===================================================================
--- trunk/docutils/docutils/transforms/universal.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/transforms/universal.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -108,7 +108,7 @@
def apply(self):
if self.document.settings.expose_internals:
- for node in self.document.traverse(self.not_Text):
+ for node in self.document.findall(self.not_Text):
for att in self.document.settings.expose_internals:
value = getattr(node, att, None)
if value is not None:
@@ -149,7 +149,7 @@
default_priority = 870
def apply(self):
- for node in tuple(self.document.traverse(nodes.system_message)):
+ for node in tuple(self.document.findall(nodes.system_message)):
if node['level'] < self.document.reporter.report_level:
node.parent.remove(node)
@@ -181,7 +181,7 @@
def apply(self):
if self.document.settings.strip_comments:
- for node in tuple(self.document.traverse(nodes.comment)):
+ for node in tuple(self.document.findall(nodes.comment)):
node.parent.remove(node)
@@ -200,14 +200,14 @@
self.strip_elements = set(
self.document.settings.strip_elements_with_classes)
# Iterate over a tuple as removing the current node
- # corrupts the iterator returned by `traverse`:
- for node in tuple(self.document.traverse(self.check_classes)):
+ # corrupts the iterator returned by `iter`:
+ for node in tuple(self.document.findall(self.check_classes)):
node.parent.remove(node)
if not self.document.settings.strip_classes:
return
strip_classes = self.document.settings.strip_classes
- for node in self.document.traverse(nodes.Element):
+ for node in self.document.findall(nodes.Element):
for class_value in strip_classes:
try:
node['classes'].remove(class_value)
@@ -282,7 +282,7 @@
# "Educate" quotes in normal text. Handle each block of text
# (TextElement node) as a unit to keep context around inline nodes:
- for node in self.document.traverse(nodes.TextElement):
+ for node in self.document.findall(nodes.TextElement):
# skip preformatted text blocks and special elements:
if isinstance(node, self.nodes_to_skip):
continue
@@ -291,7 +291,7 @@
continue
# list of text nodes in the "text block":
- txtnodes = [txtnode for txtnode in node.traverse(nodes.Text)
+ txtnodes = [txtnode for txtnode in node.findall(nodes.Text)
if not isinstance(txtnode.parent,
nodes.option_string)]
Modified: trunk/docutils/docutils/transforms/writer_aux.py
===================================================================
--- trunk/docutils/docutils/transforms/writer_aux.py 2021-11-09 23:55:06 UTC (rev 8884)
+++ trunk/docutils/docutils/transforms/writer_aux.py 2021-11-11 16:29:16 UTC (rev 8885)
@@ -38,7 +38,7 @@
default_priority = 910
def apply(self):
- for compound in self.document.traverse(nodes.compound):
+ for compound in self.document.findall(nodes.compound):
first_child = True
for child in compound:
if first_child:
@@ -75,7 +75,7 @@
def apply(self):
language = languages.get_language(self.document.settings.language_code,
self.document.reporter)
- for node in self.document.traverse(nodes.Admonition):
+ for node in self.document.findall(nodes.Admonition):
node_name = node.__class__.__name__
# Set class, so that we know what node this admonition came from.
node['classes'].append(node_name)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|