1 # $Id: __init__.py 4975 2007-03-01 18:08:32Z wiemann $ |
|
2 # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer |
|
3 # Copyright: This module has been placed in the public domain. |
|
4 |
|
5 """ |
|
6 This package contains modules for standard tree transforms available |
|
7 to 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 |
|
20 Each transform is an optional step that a Docutils component may |
|
21 choose to perform on the parsed document. |
|
22 """ |
|
23 |
|
24 __docformat__ = 'reStructuredText' |
|
25 |
|
26 |
|
27 from docutils import languages, ApplicationError, TransformSpec |
|
28 |
|
29 |
|
30 class TransformError(ApplicationError): pass |
|
31 |
|
32 |
|
33 class Transform: |
|
34 |
|
35 """ |
|
36 Docutils transform component abstract base class. |
|
37 """ |
|
38 |
|
39 default_priority = None |
|
40 """Numerical priority of this transform, 0 through 999 (override).""" |
|
41 |
|
42 def __init__(self, document, startnode=None): |
|
43 """ |
|
44 Initial setup for in-place document transforms. |
|
45 """ |
|
46 |
|
47 self.document = document |
|
48 """The document tree to transform.""" |
|
49 |
|
50 self.startnode = startnode |
|
51 """Node from which to begin the transform. For many transforms which |
|
52 apply to the document as a whole, `startnode` is not set (i.e. its |
|
53 value is `None`).""" |
|
54 |
|
55 self.language = languages.get_language( |
|
56 document.settings.language_code) |
|
57 """Language module local to this document.""" |
|
58 |
|
59 def apply(self, **kwargs): |
|
60 """Override to apply the transform to the document tree.""" |
|
61 raise NotImplementedError('subclass must override this method') |
|
62 |
|
63 |
|
64 class Transformer(TransformSpec): |
|
65 |
|
66 """ |
|
67 Stores transforms (`Transform` classes) and applies them to document |
|
68 trees. Also keeps track of components by component type name. |
|
69 """ |
|
70 |
|
71 def __init__(self, document): |
|
72 self.transforms = [] |
|
73 """List of transforms to apply. Each item is a 3-tuple: |
|
74 ``(priority string, transform class, pending node or None)``.""" |
|
75 |
|
76 self.unknown_reference_resolvers = [] |
|
77 """List of hook functions which assist in resolving references""" |
|
78 |
|
79 self.document = document |
|
80 """The `nodes.document` object this Transformer is attached to.""" |
|
81 |
|
82 self.applied = [] |
|
83 """Transforms already applied, in order.""" |
|
84 |
|
85 self.sorted = 0 |
|
86 """Boolean: is `self.tranforms` sorted?""" |
|
87 |
|
88 self.components = {} |
|
89 """Mapping of component type name to component object. Set by |
|
90 `self.populate_from_components()`.""" |
|
91 |
|
92 self.serialno = 0 |
|
93 """Internal serial number to keep track of the add order of |
|
94 transforms.""" |
|
95 |
|
96 def add_transform(self, transform_class, priority=None, **kwargs): |
|
97 """ |
|
98 Store a single transform. Use `priority` to override the default. |
|
99 `kwargs` is a dictionary whose contents are passed as keyword |
|
100 arguments to the `apply` method of the transform. This can be used to |
|
101 pass application-specific data to the transform instance. |
|
102 """ |
|
103 if priority is None: |
|
104 priority = transform_class.default_priority |
|
105 priority_string = self.get_priority_string(priority) |
|
106 self.transforms.append( |
|
107 (priority_string, transform_class, None, kwargs)) |
|
108 self.sorted = 0 |
|
109 |
|
110 def add_transforms(self, transform_list): |
|
111 """Store multiple transforms, with default priorities.""" |
|
112 for transform_class in transform_list: |
|
113 priority_string = self.get_priority_string( |
|
114 transform_class.default_priority) |
|
115 self.transforms.append( |
|
116 (priority_string, transform_class, None, {})) |
|
117 self.sorted = 0 |
|
118 |
|
119 def add_pending(self, pending, priority=None): |
|
120 """Store a transform with an associated `pending` node.""" |
|
121 transform_class = pending.transform |
|
122 if priority is None: |
|
123 priority = transform_class.default_priority |
|
124 priority_string = self.get_priority_string(priority) |
|
125 self.transforms.append( |
|
126 (priority_string, transform_class, pending, {})) |
|
127 self.sorted = 0 |
|
128 |
|
129 def get_priority_string(self, priority): |
|
130 """ |
|
131 Return a string, `priority` combined with `self.serialno`. |
|
132 |
|
133 This ensures FIFO order on transforms with identical priority. |
|
134 """ |
|
135 self.serialno += 1 |
|
136 return '%03d-%03d' % (priority, self.serialno) |
|
137 |
|
138 def populate_from_components(self, components): |
|
139 """ |
|
140 Store each component's default transforms, with default priorities. |
|
141 Also, store components by type name in a mapping for later lookup. |
|
142 """ |
|
143 for component in components: |
|
144 if component is None: |
|
145 continue |
|
146 self.add_transforms(component.get_transforms()) |
|
147 self.components[component.component_type] = component |
|
148 self.sorted = 0 |
|
149 # Set up all of the reference resolvers for this transformer. Each |
|
150 # component of this transformer is able to register its own helper |
|
151 # functions to help resolve references. |
|
152 unknown_reference_resolvers = [] |
|
153 for i in components: |
|
154 unknown_reference_resolvers.extend(i.unknown_reference_resolvers) |
|
155 decorated_list = [(f.priority, f) for f in unknown_reference_resolvers] |
|
156 decorated_list.sort() |
|
157 self.unknown_reference_resolvers.extend([f[1] for f in decorated_list]) |
|
158 |
|
159 def apply_transforms(self): |
|
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 self.transforms.sort() |
|
167 self.transforms.reverse() |
|
168 self.sorted = 1 |
|
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)) |
|