1# $Id: __init__.py 10357 2026-06-14 21:16:29Z milde $
2# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer
3# Copyright: This module has been placed in the public domain.
4
5"""
6This package contains modules for standard tree transforms available
7to Docutils components. Tree transforms serve a variety of purposes:
8
9- To tie up certain syntax-specific "loose ends" that remain after the
10 initial parsing of the input plaintext. These transforms are used to
11 supplement a limited syntax.
12
13- To automate the internal linking of the document tree (hyperlink
14 references, footnote references, etc.).
15
16- To extract useful information from the document tree. These
17 transforms may be used to construct (for example) indexes and tables
18 of contents.
19
20Each transform is an optional step that a Docutils component may
21choose to perform on the parsed document.
22"""
23
24from __future__ import annotations
25
26__docformat__ = 'reStructuredText'
27
28from docutils import languages, ApplicationError, TransformSpec
29
30
31class TransformError(ApplicationError):
32 pass
33
34
35class Transform:
36 """Docutils transform component abstract base class."""
37
38 default_priority = None
39 """Numerical priority of this transform, 0 through 999 (override)."""
40
41 def __init__(self, document, startnode=None) -> None:
42 """
43 Initial setup for in-place document transforms.
44 """
45
46 self.document = document
47 """The document tree to transform."""
48
49 self.startnode = startnode
50 """Node from which to begin the transform. For many transforms which
51 apply to the document as a whole, `startnode` is not set (i.e. its
52 value is `None`)."""
53
54 self.language = languages.get_language(
55 document.settings.language_code, document.reporter)
56 """Language module local to this document."""
57
58 def apply(self, **kwargs):
59 """Override to apply the transform to the document tree."""
60 raise NotImplementedError('subclass must override this method')
61
62
63class Transformer(TransformSpec):
64 """
65 Store "transforms" and apply them to the document tree.
66
67 Collect lists of `Transform` instances from Docutils
68 components (`TransformSpec` instances).
69 Apply collected "transforms" to the document tree.
70
71 Also keeps track of components by component type name.
72
73 https://docutils.sourceforge.io/docs/peps/pep-0258.html#transformer
74 """
75
76 def __init__(self, document) -> None:
77 self.transforms = []
78 """List of transforms to apply. Each item is a 4-tuple:
79 ``(priority string, transform class, pending node or None, kwargs)``.
80 """
81
82 self.document = document
83 """The `nodes.document` object this Transformer is attached to."""
84
85 self.applied = []
86 """Transforms already applied, in order."""
87
88 self.sorted = False
89 """Boolean: is `self.tranforms` sorted?"""
90
91 self.components = {}
92 """Mapping of component type name to component object.
93
94 Set by `self.populate_from_components()`.
95 """
96
97 self.serialno = 0
98 """Internal serial number to keep track of the add order of
99 transforms."""
100
101 def add_transform(self, transform_class, priority=None, **kwargs) -> None:
102 """
103 Store a single transform. Use `priority` to override the default.
104 `kwargs` is a dictionary whose contents are passed as keyword
105 arguments to the `apply` method of the transform. This can be used to
106 pass application-specific data to the transform instance.
107 """
108 if priority is None:
109 priority = transform_class.default_priority
110 priority_string = self.get_priority_string(priority)
111 self.transforms.append(
112 (priority_string, transform_class, None, kwargs))
113 self.sorted = False
114
115 def add_transforms(self, transform_list) -> None:
116 """Store multiple transforms, with default priorities."""
117 for transform_class in transform_list:
118 priority_string = self.get_priority_string(
119 transform_class.default_priority)
120 self.transforms.append(
121 (priority_string, transform_class, None, {}))
122 self.sorted = False
123
124 def add_pending(self, pending, priority=None) -> None:
125 """Store a transform with an associated `pending` node."""
126 transform_class = pending.transform
127 if priority is None:
128 priority = transform_class.default_priority
129 priority_string = self.get_priority_string(priority)
130 self.transforms.append(
131 (priority_string, transform_class, pending, {}))
132 self.sorted = False
133
134 def get_priority_string(self, priority) -> str:
135 """
136 Return a string, `priority` combined with `self.serialno`.
137
138 This ensures FIFO order on transforms with identical priority.
139 """
140 self.serialno += 1
141 return '%03d-%03d' % (priority, self.serialno)
142
143 def populate_from_components(self, components) -> None:
144 """
145 Store each component's default transforms.
146
147 Transforms are stored with default priorities for later sorting.
148 Components that don't inherit from `TransformSpec` are ignored.
149
150 Also, store components by type name in a mapping for later lookup.
151 """
152 for component in components:
153 if not isinstance(component, TransformSpec):
154 continue
155 self.add_transforms(component.get_transforms())
156 self.components[component.component_type] = component
157 self.sorted = False # sort transform list in self.apply_transforms()
158
159 def apply_transforms(self) -> None:
160 """Apply all of the stored transforms, in priority order."""
161 self.document.reporter.attach_observer(
162 self.document.note_transform_message)
163 while self.transforms:
164 if not self.sorted:
165 # Unsorted initially, and whenever a transform is added
166 # (transforms may add other transforms).
167 self.transforms.sort(reverse=True)
168 self.sorted = True
169 priority, transform_class, pending, kwargs = self.transforms.pop()
170 transform = transform_class(self.document, startnode=pending)
171 transform.apply(**kwargs)
172 self.applied.append((priority, transform_class, pending, kwargs))
173 self.document.reporter.detach_observer(
174 self.document.note_transform_message)