diff --git a/CHANGES b/CHANGES index 07b967797..9a481b588 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,8 @@ Features added images (imagesize-1.2.0 or above is required) * #6994: imgconverter: Support illustrator file (.ai) to .png conversion * autodoc: Support Positional-Only Argument separator (PEP-570 compliant) +* SphinxTranslator now calls visitor/departure method for super node class if + visitor/departure method for original node class not found Bugs fixed ---------- diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index a44d8bd2e..23f2c888b 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -452,7 +452,10 @@ class ReferenceRole(SphinxRole): class SphinxTranslator(nodes.NodeVisitor): """A base class for Sphinx translators. - This class provides helper methods for Sphinx translators. + This class adds a support for visitor/departure method for super node class + if visitor/departure method for node class is not found. + + It also provides helper methods for Sphinx translators. .. note:: The subclasses of this class might not work with docutils. This class is strongly coupled with Sphinx. @@ -464,6 +467,42 @@ class SphinxTranslator(nodes.NodeVisitor): self.config = builder.config self.settings = document.settings + def dispatch_visit(self, node): + """ + Dispatch node to appropriate visitor method. + The priority of visitor method is: + + 1. ``self.visit_{node_class}()`` + 2. ``self.visit_{supre_node_class}()`` + 3. ``self.unknown_visit()`` + """ + for node_class in node.__class__.__mro__: + method = getattr(self, 'visit_%s' % (node_class.__name__), None) + if method: + logger.debug('SphinxTranslator.dispatch_visit calling %s for %s' % + (method.__name__, node)) + return method(node) + else: + super().dispatch_visit(node) + + def dispatch_departure(self, node): + """ + Dispatch node to appropriate departure method. + The priority of departure method is: + + 1. ``self.depart_{node_class}()`` + 2. ``self.depart_{super_node_class}()`` + 3. ``self.unknown_departure()`` + """ + for node_class in node.__class__.__mro__: + method = getattr(self, 'depart_%s' % (node_class.__name__), None) + if method: + logger.debug('SphinxTranslator.dispatch_departure calling %s for %s' % + (method.__name__, node)) + return method(node) + else: + super().dispatch_departure(node) + # cache a vanilla instance of nodes.document # Used in new_document() function diff --git a/tests/test_util_docutils.py b/tests/test_util_docutils.py index c1df3f7ca..a22cf277a 100644 --- a/tests/test_util_docutils.py +++ b/tests/test_util_docutils.py @@ -12,7 +12,9 @@ import os from docutils import nodes -from sphinx.util.docutils import SphinxFileOutput, docutils_namespace, register_node +from sphinx.util.docutils import ( + SphinxFileOutput, SphinxTranslator, docutils_namespace, new_document, register_node +) def test_register_node(): @@ -61,3 +63,34 @@ def test_SphinxFileOutput(tmpdir): # overrite it again (content changed) output.write(content + "; content change") assert os.stat(filename).st_mtime != 0 # updated + + +def test_SphinxTranslator(app): + class CustomNode(nodes.inline): + pass + + class MyTranslator(SphinxTranslator): + def __init__(self, *args): + self.called = [] + super().__init__(*args) + + def visit_document(self, node): + pass + + def depart_document(self, node): + pass + + def visit_inline(self, node): + self.called.append('visit_inline') + + def depart_inline(self, node): + self.called.append('depart_inline') + + document = new_document('') + document += CustomNode() + + translator = MyTranslator(document, app.builder) + document.walkabout(translator) + + # MyTranslator does not have visit_CustomNode. But it calls visit_inline instead. + assert translator.called == ['visit_inline', 'depart_inline']