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