1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
# $Id: misc.py 10166 2025-06-16 09:45:10Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
Miscellaneous transforms.
"""
from __future__ import annotations
__docformat__ = 'reStructuredText'
from docutils import nodes
from docutils.transforms import Transform
class CallBack(Transform):
"""
Inserts a callback into a document. The callback is called when the
transform is applied, which is determined by its priority.
For use with `nodes.pending` elements. Requires a ``details['callback']``
entry, a bound method or function which takes one parameter: the pending
node. Other data can be stored in the ``details`` attribute or in the
object hosting the callback method.
"""
default_priority = 990
def apply(self) -> None:
pending = self.startnode
pending.details['callback'](pending)
pending.parent.remove(pending)
class ClassAttribute(Transform):
"""
Move the "class" attribute specified in the "pending" node into the
next visible element.
"""
default_priority = 210
def apply(self) -> None:
pending = self.startnode
parent = pending.parent
child = pending
while parent:
# Check for appropriate following siblings:
for index in range(parent.index(child) + 1, len(parent)):
element = parent[index]
if (isinstance(element, nodes.Invisible)
or isinstance(element, nodes.system_message)):
continue
element['classes'] += pending.details['class']
pending.parent.remove(pending)
return
else:
# At end of section or container; apply to sibling
child = parent
parent = parent.parent
error = self.document.reporter.error(
'No suitable element following "%s" directive'
% pending.details['directive'],
nodes.literal_block(pending.rawsource, pending.rawsource),
line=pending.line)
pending.replace_self(error)
class Transitions(Transform):
"""
Move transitions at the end of sections up the tree. Complain
on transitions after a title, subtitle, meta, or decoration element,
at the beginning or end of the document, and after another transition.
For example, transform this::
<section>
...
<transition>
<section>
...
into this::
<section>
...
<transition>
<section>
...
"""
default_priority = 830
def apply(self) -> None:
for node in self.document.findall(nodes.transition):
self.visit_transition(node)
def visit_transition(self, node) -> None:
index = node.parent.index(node)
previous_sibling = node.previous_sibling()
msg = ''
if not isinstance(node.parent, (nodes.document, nodes.section)):
msg = 'Transition must be child of <document> or <section>.'
elif index == 0 or isinstance(previous_sibling, (nodes.title,
nodes.subtitle,
nodes.meta,
nodes.decoration)):
msg = 'Document or section may not begin with a transition.'
elif isinstance(previous_sibling, nodes.transition):
msg = ('At least one body element must separate transitions; '
'adjacent transitions are not allowed.')
if msg:
warning = self.document.reporter.warning(msg, base_node=node)
# Check, if it is valid to insert a body element
node.parent[index] = nodes.paragraph()
try:
node.parent.validate(recursive=False)
except nodes.ValidationError:
node.parent[index] = node
else:
node.parent[index] = node
node.parent.insert(index+1, warning)
index += 1
if not isinstance(node.parent, (nodes.document, nodes.section)):
return
assert index < len(node.parent)
if index != len(node.parent) - 1:
# No need to move the node.
return
# Node behind which the transition is to be moved.
sibling = node
# While sibling is the last node of its parent.
while index == len(sibling.parent) - 1:
sibling = sibling.parent
if sibling.parent is None: # sibling is the top node (document)
# Transition at the end of document. Do not move the
# transition up, and place a warning behind.
warning = self.document.reporter.warning(
'Document may not end with a transition.',
base_node=node)
node.parent.append(warning)
return
index = sibling.parent.index(sibling)
# Remove the original transition node.
node.parent.remove(node)
# Insert the transition after the sibling.
sibling.parent.insert(index + 1, node)
|