view env/lib/python3.9/site-packages/docutils/transforms/ @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
line wrap: on
line source

# $Id: 8393 2019-09-18 10:13:00Z milde $
# -*- coding: utf-8 -*-
# Authors: David Goodger <>; Ueli Schlaepfer; G√ľnter Milde
# Maintainer:
# 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
- `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
from docutils.utils import smartquotes

if sys.version_info >= (3, 0):
    unicode = str  # noqa

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()
        footer_nodes = self.generate_footer()
        if footer_nodes:
            decoration = self.document.get_decoration()
            footer = decoration.get_footer()

    def generate_header(self):
        return None

    def generate_footer(self):
        # @@@ Text is hard-coded for now.
        # Should be made dynamic (language-dependent).
        # @@@ Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable
        # for the datestamp?
        # See
        # and
        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
                    source = utils.relative_path(settings._destination,
                    nodes.reference('', 'View document source',
            if settings.datestamp:
                datestamp = time.strftime(settings.datestamp, time.gmtime())
                text.append(nodes.Text('Generated on: ' + datestamp + '.\n'))
            if settings.generator:
                    nodes.Text('Generated by '),
                    nodes.reference('', 'Docutils', refuri=
                    nodes.Text(' from '),
                    nodes.reference('', 'reStructuredText', refuri='http://'
                    nodes.Text(' source.\n')])
            return [nodes.paragraph('', '', *text)]
            return None

class ExposeInternals(Transform):

    Expose internal attributes if ``expose_internals`` setting is set.

    default_priority = 840

    def not_Text(self, node):
        return not isinstance(node, nodes.Text)

    def apply(self):
        if self.document.settings.expose_internals:
            for node in self.document.traverse(self.not_Text):
                for att in self.document.settings.expose_internals:
                    value = getattr(node, att, None)
                    if value is not None:
                        node['internal:' + att] = value

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:
        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):
        for node in tuple(self.document.traverse(nodes.system_message)):
            if node['level'] < self.document.reporter.report_level:

class TestMessages(Transform):

    Append all post-parse system messages to the end of the document.

    Used for testing purposes.

    default_priority = 880

    def apply(self):
        for msg in self.document.transform_messages:
            if not msg.parent:
                self.document += msg

class StripComments(Transform):

    Remove comment elements from the document tree (only if the
    ``strip_comments`` setting is enabled).

    default_priority = 740

    def apply(self):
        if self.document.settings.strip_comments:
            for node in tuple(self.document.traverse(nodes.comment)):

class StripClassesAndElements(Transform):

    Remove from the document tree all elements with classes in
    `self.document.settings.strip_elements_with_classes` and all "classes"
    attribute values in `self.document.settings.strip_classes`.

    default_priority = 420

    def apply(self):
        if self.document.settings.strip_elements_with_classes:
            self.strip_elements = set(
            # Iterate over a tuple as removing the current node
            # corrupts the iterator returned by `traverse`:
            for node in tuple(self.document.traverse(self.check_classes)):

        if not self.document.settings.strip_classes:
        strip_classes = self.document.settings.strip_classes
        for node in self.document.traverse(nodes.Element):
            for class_value in strip_classes:
                except ValueError:

    def check_classes(self, node):
        if not isinstance(node, nodes.Element):
            return False
        for class_value in node['classes'][:]:
            if class_value in self.strip_elements:
                return True
        return False

class SmartQuotes(Transform):

    Replace ASCII quotation marks with typographic form.

    Also replace multiple dashes with em-dash/en-dash characters.

    default_priority = 850

    nodes_to_skip = (nodes.FixedTextElement, nodes.Special)
    """Do not apply "smartquotes" to instances of these block-level nodes."""

    literal_nodes = (nodes.FixedTextElement, nodes.Special,
                     nodes.image, nodes.literal, nodes.math,
                     nodes.raw, nodes.problematic)
    """Do apply smartquotes to instances of these inline nodes."""

    smartquotes_action = 'qDe'
    """Setting to select smartquote transformations.

    The default 'qDe' educates normal quote characters: (", '),
    em- and en-dashes (---, --) and ellipses (...).

    def __init__(self, document, startnode):
        Transform.__init__(self, document, startnode=startnode)
        self.unsupported_languages = set()

    def get_tokens(self, txtnodes):
        # A generator that yields ``(texttype, nodetext)`` tuples for a list
        # of "Text" nodes (interface to ``smartquotes.educate_tokens()``).
        for node in txtnodes:
            if (isinstance(node.parent, self.literal_nodes)
                or isinstance(node.parent.parent, self.literal_nodes)):
                yield ('literal', unicode(node))
                # SmartQuotes uses backslash escapes instead of null-escapes
                txt = re.sub('(?<=\x00)([-\\\'".`])', r'\\\1', unicode(node))
                yield ('plain', txt)

    def apply(self):
        smart_quotes = self.document.settings.smart_quotes
        if not smart_quotes:
            alternative = smart_quotes.startswith('alt')
        except AttributeError:
            alternative = False

        document_language = self.document.settings.language_code
        lc_smartquotes = self.document.settings.smartquotes_locales
        if lc_smartquotes:

        # "Educate" quotes in normal text. Handle each block of text
        # (TextElement node) as a unit to keep context around inline nodes:
        for node in self.document.traverse(nodes.TextElement):
            # skip preformatted text blocks and special elements:
            if isinstance(node, self.nodes_to_skip):
            # nested TextElements are not "block-level" elements:
            if isinstance(node.parent, nodes.TextElement):

            # list of text nodes in the "text block":
            txtnodes = [txtnode for txtnode in node.traverse(nodes.Text)
                        if not isinstance(txtnode.parent,

            # language: use typographical quotes for language "lang"
            lang = node.get_language_code(document_language)
            # use alternative form if `smart-quotes` setting starts with "alt":
            if alternative:
                if '-x-altquot' in lang:
                    lang = lang.replace('-x-altquot', '')
                    lang += '-x-altquot'
            # drop unsupported subtags:
            for tag in utils.normalize_language_tag(lang):
                if tag in smartquotes.smartchars.quotes:
                    lang = tag
            else: # language not supported: (keep ASCII quotes)
                if lang not in self.unsupported_languages:
                    self.document.reporter.warning('No smart quotes '
                        'defined for language "%s".'%lang, base_node=node)
                lang = ''

            # Iterator educating quotes in plain text:
            # (see "utils/" for the attribute setting)
            teacher = smartquotes.educate_tokens(self.get_tokens(txtnodes),
                                attr=self.smartquotes_action, language=lang)

            for txtnode, newtext in zip(txtnodes, teacher):
                txtnode.parent.replace(txtnode, nodes.Text(newtext,

        self.unsupported_languages = set() # reset