summaryrefslogtreecommitdiff
path: root/docutils/transforms/universal.py
diff options
context:
space:
mode:
authorwiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2005-08-17 13:59:12 +0000
committerwiemann <wiemann@929543f6-e4f2-0310-98a6-ba3bd3dd1d04>2005-08-17 13:59:12 +0000
commita4bc0d09f3d1f633a5780976d1f913e083c9564a (patch)
tree1777475e88463b08438f0719be97cc087554eadd /docutils/transforms/universal.py
parent60c69f8cf97125135948a3d35b7698ee80921137 (diff)
parent2818db9a234cc37008c5f2bb0e821f9b1fa92de2 (diff)
downloaddocutils-0.3.9.tar.gz
re-added docutils-0.3.9 tag one level deeper (without web/ and sandboxes/)docutils-0.3.9
git-svn-id: http://svn.code.sf.net/p/docutils/code/tags/docutils-0.3.9@3815 929543f6-e4f2-0310-98a6-ba3bd3dd1d04
Diffstat (limited to 'docutils/transforms/universal.py')
-rw-r--r--docutils/transforms/universal.py301
1 files changed, 301 insertions, 0 deletions
diff --git a/docutils/transforms/universal.py b/docutils/transforms/universal.py
new file mode 100644
index 000000000..aac05a2e4
--- /dev/null
+++ b/docutils/transforms/universal.py
@@ -0,0 +1,301 @@
+# Authors: David Goodger, Ueli Schlaepfer
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision$
+# Date: $Date$
+# Copyright: This module has been placed in the public domain.
+
+"""
+Transforms needed by most or all documents:
+
+- `Decorations`: Generate a document's header & footer.
+- `Messages`: Placement of system messages stored in
+ `nodes.document.transform_messages`.
+- `TestMessages`: Like `Messages`, used on test runs.
+- `FinalReferences`: Resolve remaining references.
+"""
+
+__docformat__ = 'reStructuredText'
+
+import re
+import sys
+import time
+from docutils import nodes, utils
+from docutils.transforms import TransformError, Transform
+
+
+class Decorations(Transform):
+
+ """
+ Populate a document's decoration element (header, footer).
+ """
+
+ default_priority = 820
+
+ def apply(self):
+ header_nodes = self.generate_header()
+ if header_nodes:
+ decoration = self.document.get_decoration()
+ header = decoration.get_header()
+ header.extend(header_nodes)
+ footer_nodes = self.generate_footer()
+ if footer_nodes:
+ decoration = self.document.get_decoration()
+ footer = decoration.get_footer()
+ footer.extend(footer_nodes)
+
+ def generate_header(self):
+ return None
+
+ def generate_footer(self):
+ # @@@ Text is hard-coded for now.
+ # Should be made dynamic (language-dependent).
+ settings = self.document.settings
+ if settings.generator or settings.datestamp or settings.source_link \
+ or settings.source_url:
+ text = []
+ if settings.source_link and settings._source \
+ or settings.source_url:
+ if settings.source_url:
+ source = settings.source_url
+ else:
+ source = utils.relative_path(settings._destination,
+ settings._source)
+ text.extend([
+ nodes.reference('', 'View document source',
+ refuri=source),
+ nodes.Text('.\n')])
+ if settings.datestamp:
+ datestamp = time.strftime(settings.datestamp, time.gmtime())
+ text.append(nodes.Text('Generated on: ' + datestamp + '.\n'))
+ if settings.generator:
+ text.extend([
+ nodes.Text('Generated by '),
+ nodes.reference('', 'Docutils', refuri=
+ 'http://docutils.sourceforge.net/'),
+ nodes.Text(' from '),
+ nodes.reference('', 'reStructuredText', refuri='http://'
+ 'docutils.sourceforge.net/rst.html'),
+ nodes.Text(' source.\n')])
+ return [nodes.paragraph('', '', *text)]
+ else:
+ return None
+
+
+class Messages(Transform):
+
+ """
+ Place any system messages generated after parsing into a dedicated section
+ of the document.
+ """
+
+ default_priority = 860
+
+ def apply(self):
+ unfiltered = self.document.transform_messages
+ threshold = self.document.reporter.report_level
+ messages = []
+ for msg in unfiltered:
+ if msg['level'] >= threshold and not msg.parent:
+ messages.append(msg)
+ if messages:
+ section = nodes.section(classes=['system-messages'])
+ # @@@ get this from the language module?
+ section += nodes.title('', 'Docutils System Messages')
+ section += messages
+ self.document.transform_messages[:] = []
+ self.document += section
+
+
+class FilterMessages(Transform):
+
+ """
+ Remove system messages below verbosity threshold.
+ """
+
+ default_priority = 870
+
+ def apply(self):
+ visitor = SystemMessageFilterVisitor(self.document)
+ self.document.walk(visitor)
+
+
+class SystemMessageFilterVisitor(nodes.SparseNodeVisitor):
+
+ def unknown_visit(self, node):
+ pass
+
+ def visit_system_message(self, node):
+ if node['level'] < self.document.reporter.report_level:
+ node.parent.remove(node)
+
+
+class TestMessages(Transform):
+
+ """
+ Append all post-parse system messages to the end of the document.
+ """
+
+ default_priority = 890
+
+ def apply(self):
+ for msg in self.document.transform_messages:
+ if not msg.parent:
+ self.document += msg
+
+
+class FinalChecks(Transform):
+
+ """
+ Perform last-minute checks and transforms.
+
+ - Check for dangling references (incl. footnote & citation).
+ - Check for illegal transitions, move transitions.
+ """
+
+ default_priority = 840
+
+ def apply(self):
+ visitor = FinalCheckVisitor(
+ self.document,
+ self.document.transformer.unknown_reference_resolvers)
+ self.document.walk(visitor)
+ if self.document.settings.expose_internals:
+ visitor = InternalAttributeExposer(self.document)
+ self.document.walk(visitor)
+ # *After* resolving all references, check for unreferenced
+ # targets:
+ for target in self.document.traverse():
+ if isinstance(target, nodes.target) and not target.referenced:
+ if target['names']:
+ naming = target['names'][0]
+ elif target['ids']:
+ naming = target['ids'][0]
+ else:
+ # Hack: Propagated targets always have their refid
+ # attribute set.
+ naming = target['refid']
+ self.document.reporter.info(
+ 'Hyperlink target "%s" is not referenced.'
+ % naming, base_node=target)
+
+
+class FinalCheckVisitor(nodes.SparseNodeVisitor):
+
+ def __init__(self, document, unknown_reference_resolvers):
+ nodes.SparseNodeVisitor.__init__(self, document)
+ self.document = document
+ self.unknown_reference_resolvers = unknown_reference_resolvers
+
+ def unknown_visit(self, node):
+ pass
+
+ def visit_reference(self, node):
+ if node.resolved or not node.hasattr('refname'):
+ return
+ refname = node['refname']
+ id = self.document.nameids.get(refname)
+ if id is None:
+ for resolver_function in self.unknown_reference_resolvers:
+ if resolver_function(node):
+ break
+ else:
+ if self.document.nameids.has_key(refname):
+ msg = self.document.reporter.error(
+ 'Duplicate target name, cannot be used as a unique '
+ 'reference: "%s".' % (node['refname']), base_node=node)
+ else:
+ msg = self.document.reporter.error(
+ 'Unknown target name: "%s".' % (node['refname']),
+ base_node=node)
+ msgid = self.document.set_id(msg)
+ prb = nodes.problematic(
+ node.rawsource, node.rawsource, refid=msgid)
+ prbid = self.document.set_id(prb)
+ msg.add_backref(prbid)
+ node.parent.replace(node, prb)
+ else:
+ del node['refname']
+ node['refid'] = id
+ self.document.ids[id].note_referenced_by(id=id)
+ node.resolved = 1
+
+ visit_footnote_reference = visit_citation_reference = visit_reference
+
+ def visit_transition(self, node):
+ """
+ Move transitions at the end of sections up the tree. Complain
+ on transitions after a title, at the beginning or end of the
+ document, and after another transition.
+
+ For example, transform this::
+
+ <section>
+ ...
+ <transition>
+ <section>
+ ...
+
+ into this::
+
+ <section>
+ ...
+ <transition>
+ <section>
+ ...
+ """
+ index = node.parent.index(node)
+ error = None
+ if (index == 0 or
+ isinstance(node.parent[0], nodes.title) and
+ (index == 1 or
+ isinstance(node.parent[1], nodes.subtitle) and
+ index == 2)):
+ assert (isinstance(node.parent, nodes.document) or
+ isinstance(node.parent, nodes.section))
+ error = self.document.reporter.error(
+ 'Document or section may not begin with a transition.',
+ line=node.line)
+ elif isinstance(node.parent[index - 1], nodes.transition):
+ error = self.document.reporter.error(
+ 'At least one body element must separate transitions; '
+ 'adjacent transitions are not allowed.', line=node.line)
+ if error:
+ # Insert before node and update index.
+ node.parent.insert(index, error)
+ index += 1
+ assert index < len(node.parent)
+ if index != len(node.parent) - 1:
+ # No need to move the node.
+ return
+ # Node behind which the transition is to be moved.
+ sibling = node
+ # While sibling is the last node of its parent.
+ while index == len(sibling.parent) - 1:
+ sibling = sibling.parent
+ # If sibling is the whole document (i.e. it has no parent).
+ if sibling.parent is None:
+ # Transition at the end of document. Do not move the
+ # transition up, and place an error behind.
+ error = self.document.reporter.error(
+ 'Document may not end with a transition.',
+ line=node.line)
+ node.parent.insert(node.parent.index(node) + 1, error)
+ return
+ index = sibling.parent.index(sibling)
+ # Remove the original transition node.
+ node.parent.remove(node)
+ # Insert the transition after the sibling.
+ sibling.parent.insert(index + 1, node)
+
+
+class InternalAttributeExposer(nodes.GenericNodeVisitor):
+
+ def __init__(self, document):
+ nodes.GenericNodeVisitor.__init__(self, document)
+ self.internal_attributes = document.settings.expose_internals
+
+ def default_visit(self, node):
+ for att in self.internal_attributes:
+ value = getattr(node, att, None)
+ if value is not None:
+ node['internal:' + att] = value