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