[Zope-Checkins] CVS: Zope/lib/python/third_party/docutils/docutils/transforms - __init__.py:1.1.4.1 components.py:1.1.4.1 frontmatter.py:1.1.4.1 misc.py:1.1.4.1 parts.py:1.1.4.1 peps.py:1.1.4.1 references.py:1.1.4.1 universal.py:1.1.4.1

Andreas Jung andreas at andreas-jung.com
Fri Oct 29 15:08:25 EDT 2004


Update of /cvs-repository/Zope/lib/python/third_party/docutils/docutils/transforms
In directory cvs.zope.org:/tmp/cvs-serv23727/lib/python/third_party/docutils/docutils/transforms

Added Files:
      Tag: Zope-2_7-branch
	__init__.py components.py frontmatter.py misc.py parts.py 
	peps.py references.py universal.py 
Log Message:
moved docutils to lib/python/third_party


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/__init__.py ===
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
This package contains modules for standard tree transforms available
to Docutils components. Tree transforms serve a variety of purposes:

- To tie up certain syntax-specific "loose ends" that remain after the
  initial parsing of the input plaintext. These transforms are used to
  supplement a limited syntax.

- To automate the internal linking of the document tree (hyperlink
  references, footnote references, etc.).

- To extract useful information from the document tree. These
  transforms may be used to construct (for example) indexes and tables
  of contents.

Each transform is an optional step that a Docutils Reader may choose to
perform on the parsed document, depending on the input context. A Docutils
Reader may also perform Reader-specific transforms before or after performing
these standard transforms.
"""

__docformat__ = 'reStructuredText'


from docutils import languages, ApplicationError, TransformSpec


class TransformError(ApplicationError): pass


class Transform:

    """
    Docutils transform component abstract base class.
    """

    default_priority = None
    """Numerical priority of this transform, 0 through 999 (override)."""

    def __init__(self, document, startnode=None):
        """
        Initial setup for in-place document transforms.
        """

        self.document = document
        """The document tree to transform."""

        self.startnode = startnode
        """Node from which to begin the transform.  For many transforms which
        apply to the document as a whole, `startnode` is not set (i.e. its
        value is `None`)."""

        self.language = languages.get_language(
            document.settings.language_code)
        """Language module local to this document."""
        

    def apply(self):
        """Override to apply the transform to the document tree."""
        raise NotImplementedError('subclass must override this method')


class Transformer(TransformSpec):

    """
    Stores transforms (`Transform` classes) and applies them to document
    trees.  Also keeps track of components by component type name.
    """

    from docutils.transforms import universal

    default_transforms = (universal.Decorations,
                          universal.FinalChecks,
                          universal.Messages,
                          universal.FilterMessages)
    """These transforms are applied to all document trees."""

    def __init__(self, document):
        self.transforms = []
        """List of transforms to apply.  Each item is a 3-tuple:
        ``(priority string, transform class, pending node or None)``."""

        self.unknown_reference_resolvers = []
        """List of hook functions which assist in resolving references"""

        self.document = document
        """The `nodes.document` object this Transformer is attached to."""

        self.applied = []
        """Transforms already applied, in order."""

        self.sorted = 0
        """Boolean: is `self.tranforms` sorted?"""

        self.components = {}
        """Mapping of component type name to component object.  Set by
        `self.populate_from_components()`."""

        self.serialno = 0
        """Internal serial number to keep track of the add order of
        transforms."""

    def add_transform(self, transform_class, priority=None):
        """
        Store a single transform.  Use `priority` to override the default.
        """
        if priority is None:
            priority = transform_class.default_priority
        priority_string = self.get_priority_string(priority)
        self.transforms.append((priority_string, transform_class, None))
        self.sorted = 0

    def add_transforms(self, transform_list):
        """Store multiple transforms, with default priorities."""
        for transform_class in transform_list:
            priority_string = self.get_priority_string(
                transform_class.default_priority)
            self.transforms.append((priority_string, transform_class, None))
        self.sorted = 0

    def add_pending(self, pending, priority=None):
        """Store a transform with an associated `pending` node."""
        transform_class = pending.transform
        if priority is None:
            priority = transform_class.default_priority
        priority_string = self.get_priority_string(priority)
        self.transforms.append((priority_string, transform_class, pending))
        self.sorted = 0

    def get_priority_string(self, priority):
        """
        Return a string, `priority` combined with `self.serialno`.

        This ensures FIFO order on transforms with identical priority.
        """
        self.serialno += 1
        return '%03d-%03d' % (priority, self.serialno)

    def populate_from_components(self, components):
        """
        Store each component's default transforms, with default priorities.
        Also, store components by type name in a mapping for later lookup.
        """
        self.add_transforms(self.default_transforms)
        for component in components:
            if component is None:
                continue
            self.add_transforms(component.default_transforms)
            self.components[component.component_type] = component
        self.sorted = 0
        # Setup all of the reference resolvers for this transformer. Each
        # component of this transformer is able to register its own helper
        # functions to help resolve references.
        unknown_reference_resolvers = []
        for i in components:
            unknown_reference_resolvers.extend(i.unknown_reference_resolvers)
        decorated_list = [(f.priority, f) for f in unknown_reference_resolvers]
        decorated_list.sort()
        self.unknown_reference_resolvers.extend([f[1] for f in decorated_list])


    def apply_transforms(self):
        """Apply all of the stored transforms, in priority order."""
        self.document.reporter.attach_observer(
            self.document.note_transform_message)
        while self.transforms:
            if not self.sorted:
                # Unsorted initially, and whenever a transform is added.
                self.transforms.sort()
                self.transforms.reverse()
                self.sorted = 1
            priority, transform_class, pending = self.transforms.pop()
            transform = transform_class(self.document, startnode=pending)
            transform.apply()
            self.applied.append((priority, transform_class, pending))


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/components.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Docutils component-related transforms.
"""

__docformat__ = 'reStructuredText'

import sys
import os
import re
import time
from docutils import nodes, utils
from docutils import ApplicationError, DataError
from docutils.transforms import Transform, TransformError


class Filter(Transform):

    """
    Include or exclude elements which depend on a specific Docutils component.

    For use with `nodes.pending` elements.  A "pending" element's dictionary
    attribute ``details`` must contain the keys "component" and "format".  The
    value of ``details['component']`` must match the type name of the
    component the elements depend on (e.g. "writer").  The value of
    ``details['format']`` is the name of a specific format or context of that
    component (e.g. "html").  If the matching Docutils component supports that
    format or context, the "pending" element is replaced by the contents of
    ``details['nodes']`` (a list of nodes); otherwise, the "pending" element
    is removed.

    For example, the reStructuredText "meta" directive creates a "pending"
    element containing a "meta" element (in ``pending.details['nodes']``).
    Only writers (``pending.details['component'] == 'writer'``) supporting the
    "html" format (``pending.details['format'] == 'html'``) will include the
    "meta" element; it will be deleted from the output of all other writers.
    """

    default_priority = 780

    def apply(self):
        pending = self.startnode
        component_type = pending.details['component'] # 'reader' or 'writer'
        format = pending.details['format']
        component = self.document.transformer.components[component_type]
        if component.supports(format):
            pending.parent.replace(pending, pending.details['nodes'])
        else:
            pending.parent.remove(pending)


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/frontmatter.py ===
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Transforms related to the front matter of a document (information
found before the main text):

- `DocTitle`: Used to transform a lone top level section's title to
  the document title, and promote a remaining lone top-level section's
  title to the document subtitle.

- `DocInfo`: Used to transform a bibliographic field list into docinfo
  elements.
"""

__docformat__ = 'reStructuredText'

import re
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform


class DocTitle(Transform):

    """
    In reStructuredText_, there is no way to specify a document title
    and subtitle explicitly. Instead, we can supply the document title
    (and possibly the subtitle as well) implicitly, and use this
    two-step transform to "raise" or "promote" the title(s) (and their
    corresponding section contents) to the document level.

    1. If the document contains a single top-level section as its
       first non-comment element, the top-level section's title
       becomes the document's title, and the top-level section's
       contents become the document's immediate contents. The lone
       top-level section header must be the first non-comment element
       in the document.

       For example, take this input text::

           =================
            Top-Level Title
           =================

           A paragraph.

       Once parsed, it looks like this::

           <document>
               <section name="top-level title">
                   <title>
                       Top-Level Title
                   <paragraph>
                       A paragraph.

       After running the DocTitle transform, we have::

           <document name="top-level title">
               <title>
                   Top-Level Title
               <paragraph>
                   A paragraph.

    2. If step 1 successfully determines the document title, we
       continue by checking for a subtitle.

       If the lone top-level section itself contains a single
       second-level section as its first non-comment element, that
       section's title is promoted to the document's subtitle, and
       that section's contents become the document's immediate
       contents. Given this input text::

           =================
            Top-Level Title
           =================

           Second-Level Title
           ~~~~~~~~~~~~~~~~~~

           A paragraph.

       After parsing and running the Section Promotion transform, the
       result is::

           <document name="top-level title">
               <title>
                   Top-Level Title
               <subtitle name="second-level title">
                   Second-Level Title
               <paragraph>
                   A paragraph.

       (Note that the implicit hyperlink target generated by the
       "Second-Level Title" is preserved on the "subtitle" element
       itself.)

    Any comment elements occurring before the document title or
    subtitle are accumulated and inserted as the first body elements
    after the title(s).
    """

    default_priority = 320

    def apply(self):
        if not getattr(self.document.settings, 'doctitle_xform', 1):
            return
        if self.promote_document_title():
            self.promote_document_subtitle()

    def promote_document_title(self):
        section, index = self.candidate_index()
        if index is None:
            return None
        document = self.document
        # Transfer the section's attributes to the document element (at root):
        document.attributes.update(section.attributes)
        document[:] = (section[:1]        # section title
                       + document[:index] # everything that was in the
                                          # document before the section
                       + section[1:])     # everything that was in the section
        return 1

    def promote_document_subtitle(self):
        subsection, index = self.candidate_index()
        if index is None:
            return None
        subtitle = nodes.subtitle()
        # Transfer the subsection's attributes to the new subtitle:
        subtitle.attributes.update(subsection.attributes)
        # Transfer the contents of the subsection's title to the subtitle:
        subtitle[:] = subsection[0][:]
        document = self.document
        document[:] = (document[:1]       # document title
                       + [subtitle]
                       # everything that was before the section:
                       + document[1:index]
                       # everything that was in the subsection:
                       + subsection[1:])
        return 1

    def candidate_index(self):
        """
        Find and return the promotion candidate and its index.

        Return (None, None) if no valid candidate was found.
        """
        document = self.document
        index = document.first_child_not_matching_class(
              nodes.PreBibliographic)
        if index is None or len(document) > (index + 1) or \
              not isinstance(document[index], nodes.section):
            return None, None
        else:
            return document[index], index


class DocInfo(Transform):

    """
    This transform is specific to the reStructuredText_ markup syntax;
    see "Bibliographic Fields" in the `reStructuredText Markup
    Specification`_ for a high-level description. This transform
    should be run *after* the `DocTitle` transform.

    Given a field list as the first non-comment element after the
    document title and subtitle (if present), registered bibliographic
    field names are transformed to the corresponding DTD elements,
    becoming child elements of the "docinfo" element (except for a
    dedication and/or an abstract, which become "topic" elements after
    "docinfo").

    For example, given this document fragment after parsing::

        <document>
            <title>
                Document Title
            <field_list>
                <field>
                    <field_name>
                        Author
                    <field_body>
                        <paragraph>
                            A. Name
                <field>
                    <field_name>
                        Status
                    <field_body>
                        <paragraph>
                            $RCSfile: frontmatter.py,v $
            ...

    After running the bibliographic field list transform, the
    resulting document tree would look like this::

        <document>
            <title>
                Document Title
            <docinfo>
                <author>
                    A. Name
                <status>
                    frontmatter.py
            ...

    The "Status" field contained an expanded RCS keyword, which is
    normally (but optionally) cleaned up by the transform. The sole
    contents of the field body must be a paragraph containing an
    expanded RCS keyword of the form "$keyword: expansion text $". Any
    RCS keyword can be processed in any bibliographic field. The
    dollar signs and leading RCS keyword name are removed. Extra
    processing is done for the following RCS keywords:

    - "RCSfile" expands to the name of the file in the RCS or CVS
      repository, which is the name of the source file with a ",v"
      suffix appended. The transform will remove the ",v" suffix.

    - "Date" expands to the format "YYYY/MM/DD hh:mm:ss" (in the UTC
      time zone). The RCS Keywords transform will extract just the
      date itself and transform it to an ISO 8601 format date, as in
      "2000-12-31".

      (Since the source file for this text is itself stored under CVS,
      we can't show an example of the "Date" RCS keyword because we
      can't prevent any RCS keywords used in this explanation from
      being expanded. Only the "RCSfile" keyword is stable; its
      expansion text changes only if the file name changes.)
    """

    default_priority = 340

    biblio_nodes = {
          'author': nodes.author,
          'authors': nodes.authors,
          'organization': nodes.organization,
          'address': nodes.address,
          'contact': nodes.contact,
          'version': nodes.version,
          'revision': nodes.revision,
          'status': nodes.status,
          'date': nodes.date,
          'copyright': nodes.copyright,
          'dedication': nodes.topic,
          'abstract': nodes.topic}
    """Canonical field name (lowcased) to node class name mapping for
    bibliographic fields (field_list)."""

    def apply(self):
        if not getattr(self.document.settings, 'docinfo_xform', 1):
            return
        document = self.document
        index = document.first_child_not_matching_class(
              nodes.PreBibliographic)
        if index is None:
            return
        candidate = document[index]
        if isinstance(candidate, nodes.field_list):
            biblioindex = document.first_child_not_matching_class(
                  nodes.Titular)
            nodelist = self.extract_bibliographic(candidate)
            del document[index]         # untransformed field list (candidate)
            document[biblioindex:biblioindex] = nodelist
        return

    def extract_bibliographic(self, field_list):
        docinfo = nodes.docinfo()
        bibliofields = self.language.bibliographic_fields
        labels = self.language.labels
        topics = {'dedication': None, 'abstract': None}
        for field in field_list:
            try:
                name = field[0][0].astext()
                normedname = nodes.fully_normalize_name(name)
                if not (len(field) == 2 and bibliofields.has_key(normedname)
                        and self.check_empty_biblio_field(field, name)):
                    raise TransformError
                canonical = bibliofields[normedname]
                biblioclass = self.biblio_nodes[canonical]
                if issubclass(biblioclass, nodes.TextElement):
                    if not self.check_compound_biblio_field(field, name):
                        raise TransformError
                    utils.clean_rcs_keywords(
                          field[1][0], self.rcs_keyword_substitutions)
                    docinfo.append(biblioclass('', '', *field[1][0]))
                elif issubclass(biblioclass, nodes.authors):
                    self.extract_authors(field, name, docinfo)
                elif issubclass(biblioclass, nodes.topic):
                    if topics[canonical]:
                        field[-1] += self.document.reporter.warning(
                            'There can only be one "%s" field.' % name,
                            base_node=field)
                        raise TransformError
                    title = nodes.title(name, labels[canonical])
                    topics[canonical] = biblioclass(
                        '', title, CLASS=canonical, *field[1].children)
                else:
                    docinfo.append(biblioclass('', *field[1].children))
            except TransformError:
                if len(field[-1]) == 1 \
                       and isinstance(field[-1][0], nodes.paragraph):
                    utils.clean_rcs_keywords(
                        field[-1][0], self.rcs_keyword_substitutions)
                docinfo.append(field)
        nodelist = []
        if len(docinfo) != 0:
            nodelist.append(docinfo)
        for name in ('dedication', 'abstract'):
            if topics[name]:
                nodelist.append(topics[name])
        return nodelist

    def check_empty_biblio_field(self, field, name):
        if len(field[-1]) < 1:
            field[-1] += self.document.reporter.warning(
                  'Cannot extract empty bibliographic field "%s".' % name,
                  base_node=field)
            return None
        return 1

    def check_compound_biblio_field(self, field, name):
        if len(field[-1]) > 1:
            field[-1] += self.document.reporter.warning(
                  'Cannot extract compound bibliographic field "%s".' % name,
                  base_node=field)
            return None
        if not isinstance(field[-1][0], nodes.paragraph):
            field[-1] += self.document.reporter.warning(
                  'Cannot extract bibliographic field "%s" containing '
                  'anything other than a single paragraph.' % name,
                  base_node=field)
            return None
        return 1

    rcs_keyword_substitutions = [
          (re.compile(r'\$' r'Date: (\d\d\d\d)/(\d\d)/(\d\d) [\d:]+ \$',
                      re.IGNORECASE), r'\1-\2-\3'),
          (re.compile(r'\$' r'RCSfile: (.+),v \$', re.IGNORECASE), r'\1'),
          (re.compile(r'\$[a-zA-Z]+: (.+) \$'), r'\1'),]

    def extract_authors(self, field, name, docinfo):
        try:
            if len(field[1]) == 1:
                if isinstance(field[1][0], nodes.paragraph):
                    authors = self.authors_from_one_paragraph(field)
                elif isinstance(field[1][0], nodes.bullet_list):
                    authors = self.authors_from_bullet_list(field)
                else:
                    raise TransformError
            else:
                authors = self.authors_from_paragraphs(field)
            authornodes = [nodes.author('', '', *author)
                           for author in authors if author]
            if len(authornodes) > 1:
                docinfo.append(nodes.authors('', *authornodes))
            elif len(authornodes) == 1:
                docinfo.append(authornodes[0])
            else:
                raise TransformError
        except TransformError:
            field[-1] += self.document.reporter.warning(
                  'Bibliographic field "%s" incompatible with extraction: '
                  'it must contain either a single paragraph (with authors '
                  'separated by one of "%s"), multiple paragraphs (one per '
                  'author), or a bullet list with one paragraph (one author) '
                  'per item.'
                  % (name, ''.join(self.language.author_separators)),
                  base_node=field)
            raise

    def authors_from_one_paragraph(self, field):
        text = field[1][0].astext().strip()
        if not text:
            raise TransformError
        for authorsep in self.language.author_separators:
            authornames = text.split(authorsep)
            if len(authornames) > 1:
                break
        authornames = [author.strip() for author in authornames]
        authors = [[nodes.Text(author)] for author in authornames if author]
        return authors

    def authors_from_bullet_list(self, field):
        authors = []
        for item in field[1][0]:
            if len(item) != 1 or not isinstance(item[0], nodes.paragraph):
                raise TransformError
            authors.append(item[0].children)
        if not authors:
            raise TransformError
        return authors

    def authors_from_paragraphs(self, field):
        for item in field[1]:
            if not isinstance(item, nodes.paragraph):
                raise TransformError
        authors = [item.children for item in field[1]]
        return authors


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/misc.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Miscellaneous transforms.
"""

__docformat__ = 'reStructuredText'

from docutils import nodes
from docutils.transforms import Transform, TransformError


class CallBack(Transform):

    """
    Inserts a callback into a document.  The callback is called when the
    transform is applied, which is determined by its priority.

    For use with `nodes.pending` elements.  Requires a ``details['callback']``
    entry, a bound method or function which takes one parameter: the pending
    node.  Other data can be stored in the ``details`` attribute or in the
    object hosting the callback method.
    """

    default_priority = 990

    def apply(self):
        pending = self.startnode
        pending.details['callback'](pending)
        pending.parent.remove(pending)


class ClassAttribute(Transform):

    default_priority = 210

    def apply(self):
        pending = self.startnode
        class_value = pending.details['class']
        parent = pending.parent
        child = pending
        while parent:
            for index in range(parent.index(child) + 1, len(parent)):
                element = parent[index]
                if isinstance(element, nodes.comment):
                    continue
                element.set_class(class_value)
                pending.parent.remove(pending)
                return
            else:
                child = parent
                parent = parent.parent
        error = self.document.reporter.error(
            'No suitable element following "%s" directive'
            % pending.details['directive'],
            nodes.literal_block(pending.rawsource, pending.rawsource),
            line=pending.line)
        pending.parent.replace(pending, error)


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/parts.py ===
# Authors: David Goodger, Ueli Schlaepfer, Dmitry Jemerov
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Transforms related to document parts.
"""

__docformat__ = 'reStructuredText'


import re
import sys
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform


class SectNum(Transform):

    """
    Automatically assigns numbers to the titles of document sections.

    It is possible to limit the maximum section level for which the numbers
    are added.  For those sections that are auto-numbered, the "autonum"
    attribute is set, informing the contents table generator that a different
    form of the TOC should be used.
    """

    default_priority = 710
    """Should be applied before `Contents`."""

    def apply(self):
        self.maxdepth = self.startnode.details.get('depth', sys.maxint)
        self.startnode.parent.remove(self.startnode)
        if self.document.settings.sectnum_xform:
            self.update_section_numbers(self.document)

    def update_section_numbers(self, node, prefix=(), depth=0):
        depth += 1
        sectnum = 1
        for child in node:
            if isinstance(child, nodes.section):
                numbers = prefix + (str(sectnum),)
                title = child[0]
                # Use &nbsp; for spacing:
                generated = nodes.generated(
                    '', '.'.join(numbers) + u'\u00a0' * 3, CLASS='sectnum')
                title.insert(0, generated)
                title['auto'] = 1
                if depth < self.maxdepth:
                    self.update_section_numbers(child, numbers, depth)
                sectnum += 1


class Contents(Transform):

    """
    This transform generates a table of contents from the entire document tree
    or from a single branch.  It locates "section" elements and builds them
    into a nested bullet list, which is placed within a "topic" created by the
    contents directive.  A title is either explicitly specified, taken from
    the appropriate language module, or omitted (local table of contents).
    The depth may be specified.  Two-way references between the table of
    contents and section titles are generated (requires Writer support).

    This transform requires a startnode, which which contains generation
    options and provides the location for the generated table of contents (the
    startnode is replaced by the table of contents "topic").
    """

    default_priority = 720

    def apply(self):
        details = self.startnode.details
        if details.has_key('local'):
            startnode = self.startnode.parent.parent
            # @@@ generate an error if the startnode (directive) not at
            # section/document top-level? Drag it up until it is?
            while not isinstance(startnode, nodes.Structural):
                startnode = startnode.parent
        else:
            startnode = self.document

        self.toc_id = self.startnode.parent['id']
        if details.has_key('backlinks'):
            self.backlinks = details['backlinks']
        else:
            self.backlinks = self.document.settings.toc_backlinks
        contents = self.build_contents(startnode)
        if len(contents):
            self.startnode.parent.replace(self.startnode, contents)
        else:
            self.startnode.parent.parent.remove(self.startnode.parent)

    def build_contents(self, node, level=0):
        level += 1
        sections = []
        i = len(node) - 1
        while i >= 0 and isinstance(node[i], nodes.section):
            sections.append(node[i])
            i -= 1
        sections.reverse()
        entries = []
        autonum = 0
        depth = self.startnode.details.get('depth', sys.maxint)
        for section in sections:
            title = section[0]
            auto = title.get('auto')    # May be set by SectNum.
            entrytext = self.copy_and_filter(title)
            reference = nodes.reference('', '', refid=section['id'],
                                        *entrytext)
            ref_id = self.document.set_id(reference)
            entry = nodes.paragraph('', '', reference)
            item = nodes.list_item('', entry)
            if self.backlinks == 'entry':
                title['refid'] = ref_id
            elif self.backlinks == 'top':
                title['refid'] = self.toc_id
            if level < depth:
                subsects = self.build_contents(section, level)
                item += subsects
            entries.append(item)
        if entries:
            contents = nodes.bullet_list('', *entries)
            if auto:
                contents.set_class('auto-toc')
            return contents
        else:
            return []

    def copy_and_filter(self, node):
        """Return a copy of a title, with references, images, etc. removed."""
        visitor = ContentsFilter(self.document)
        node.walkabout(visitor)
        return visitor.get_entry_text()


class ContentsFilter(nodes.TreeCopyVisitor):

    def get_entry_text(self):
        return self.get_tree_copy().get_children()

    def visit_citation_reference(self, node):
        raise nodes.SkipNode

    def visit_footnote_reference(self, node):
        raise nodes.SkipNode

    def visit_image(self, node):
        if node.hasattr('alt'):
            self.parent.append(nodes.Text(node['alt']))
        raise nodes.SkipNode

    def ignore_node_but_process_children(self, node):
        raise nodes.SkipDeparture

    visit_interpreted = ignore_node_but_process_children
    visit_problematic = ignore_node_but_process_children
    visit_reference = ignore_node_but_process_children
    visit_target = ignore_node_but_process_children


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/peps.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Transforms for PEP processing.

- `Headers`: Used to transform a PEP's initial RFC-2822 header.  It remains a
  field list, but some entries get processed.
- `Contents`: Auto-inserts a table of contents.
- `PEPZero`: Special processing for PEP 0.
"""

__docformat__ = 'reStructuredText'

import sys
import os
import re
import time
from docutils import nodes, utils, languages
from docutils import ApplicationError, DataError
from docutils.transforms import Transform, TransformError
from docutils.transforms import parts, references, misc


class Headers(Transform):

    """
    Process fields in a PEP's initial RFC-2822 header.
    """

    default_priority = 360

    pep_url = 'pep-%04d.html'
    pep_cvs_url = ('http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/python/'
                   'python/nondist/peps/pep-%04d.txt')
    rcs_keyword_substitutions = (
          (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
          (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)

    def apply(self):
        if not len(self.document):
            # @@@ replace these DataErrors with proper system messages
            raise DataError('Document tree is empty.')
        header = self.document[0]
        if not isinstance(header, nodes.field_list) or \
              header.get('class') != 'rfc2822':
            raise DataError('Document does not begin with an RFC-2822 '
                            'header; it is not a PEP.')
        pep = None
        for field in header:
            if field[0].astext().lower() == 'pep': # should be the first field
                value = field[1].astext()
                try:
                    pep = int(value)
                    cvs_url = self.pep_cvs_url % pep
                except ValueError:
                    pep = value
                    cvs_url = None
                    msg = self.document.reporter.warning(
                        '"PEP" header must contain an integer; "%s" is an '
                        'invalid value.' % pep, base_node=field)
                    msgid = self.document.set_id(msg)
                    prb = nodes.problematic(value, value or '(none)',
                                            refid=msgid)
                    prbid = self.document.set_id(prb)
                    msg.add_backref(prbid)
                    if len(field[1]):
                        field[1][0][:] = [prb]
                    else:
                        field[1] += nodes.paragraph('', '', prb)
                break
        if pep is None:
            raise DataError('Document does not contain an RFC-2822 "PEP" '
                            'header.')
        if pep == 0:
            # Special processing for PEP 0.
            pending = nodes.pending(PEPZero)
            self.document.insert(1, pending)
            self.document.note_pending(pending)
        if len(header) < 2 or header[1][0].astext().lower() != 'title':
            raise DataError('No title!')
        for field in header:
            name = field[0].astext().lower()
            body = field[1]
            if len(body) > 1:
                raise DataError('PEP header field body contains multiple '
                                'elements:\n%s' % field.pformat(level=1))
            elif len(body) == 1:
                if not isinstance(body[0], nodes.paragraph):
                    raise DataError('PEP header field body may only contain '
                                    'a single paragraph:\n%s'
                                    % field.pformat(level=1))
            elif name == 'last-modified':
                date = time.strftime(
                      '%d-%b-%Y',
                      time.localtime(os.stat(self.document['source'])[8]))
                if cvs_url:
                    body += nodes.paragraph(
                        '', '', nodes.reference('', date, refuri=cvs_url))
            else:
                # empty
                continue
            para = body[0]
            if name == 'author':
                for node in para:
                    if isinstance(node, nodes.reference):
                        node.parent.replace(node, mask_email(node))
            elif name == 'discussions-to':
                for node in para:
                    if isinstance(node, nodes.reference):
                        node.parent.replace(node, mask_email(node, pep))
            elif name in ('replaces', 'replaced-by', 'requires'):
                newbody = []
                space = nodes.Text(' ')
                for refpep in re.split(',?\s+', body.astext()):
                    pepno = int(refpep)
                    newbody.append(nodes.reference(
                        refpep, refpep,
                        refuri=(self.document.settings.pep_base_url
                                + self.pep_url % pepno)))
                    newbody.append(space)
                para[:] = newbody[:-1] # drop trailing space
            elif name == 'last-modified':
                utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
                if cvs_url:
                    date = para.astext()
                    para[:] = [nodes.reference('', date, refuri=cvs_url)]
            elif name == 'content-type':
                pep_type = para.astext()
                uri = self.document.settings.pep_base_url + self.pep_url % 12
                para[:] = [nodes.reference('', pep_type, refuri=uri)]
            elif name == 'version' and len(body):
                utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)


class Contents(Transform):

    """
    Insert an empty table of contents topic and a transform placeholder into
    the document after the RFC 2822 header.
    """

    default_priority = 380

    def apply(self):
        language = languages.get_language(self.document.settings.language_code)
        name = language.labels['contents']
        title = nodes.title('', name)
        topic = nodes.topic('', title, CLASS='contents')
        name = nodes.fully_normalize_name(name)
        if not self.document.has_name(name):
            topic['name'] = name
        self.document.note_implicit_target(topic)
        pending = nodes.pending(parts.Contents)
        topic += pending
        self.document.insert(1, topic)
        self.document.note_pending(pending)


class TargetNotes(Transform):

    """
    Locate the "References" section, insert a placeholder for an external
    target footnote insertion transform at the end, and schedule the
    transform to run immediately.
    """

    default_priority = 520

    def apply(self):
        doc = self.document
        i = len(doc) - 1
        refsect = copyright = None
        while i >= 0 and isinstance(doc[i], nodes.section):
            title_words = doc[i][0].astext().lower().split()
            if 'references' in title_words:
                refsect = doc[i]
                break
            elif 'copyright' in title_words:
                copyright = i
            i -= 1
        if not refsect:
            refsect = nodes.section()
            refsect += nodes.title('', 'References')
            doc.set_id(refsect)
            if copyright:
                # Put the new "References" section before "Copyright":
                doc.insert(copyright, refsect)
            else:
                # Put the new "References" section at end of doc:
                doc.append(refsect)
        pending = nodes.pending(references.TargetNotes)
        refsect.append(pending)
        self.document.note_pending(pending, 0)
        pending = nodes.pending(misc.CallBack,
                                details={'callback': self.cleanup_callback})
        refsect.append(pending)
        self.document.note_pending(pending, 1)

    def cleanup_callback(self, pending):
        """
        Remove an empty "References" section.

        Called after the `references.TargetNotes` transform is complete.
        """
        if len(pending.parent) == 2:    # <title> and <pending>
            pending.parent.parent.remove(pending.parent)


class PEPZero(Transform):

    """
    Special processing for PEP 0.
    """

    default_priority =760

    def apply(self):
        visitor = PEPZeroSpecial(self.document)
        self.document.walk(visitor)
        self.startnode.parent.remove(self.startnode)


class PEPZeroSpecial(nodes.SparseNodeVisitor):

    """
    Perform the special processing needed by PEP 0:

    - Mask email addresses.

    - Link PEP numbers in the second column of 4-column tables to the PEPs
      themselves.
    """

    pep_url = Headers.pep_url

    def unknown_visit(self, node):
        pass

    def visit_reference(self, node):
        node.parent.replace(node, mask_email(node))

    def visit_field_list(self, node):
        if node.hasattr('class') and node['class'] == 'rfc2822':
            raise nodes.SkipNode

    def visit_tgroup(self, node):
        self.pep_table = node['cols'] == 4
        self.entry = 0

    def visit_colspec(self, node):
        self.entry += 1
        if self.pep_table and self.entry == 2:
            node['class'] = 'num'

    def visit_row(self, node):
        self.entry = 0

    def visit_entry(self, node):
        self.entry += 1
        if self.pep_table and self.entry == 2 and len(node) == 1:
            node['class'] = 'num'
            p = node[0]
            if isinstance(p, nodes.paragraph) and len(p) == 1:
                text = p.astext()
                try:
                    pep = int(text)
                    ref = (self.document.settings.pep_base_url
                           + self.pep_url % pep)
                    p[0] = nodes.reference(text, text, refuri=ref)
                except ValueError:
                    pass


non_masked_addresses = ('peps at python.org',
                        'python-list at python.org',
                        'python-dev at python.org')

def mask_email(ref, pepno=None):
    """
    Mask the email address in `ref` and return a replacement node.

    `ref` is returned unchanged if it contains no email address.

    For email addresses such as "user at host", mask the address as "user at
    host" (text) to thwart simple email address harvesters (except for those
    listed in `non_masked_addresses`).  If a PEP number (`pepno`) is given,
    return a reference including a default email subject.
    """
    if ref.hasattr('refuri') and ref['refuri'].startswith('mailto:'):
        if ref['refuri'][8:] in non_masked_addresses:
            replacement = ref[0]
        else:
            replacement_text = ref.astext().replace('@', '&#32;&#97;t&#32;')
            replacement = nodes.raw('', replacement_text, format='html')
        if pepno is None:
            return replacement
        else:
            ref['refuri'] += '?subject=PEP%%20%s' % pepno
            ref[:] = [replacement]
            return ref
    else:
        return ref


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/references.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# Copyright: This module has been placed in the public domain.

"""
Transforms for resolving references.
"""

__docformat__ = 'reStructuredText'

import sys
import re
from docutils import nodes, utils
from docutils.transforms import TransformError, Transform


indices = xrange(sys.maxint)


class ChainedTargets(Transform):

    """
    Attributes "refuri" and "refname" are migrated from the final direct
    target up the chain of contiguous adjacent internal targets, using
    `ChainedTargetResolver`.
    """

    default_priority = 420

    def apply(self):
        visitor = ChainedTargetResolver(self.document)
        self.document.walk(visitor)


class ChainedTargetResolver(nodes.SparseNodeVisitor):

    """
    Copy reference attributes up the length of a hyperlink target chain.

    "Chained targets" are multiple adjacent internal hyperlink targets which
    "point to" an external or indirect target.  After the transform, all
    chained targets will effectively point to the same place.

    Given the following ``document`` as input::

        <document>
            <target id="a" name="a">
            <target id="b" name="b">
            <target id="c" name="c" refuri="http://chained.external.targets">
            <target id="d" name="d">
            <paragraph>
                I'm known as "d".
            <target id="e" name="e">
            <target id="id1">
            <target id="f" name="f" refname="d">

    ``ChainedTargetResolver(document).walk()`` will transform the above into::

        <document>
            <target id="a" name="a" refuri="http://chained.external.targets">
            <target id="b" name="b" refuri="http://chained.external.targets">
            <target id="c" name="c" refuri="http://chained.external.targets">
            <target id="d" name="d">
            <paragraph>
                I'm known as "d".
            <target id="e" name="e" refname="d">
            <target id="id1" refname="d">
            <target id="f" name="f" refname="d">
    """

    def unknown_visit(self, node):
        pass

    def visit_target(self, node):
        if node.hasattr('refuri'):
            attname = 'refuri'
            call_if_named = self.document.note_external_target
        elif node.hasattr('refname'):
            attname = 'refname'
            call_if_named = self.document.note_indirect_target
        elif node.hasattr('refid'):
            attname = 'refid'
            call_if_named = None
        else:
            return
        attval = node[attname]
        index = node.parent.index(node)
        for i in range(index - 1, -1, -1):
            sibling = node.parent[i]
            if not isinstance(sibling, nodes.target) \
                  or sibling.hasattr('refuri') \
                  or sibling.hasattr('refname') \
                  or sibling.hasattr('refid'):
                break
            sibling[attname] = attval
            if sibling.hasattr('name') and call_if_named:
                call_if_named(sibling)


class AnonymousHyperlinks(Transform):

    """
    Link anonymous references to targets.  Given::

        <paragraph>
            <reference anonymous="1">
                internal
            <reference anonymous="1">
                external
        <target anonymous="1" id="id1">
        <target anonymous="1" id="id2" refuri="http://external">

    Corresponding references are linked via "refid" or resolved via "refuri"::

        <paragraph>
            <reference anonymous="1" refid="id1">
                text
            <reference anonymous="1" refuri="http://external">
                external
        <target anonymous="1" id="id1">
        <target anonymous="1" id="id2" refuri="http://external">
    """

    default_priority = 440

    def apply(self):
        if len(self.document.anonymous_refs) \
              != len(self.document.anonymous_targets):
            msg = self.document.reporter.error(
                  'Anonymous hyperlink mismatch: %s references but %s '
                  'targets.\nSee "backrefs" attribute for IDs.'
                  % (len(self.document.anonymous_refs),
                     len(self.document.anonymous_targets)))
            msgid = self.document.set_id(msg)
            for ref in self.document.anonymous_refs:
                prb = nodes.problematic(
                      ref.rawsource, ref.rawsource, refid=msgid)
                prbid = self.document.set_id(prb)
                msg.add_backref(prbid)
                ref.parent.replace(ref, prb)
            return
        for ref, target in zip(self.document.anonymous_refs,
                               self.document.anonymous_targets):
            if target.hasattr('refuri'):
                ref['refuri'] = target['refuri']
                ref.resolved = 1
            else:
                ref['refid'] = target['id']
                self.document.note_refid(ref)
            target.referenced = 1


class IndirectHyperlinks(Transform):

    """
    a) Indirect external references::

           <paragraph>
               <reference refname="indirect external">
                   indirect external
           <target id="id1" name="direct external"
               refuri="http://indirect">
           <target id="id2" name="indirect external"
               refname="direct external">

       The "refuri" attribute is migrated back to all indirect targets
       from the final direct target (i.e. a target not referring to
       another indirect target)::

           <paragraph>
               <reference refname="indirect external">
                   indirect external
           <target id="id1" name="direct external"
               refuri="http://indirect">
           <target id="id2" name="indirect external"
               refuri="http://indirect">

       Once the attribute is migrated, the preexisting "refname" attribute
       is dropped.

    b) Indirect internal references::

           <target id="id1" name="final target">
           <paragraph>
               <reference refname="indirect internal">
                   indirect internal
           <target id="id2" name="indirect internal 2"
               refname="final target">
           <target id="id3" name="indirect internal"
               refname="indirect internal 2">

       Targets which indirectly refer to an internal target become one-hop
       indirect (their "refid" attributes are directly set to the internal
       target's "id"). References which indirectly refer to an internal
       target become direct internal references::

           <target id="id1" name="final target">
           <paragraph>
               <reference refid="id1">
                   indirect internal
           <target id="id2" name="indirect internal 2" refid="id1">
           <target id="id3" name="indirect internal" refid="id1">
    """

    default_priority = 460

    def apply(self):
        for target in self.document.indirect_targets:
            if not target.resolved:
                self.resolve_indirect_target(target)
            self.resolve_indirect_references(target)

    def resolve_indirect_target(self, target):
        refname = target['refname']
        reftarget_id = self.document.nameids.get(refname)
        if not reftarget_id:
            # Check the unknown_reference_resolvers
            for resolver_function in (self.document.transformer
                                      .unknown_reference_resolvers):
                if resolver_function(target):
                    break
            else:
                self.nonexistent_indirect_target(target)
            return
        reftarget = self.document.ids[reftarget_id]
        if isinstance(reftarget, nodes.target) \
              and not reftarget.resolved and reftarget.hasattr('refname'):
            if hasattr(target, 'multiply_indirect'):
                #and target.multiply_indirect):
                #del target.multiply_indirect
                self.circular_indirect_reference(target)
                return
            target.multiply_indirect = 1
            self.resolve_indirect_target(reftarget) # multiply indirect
            del target.multiply_indirect
        if reftarget.hasattr('refuri'):
            target['refuri'] = reftarget['refuri']
            if target.hasattr('name'):
                self.document.note_external_target(target)
        elif reftarget.hasattr('refid'):
            target['refid'] = reftarget['refid']
            self.document.note_refid(target)
        else:
            try:
                target['refid'] = reftarget['id']
                self.document.note_refid(target)
            except KeyError:
                self.nonexistent_indirect_target(target)
                return
        del target['refname']
        target.resolved = 1
        reftarget.referenced = 1

    def nonexistent_indirect_target(self, target):
        if self.document.nameids.has_key(target['refname']):
            self.indirect_target_error(target, 'which is a duplicate, and '
                                       'cannot be used as a unique reference')
        else:
            self.indirect_target_error(target, 'which does not exist')

    def circular_indirect_reference(self, target):
        self.indirect_target_error(target, 'forming a circular reference')

    def indirect_target_error(self, target, explanation):
        naming = ''
        if target.hasattr('name'):
            naming = '"%s" ' % target['name']
            reflist = self.document.refnames.get(target['name'], [])
        else:
            reflist = self.document.refids.get(target['id'], [])
        naming += '(id="%s")' % target['id']
        msg = self.document.reporter.error(
              'Indirect hyperlink target %s refers to target "%s", %s.'
              % (naming, target['refname'], explanation),
              base_node=target)
        msgid = self.document.set_id(msg)
        for ref in reflist:
            prb = nodes.problematic(
                  ref.rawsource, ref.rawsource, refid=msgid)
            prbid = self.document.set_id(prb)
            msg.add_backref(prbid)
            ref.parent.replace(ref, prb)
        target.resolved = 1

    def resolve_indirect_references(self, target):
        if target.hasattr('refid'):
            attname = 'refid'
            call_if_named = 0
            call_method = self.document.note_refid
        elif target.hasattr('refuri'):
            attname = 'refuri'
            call_if_named = 1
            call_method = self.document.note_external_target
        else:
            return
        attval = target[attname]
        if target.hasattr('name'):
            name = target['name']
            try:
                reflist = self.document.refnames[name]
            except KeyError, instance:
                if target.referenced:
                    return
                msg = self.document.reporter.info(
                      'Indirect hyperlink target "%s" is not referenced.'
                      % name, base_node=target)
                target.referenced = 1
                return
            delatt = 'refname'
        else:
            id = target['id']
            try:
                reflist = self.document.refids[id]
            except KeyError, instance:
                if target.referenced:
                    return
                msg = self.document.reporter.info(
                      'Indirect hyperlink target id="%s" is not referenced.'
                      % id, base_node=target)
                target.referenced = 1
                return
            delatt = 'refid'
        for ref in reflist:
            if ref.resolved:
                continue
            del ref[delatt]
            ref[attname] = attval
            if not call_if_named or ref.hasattr('name'):
                call_method(ref)
            ref.resolved = 1
            if isinstance(ref, nodes.target):
                self.resolve_indirect_references(ref)
        target.referenced = 1


class ExternalTargets(Transform):

    """
    Given::

        <paragraph>
            <reference refname="direct external">
                direct external
        <target id="id1" name="direct external" refuri="http://direct">

    The "refname" attribute is replaced by the direct "refuri" attribute::

        <paragraph>
            <reference refuri="http://direct">
                direct external
        <target id="id1" name="direct external" refuri="http://direct">
    """

    default_priority = 640

    def apply(self):
        for target in self.document.external_targets:
            if target.hasattr('refuri') and target.hasattr('name'):
                name = target['name']
                refuri = target['refuri']
                try:
                    reflist = self.document.refnames[name]
                except KeyError, instance:
                    # @@@ First clause correct???
                    if not isinstance(target, nodes.target) or target.referenced:
                        continue
                    msg = self.document.reporter.info(
                          'External hyperlink target "%s" is not referenced.'
                          % name, base_node=target)
                    target.referenced = 1
                    continue
                for ref in reflist:
                    if ref.resolved:
                        continue
                    del ref['refname']
                    ref['refuri'] = refuri
                    ref.resolved = 1
                target.referenced = 1


class InternalTargets(Transform):

    """
    Given::

        <paragraph>
            <reference refname="direct internal">
                direct internal
        <target id="id1" name="direct internal">

    The "refname" attribute is replaced by "refid" linking to the target's
    "id"::

        <paragraph>
            <reference refid="id1">
                direct internal
        <target id="id1" name="direct internal">
    """

    default_priority = 660

    def apply(self):
        for target in self.document.internal_targets:
            if target.hasattr('refuri') or target.hasattr('refid') \
                  or not target.hasattr('name'):
                continue
            name = target['name']
            refid = target['id']
            try:
                reflist = self.document.refnames[name]
            except KeyError, instance:
                if target.referenced:
                    continue
                msg = self.document.reporter.info(
                      'Internal hyperlink target "%s" is not referenced.'
                      % name, base_node=target)
                target.referenced = 1
                continue
            for ref in reflist:
                if ref.resolved:
                    continue
                del ref['refname']
                ref['refid'] = refid
                ref.resolved = 1
            target.referenced = 1


class Footnotes(Transform):

    """
    Assign numbers to autonumbered footnotes, and resolve links to footnotes,
    citations, and their references.

    Given the following ``document`` as input::

        <document>
            <paragraph>
                A labeled autonumbered footnote referece:
                <footnote_reference auto="1" id="id1" refname="footnote">
            <paragraph>
                An unlabeled autonumbered footnote referece:
                <footnote_reference auto="1" id="id2">
            <footnote auto="1" id="id3">
                <paragraph>
                    Unlabeled autonumbered footnote.
            <footnote auto="1" id="footnote" name="footnote">
                <paragraph>
                    Labeled autonumbered footnote.

    Auto-numbered footnotes have attribute ``auto="1"`` and no label.
    Auto-numbered footnote_references have no reference text (they're
    empty elements). When resolving the numbering, a ``label`` element
    is added to the beginning of the ``footnote``, and reference text
    to the ``footnote_reference``.

    The transformed result will be::

        <document>
            <paragraph>
                A labeled autonumbered footnote referece:
                <footnote_reference auto="1" id="id1" refid="footnote">
                    2
            <paragraph>
                An unlabeled autonumbered footnote referece:
                <footnote_reference auto="1" id="id2" refid="id3">
                    1
            <footnote auto="1" id="id3" backrefs="id2">
                <label>
                    1
                <paragraph>
                    Unlabeled autonumbered footnote.
            <footnote auto="1" id="footnote" name="footnote" backrefs="id1">
                <label>
                    2
                <paragraph>
                    Labeled autonumbered footnote.

    Note that the footnotes are not in the same order as the references.

    The labels and reference text are added to the auto-numbered ``footnote``
    and ``footnote_reference`` elements.  Footnote elements are backlinked to
    their references via "refids" attributes.  References are assigned "id"
    and "refid" attributes.

    After adding labels and reference text, the "auto" attributes can be
    ignored.
    """

    default_priority = 620

    autofootnote_labels = None
    """Keep track of unlabeled autonumbered footnotes."""

    symbols = [
          # Entries 1-4 and 6 below are from section 12.51 of
          # The Chicago Manual of Style, 14th edition.
          '*',                          # asterisk/star
          u'\u2020',                    # dagger &dagger;
          u'\u2021',                    # double dagger &Dagger;
          u'\u00A7',                    # section mark &sect;
          u'\u00B6',                    # paragraph mark (pilcrow) &para;
                                        # (parallels ['||'] in CMoS)
          '#',                          # number sign
          # The entries below were chosen arbitrarily.
          u'\u2660',                    # spade suit &spades;
          u'\u2665',                    # heart suit &hearts;
          u'\u2666',                    # diamond suit &diams;
          u'\u2663',                    # club suit &clubs;
          ]

    def apply(self):
        self.autofootnote_labels = []
        startnum = self.document.autofootnote_start
        self.document.autofootnote_start = self.number_footnotes(startnum)
        self.number_footnote_references(startnum)
        self.symbolize_footnotes()
        self.resolve_footnotes_and_citations()

    def number_footnotes(self, startnum):
        """
        Assign numbers to autonumbered footnotes.

        For labeled autonumbered footnotes, copy the number over to
        corresponding footnote references.
        """
        for footnote in self.document.autofootnotes:
            while 1:
                label = str(startnum)
                startnum += 1
                if not self.document.nameids.has_key(label):
                    break
            footnote.insert(0, nodes.label('', label))
            if footnote.hasattr('dupname'):
                continue
            if footnote.hasattr('name'):
                name = footnote['name']
                for ref in self.document.footnote_refs.get(name, []):
                    ref += nodes.Text(label)
                    ref.delattr('refname')
                    ref['refid'] = footnote['id']
                    footnote.add_backref(ref['id'])
                    self.document.note_refid(ref)
                    ref.resolved = 1
            else:
                footnote['name'] = label
                self.document.note_explicit_target(footnote, footnote)
                self.autofootnote_labels.append(label)
        return startnum

    def number_footnote_references(self, startnum):
        """Assign numbers to autonumbered footnote references."""
        i = 0
        for ref in self.document.autofootnote_refs:
            if ref.resolved or ref.hasattr('refid'):
                continue
            try:
                label = self.autofootnote_labels[i]
            except IndexError:
                msg = self.document.reporter.error(
                      'Too many autonumbered footnote references: only %s '
                      'corresponding footnotes available.'
                      % len(self.autofootnote_labels), base_node=ref)
                msgid = self.document.set_id(msg)
                for ref in self.document.autofootnote_refs[i:]:
                    if ref.resolved or ref.hasattr('refname'):
                        continue
                    prb = nodes.problematic(
                          ref.rawsource, ref.rawsource, refid=msgid)
                    prbid = self.document.set_id(prb)
                    msg.add_backref(prbid)
                    ref.parent.replace(ref, prb)
                break
            ref += nodes.Text(label)
            id = self.document.nameids[label]
            footnote = self.document.ids[id]
            ref['refid'] = id
            self.document.note_refid(ref)
            footnote.add_backref(ref['id'])
            ref.resolved = 1
            i += 1

    def symbolize_footnotes(self):
        """Add symbols indexes to "[*]"-style footnotes and references."""
        labels = []
        for footnote in self.document.symbol_footnotes:
            reps, index = divmod(self.document.symbol_footnote_start,
                                 len(self.symbols))
            labeltext = self.symbols[index] * (reps + 1)
            labels.append(labeltext)
            footnote.insert(0, nodes.label('', labeltext))
            self.document.symbol_footnote_start += 1
            self.document.set_id(footnote)
        i = 0
        for ref in self.document.symbol_footnote_refs:
            try:
                ref += nodes.Text(labels[i])
            except IndexError:
                msg = self.document.reporter.error(
                      'Too many symbol footnote references: only %s '
                      'corresponding footnotes available.' % len(labels),
                      base_node=ref)
                msgid = self.document.set_id(msg)
                for ref in self.document.symbol_footnote_refs[i:]:
                    if ref.resolved or ref.hasattr('refid'):
                        continue
                    prb = nodes.problematic(
                          ref.rawsource, ref.rawsource, refid=msgid)
                    prbid = self.document.set_id(prb)
                    msg.add_backref(prbid)
                    ref.parent.replace(ref, prb)
                break
            footnote = self.document.symbol_footnotes[i]
            ref['refid'] = footnote['id']
            self.document.note_refid(ref)
            footnote.add_backref(ref['id'])
            i += 1

    def resolve_footnotes_and_citations(self):
        """
        Link manually-labeled footnotes and citations to/from their
        references.
        """
        for footnote in self.document.footnotes:
            label = footnote['name']
            if self.document.footnote_refs.has_key(label):
                reflist = self.document.footnote_refs[label]
                self.resolve_references(footnote, reflist)
        for citation in self.document.citations:
            label = citation['name']
            if self.document.citation_refs.has_key(label):
                reflist = self.document.citation_refs[label]
                self.resolve_references(citation, reflist)

    def resolve_references(self, note, reflist):
        id = note['id']
        for ref in reflist:
            if ref.resolved:
                continue
            ref.delattr('refname')
            ref['refid'] = id
            note.add_backref(ref['id'])
            ref.resolved = 1
        note.resolved = 1


class Substitutions(Transform):

    """
    Given the following ``document`` as input::

        <document>
            <paragraph>
                The
                <substitution_reference refname="biohazard">
                    biohazard
                 symbol is deservedly scary-looking.
            <substitution_definition name="biohazard">
                <image alt="biohazard" uri="biohazard.png">

    The ``substitution_reference`` will simply be replaced by the
    contents of the corresponding ``substitution_definition``.

    The transformed result will be::

        <document>
            <paragraph>
                The
                <image alt="biohazard" uri="biohazard.png">
                 symbol is deservedly scary-looking.
            <substitution_definition name="biohazard">
                <image alt="biohazard" uri="biohazard.png">
    """

    default_priority = 220
    """The Substitutions transform has to be applied very early, before
    `docutils.tranforms.frontmatter.DocTitle` and others."""

    def apply(self):
        defs = self.document.substitution_defs
        normed = self.document.substitution_names
        for refname, refs in self.document.substitution_refs.items():
            for ref in refs:
                key = None
                if defs.has_key(refname):
                    key = refname
                else:
                    normed_name = refname.lower()
                    if normed.has_key(normed_name):
                        key = normed[normed_name]
                if key is None:
                    msg = self.document.reporter.error(
                          'Undefined substitution referenced: "%s".'
                          % refname, base_node=ref)
                    msgid = self.document.set_id(msg)
                    prb = nodes.problematic(
                          ref.rawsource, ref.rawsource, refid=msgid)
                    prbid = self.document.set_id(prb)
                    msg.add_backref(prbid)
                    ref.parent.replace(ref, prb)
                else:
                    ref.parent.replace(ref, defs[key].get_children())
        self.document.substitution_refs = None  # release replaced references


class TargetNotes(Transform):

    """
    Creates a footnote for each external target in the text, and corresponding
    footnote references after each reference.
    """

    default_priority = 540
    """The TargetNotes transform has to be applied after `IndirectHyperlinks`
    but before `Footnotes`."""

    def apply(self):
        notes = {}
        nodelist = []
        for target in self.document.external_targets:
            name = target.get('name')
            if not name:
                print >>sys.stderr, 'no name on target: %r' % target
                continue
            refs = self.document.refnames.get(name, [])
            if not refs:
                continue
            footnote = self.make_target_footnote(target, refs, notes)
            if not notes.has_key(target['refuri']):
                notes[target['refuri']] = footnote
                nodelist.append(footnote)
        if len(self.document.anonymous_targets) \
               == len(self.document.anonymous_refs):
            for target, ref in zip(self.document.anonymous_targets,
                                   self.document.anonymous_refs):
                if target.hasattr('refuri'):
                    footnote = self.make_target_footnote(target, [ref], notes)
                    if not notes.has_key(target['refuri']):
                        notes[target['refuri']] = footnote
                        nodelist.append(footnote)
        self.startnode.parent.replace(self.startnode, nodelist)

    def make_target_footnote(self, target, refs, notes):
        refuri = target['refuri']
        if notes.has_key(refuri):  # duplicate?
            footnote = notes[refuri]
            footnote_name = footnote['name']
        else:                           # original
            footnote = nodes.footnote()
            footnote_id = self.document.set_id(footnote)
            # Use a colon; they can't be produced inside names by the parser:
            footnote_name = 'target_note: ' + footnote_id
            footnote['auto'] = 1
            footnote['name'] = footnote_name
            footnote_paragraph = nodes.paragraph()
            footnote_paragraph += nodes.reference('', refuri, refuri=refuri)
            footnote += footnote_paragraph
            self.document.note_autofootnote(footnote)
            self.document.note_explicit_target(footnote, footnote)
        for ref in refs:
            if isinstance(ref, nodes.target):
                continue
            refnode = nodes.footnote_reference(
                refname=footnote_name, auto=1)
            self.document.note_autofootnote_ref(refnode)
            self.document.note_footnote_ref(refnode)
            index = ref.parent.index(ref) + 1
            reflist = [refnode]
            if not self.document.settings.trim_footnote_reference_space:
                reflist.insert(0, nodes.Text(' '))
            ref.parent.insert(index, reflist)
        return footnote


=== Added File Zope/lib/python/third_party/docutils/docutils/transforms/universal.py ===
# Authors: David Goodger, Ueli Schlaepfer
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.4.1 $
# Date: $Date: 2004/10/29 19:08:24 $
# 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 = self.generate_header()
        footer = self.generate_footer()
        if header or footer:
            decoration = nodes.decoration()
            decoration += header
            decoration += footer
            document = self.document
            index = document.first_child_not_matching_class(
                nodes.PreDecorative)
            if index is None:
                document += decoration
            else:
                document[index:index] = [decoration]

    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')])
            footer = nodes.footer()
            footer += nodes.paragraph('', '', *text)
            return footer
        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['writer'].report_level
        messages = []
        for msg in unfiltered:
            if msg['level'] >= threshold and not msg.parent:
                messages.append(msg)
        if messages:
            section = nodes.section(CLASS='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['writer'].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.

    - Check for dangling references (incl. footnote & citation).
    """

    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)


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].referenced = 1
            node.resolved = 1

    visit_footnote_reference = visit_citation_reference = visit_reference


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



More information about the Zope-Checkins mailing list