| 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)) |
|---|