[Zope-Checkins] CVS: Zope/lib/python/third_party/docutils/docutils/writers - __init__.py:1.1.2.1 docutils_xml.py:1.1.2.1 html4css1.py:1.1.2.1 latex2e.py:1.1.2.1 pep_html.py:1.1.2.1 pseudoxml.py:1.1.2.1

Andreas Jung andreas at andreas-jung.com
Fri Oct 29 14:24:56 EDT 2004


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

Added Files:
      Tag: ajung-docutils-cleanup-branch
	__init__.py docutils_xml.py html4css1.py latex2e.py 
	pep_html.py pseudoxml.py 
Log Message:
moved


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/__init__.py ===
# Authors: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/10/29 18:24:54 $
# Copyright: This module has been placed in the public domain.

"""
This package contains Docutils Writer modules.
"""

__docformat__ = 'reStructuredText'


import sys
import docutils
from docutils import languages, Component
from docutils.transforms import universal


class Writer(Component):

    """
    Abstract base class for docutils Writers.

    Each writer module or package must export a subclass also called 'Writer'.
    Each writer must support all standard node types listed in
    `docutils.nodes.node_class_names`.

    The `write()` method is the main entry point.
    """

    component_type = 'writer'
    config_section = 'writers'

    document = None
    """The document to write (Docutils doctree); set by `write`."""

    output = None
    """Final translated form of `document` (Unicode string);
    set by `translate`."""

    language = None
    """Language module for the document; set by `write`."""

    destination = None
    """`docutils.io` Output object; where to write the document.
    Set by `write`."""

    def __init__(self):

        # Currently only used by HTML writer for output fragments:
        self.parts = {}
        """Mapping of document part names to fragments of `self.output`.
        Values are Unicode strings; encoding is up to the client.  The 'whole'
        key should contain the entire document output.
        """

    def write(self, document, destination):
        """
        Process a document into its final form.

        Translate `document` (a Docutils document tree) into the Writer's
        native format, and write it out to its `destination` (a
        `docutils.io.Output` subclass object).

        Normally not overridden or extended in subclasses.
        """
        self.document = document
        self.language = languages.get_language(
            document.settings.language_code)
        self.destination = destination
        self.translate()
        output = self.destination.write(self.output)
        return output

    def translate(self):
        """
        Do final translation of `self.document` into `self.output` (Unicode
        string).  Called from `write`.  Override in subclasses.

        Usually done with a `docutils.nodes.NodeVisitor` subclass, in
        combination with a call to `docutils.nodes.Node.walk()` or
        `docutils.nodes.Node.walkabout()`.  The ``NodeVisitor`` subclass must
        support all standard elements (listed in
        `docutils.nodes.node_class_names`) and possibly non-standard elements
        used by the current Reader as well.
        """
        raise NotImplementedError('subclass must override this method')

    def assemble_parts(self):
        """Assemble the `self.parts` dictionary.  Extend in subclasses."""
        self.parts['whole'] = self.output


_writer_aliases = {
      'html': 'html4css1',
      'latex': 'latex2e',
      'pprint': 'pseudoxml',
      'pformat': 'pseudoxml',
      'pdf': 'rlpdf',
      'xml': 'docutils_xml',}

def get_writer_class(writer_name):
    """Return the Writer class from the `writer_name` module."""
    writer_name = writer_name.lower()
    if _writer_aliases.has_key(writer_name):
        writer_name = _writer_aliases[writer_name]
    module = __import__(writer_name, globals(), locals())
    return module.Writer


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/docutils_xml.py ===
# Authors: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/10/29 18:24:54 $
# Copyright: This module has been placed in the public domain.

"""
Simple internal document tree Writer, writes Docutils XML.
"""

__docformat__ = 'reStructuredText'


import docutils
from docutils import frontend, writers


class Writer(writers.Writer):

    supported = ('xml',)
    """Formats this writer supports."""

    settings_spec = (
        '"Docutils XML" Writer Options',
        'Warning: the --newlines and --indents options may adversely affect '
        'whitespace; use them only for reading convenience.',
        (('Generate XML with newlines before and after tags.',
          ['--newlines'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),
         ('Generate XML with indents and newlines.',
          ['--indents'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),
         ('Omit the XML declaration.  Use with caution.',
          ['--no-xml-declaration'],
          {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
           'validator': frontend.validate_boolean}),
         ('Omit the DOCTYPE declaration.',
          ['--no-doctype'],
          {'dest': 'doctype_declaration', 'default': 1,
           'action': 'store_false', 'validator': frontend.validate_boolean}),))

    config_section = 'docutils_xml writer'
    config_section_dependencies = ('writers',)

    output = None
    """Final translated form of `document`."""

    xml_declaration = '<?xml version="1.0" encoding="%s"?>\n'
    #xml_stylesheet = '<?xml-stylesheet type="text/xsl" href="%s"?>\n'
    doctype = (
        '<!DOCTYPE document PUBLIC'
        ' "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML"'
        ' "http://docutils.sourceforge.net/docs/ref/docutils.dtd">\n')
    generator = '<!-- Generated by Docutils %s -->\n'

    def translate(self):
        settings = self.document.settings
        indent = newline = ''
        if settings.newlines:
            newline = '\n'
        if settings.indents:
            newline = '\n'
            indent = '    '
        output_prefix = []
        if settings.xml_declaration:
            output_prefix.append(
                self.xml_declaration % settings.output_encoding)
        if settings.doctype_declaration:
            output_prefix.append(self.doctype)
        output_prefix.append(self.generator % docutils.__version__)
        docnode = self.document.asdom().childNodes[0]
        self.output = (''.join(output_prefix)
                       + docnode.toprettyxml(indent, newline))


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/html4css1.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/10/29 18:24:54 $
# Copyright: This module has been placed in the public domain.

"""
Simple HyperText Markup Language document tree Writer.

The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
minimum of formatting information.  A cascading style sheet ("default.css" by
default) is required for proper viewing with a modern graphical browser.
"""

__docformat__ = 'reStructuredText'


import sys
import os
import os.path
import time
import re
from types import ListType
try:
    import Image                        # check for the Python Imaging Library
except ImportError:
    Image = None
import docutils
from docutils import frontend, nodes, utils, writers, languages


class Writer(writers.Writer):

    supported = ('html', 'html4css1', 'xhtml')
    """Formats this writer supports."""

    settings_spec = (
        'HTML-Specific Options',
        None,
        (('Specify a stylesheet URL, used verbatim.  Default is '
          '"default.css".  Overridden by --stylesheet-path.',
          ['--stylesheet'],
          {'default': 'default.css', 'metavar': '<URL>'}),
         ('Specify a stylesheet file, relative to the current working '
          'directory.  The path is adjusted relative to the output HTML '
          'file.  Overrides --stylesheet.',
          ['--stylesheet-path'],
          {'metavar': '<file>'}),
         ('Link to the stylesheet in the output HTML file.  This is the '
          'default.',
          ['--link-stylesheet'],
          {'dest': 'embed_stylesheet', 'action': 'store_false',
           'validator': frontend.validate_boolean}),
         ('Embed the stylesheet in the output HTML file.  The stylesheet '
          'file must be accessible during processing (--stylesheet-path is '
          'recommended).  The stylesheet is embedded inside a comment, so it '
          'must not contain the text "--" (two hyphens).  Default: link the '
          'stylesheet, do not embed it.',
          ['--embed-stylesheet'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),
         ('Specify the initial header level.  Default is 1 for "<h1>".  '
          'Does not affect document title & subtitle (see --no-doc-title).',
          ['--initial-header-level'],
          {'choices': '1 2 3 4 5 6'.split(), 'default': '1',
           'metavar': '<level>'}),
         ('Format for footnote references: one of "superscript" or '
          '"brackets".  Default is "superscript".',
          ['--footnote-references'],
          {'choices': ['superscript', 'brackets'], 'default': 'superscript',
           'metavar': '<format>'}),
         ('Format for block quote attributions: one of "dash" (em-dash '
          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
          ['--attribution'],
          {'choices': ['dash', 'parentheses', 'parens', 'none'],
           'default': 'dash', 'metavar': '<format>'}),
         ('Remove extra vertical whitespace between items of bullet lists '
          'and enumerated lists, when list items are "simple" (i.e., all '
          'items each contain one paragraph and/or one "simple" sublist '
          'only).  Default: enabled.',
          ['--compact-lists'],
          {'default': 1, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Disable compact simple bullet and enumerated lists.',
          ['--no-compact-lists'],
          {'dest': 'compact_lists', 'action': 'store_false'}),
         ('Omit the XML declaration.  Use with caution.',
          ['--no-xml-declaration'],
          {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false',
           'validator': frontend.validate_boolean}),))

    relative_path_settings = ('stylesheet_path',)

    config_section = 'html4css1 writer'
    config_section_dependencies = ('writers',)

    def __init__(self):
        writers.Writer.__init__(self)
        self.translator_class = HTMLTranslator

    def translate(self):
        visitor = self.translator_class(self.document)
        self.document.walkabout(visitor)
        self.output = visitor.astext()
        self.visitor = visitor
        for attr in ('head_prefix', 'stylesheet', 'head', 'body_prefix',
                     'body_pre_docinfo', 'docinfo', 'body', 'fragment',
                     'body_suffix'):
            setattr(self, attr, getattr(visitor, attr))

    def assemble_parts(self):
        writers.Writer.assemble_parts(self)
        for part in ('title', 'subtitle', 'docinfo', 'body', 'header',
                     'footer', 'meta', 'stylesheet', 'fragment'):
            self.parts[part] = ''.join(getattr(self.visitor, part))


class HTMLTranslator(nodes.NodeVisitor):

    """
    This HTML writer has been optimized to produce visually compact
    lists (less vertical whitespace).  HTML's mixed content models
    allow list items to contain "<li><p>body elements</p></li>" or
    "<li>just text</li>" or even "<li>text<p>and body
    elements</p>combined</li>", each with different effects.  It would
    be best to stick with strict body elements in list items, but they
    affect vertical spacing in browsers (although they really
    shouldn't).

    Here is an outline of the optimization:

    - Check for and omit <p> tags in "simple" lists: list items
      contain either a single paragraph, a nested simple list, or a
      paragraph followed by a nested simple list.  This means that
      this list can be compact:

          - Item 1.
          - Item 2.

      But this list cannot be compact:

          - Item 1.

            This second paragraph forces space between list items.

          - Item 2.

    - In non-list contexts, omit <p> tags on a paragraph if that
      paragraph is the only child of its parent (footnotes & citations
      are allowed a label first).

    - Regardless of the above, in definitions, table cells, field bodies,
      option descriptions, and list items, mark the first child with
      'class="first"' and the last child with 'class="last"'.  The stylesheet
      sets the margins (top & bottom respectively) to 0 for these elements.

    The ``no_compact_lists`` setting (``--no-compact-lists`` command-line
    option) disables list whitespace optimization.
    """

    xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n'
    doctype = ('<!DOCTYPE html'
               ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
               ' "http://www.w3.org/TR/xhtml1/DTD/'
               'xhtml1-transitional.dtd">\n')
    html_head = ('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="%s" '
                 'lang="%s">\n<head>\n')
    content_type = ('<meta http-equiv="Content-Type" content="text/html; '
                    'charset=%s" />\n')
    generator = ('<meta name="generator" content="Docutils %s: '
                 'http://docutils.sourceforge.net/" />\n')
    stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n'
    embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n'
    named_tags = {'a': 1, 'applet': 1, 'form': 1, 'frame': 1, 'iframe': 1,
                  'img': 1, 'map': 1}
    words_and_spaces = re.compile(r'\S+| +|\n')

    def __init__(self, document):
        nodes.NodeVisitor.__init__(self, document)
        self.settings = settings = document.settings
        lcode = settings.language_code
        self.language = languages.get_language(lcode)
        self.meta = [self.content_type % settings.output_encoding,
                     self.generator % docutils.__version__]
        self.head_prefix = [
              self.doctype,
              self.html_head % (lcode, lcode)]
        self.head_prefix.extend(self.meta)
        if settings.xml_declaration:
            self.head_prefix.insert(0, self.xml_declaration
                                    % settings.output_encoding)
        self.head = []
        if settings.embed_stylesheet:
            stylesheet = self.get_stylesheet_reference(
                os.path.join(os.getcwd(), 'dummy'))
            stylesheet_text = open(stylesheet).read()
            self.stylesheet = [self.embedded_stylesheet % stylesheet_text]
        else:
            stylesheet = self.get_stylesheet_reference()
            if stylesheet:
                self.stylesheet = [self.stylesheet_link % stylesheet]
            else:
                self.stylesheet = []
        self.body_prefix = ['</head>\n<body>\n']
        # document title, subtitle display
        self.body_pre_docinfo = []
        # author, date, etc.
        self.docinfo = []
        self.body = []
        self.fragment = []
        self.body_suffix = ['</body>\n</html>\n']
        self.section_level = 0
        self.initial_header_level = int(settings.initial_header_level)
        # A heterogenous stack used in conjunction with the tree traversal.
        # Make sure that the pops correspond to the pushes:
        self.context = []
        self.topic_class = ''
        self.colspecs = []
        self.compact_p = 1
        self.compact_simple = None
        self.in_docinfo = None
        self.in_sidebar = None
        self.title = []
        self.subtitle = []
        self.header = []
        self.footer = []
        self.in_document_title = 0

    def get_stylesheet_reference(self, relative_to=None):
        settings = self.settings
        if settings.stylesheet_path:
            if relative_to == None:
                relative_to = settings._destination
            return utils.relative_path(relative_to, settings.stylesheet_path)
        else:
            return settings.stylesheet

    def astext(self):
        return ''.join(self.head_prefix + self.head
                       + self.stylesheet + self.body_prefix
                       + self.body_pre_docinfo + self.docinfo
                       + self.body + self.body_suffix)

    def encode(self, text):
        """Encode special characters in `text` & return."""
        # @@@ A codec to do these and all other HTML entities would be nice.
        text = text.replace("&", "&amp;")
        text = text.replace("<", "&lt;")
        text = text.replace('"', "&quot;")
        text = text.replace(">", "&gt;")
        text = text.replace("@", "&#64;") # may thwart some address harvesters
        return text

    def attval(self, text,
               whitespace=re.compile('[\n\r\t\v\f]')):
        """Cleanse, HTML encode, and return attribute value text."""
        return self.encode(whitespace.sub(' ', text))

    def starttag(self, node, tagname, suffix='\n', infix='', **attributes):
        """
        Construct and return a start tag given a node (id & class attributes
        are extracted), tag name, and optional attributes.
        """
        tagname = tagname.lower()
        atts = {}
        for (name, value) in attributes.items():
            atts[name.lower()] = value
        for att in ('class',):          # append to node attribute
            if node.has_key(att) or atts.has_key(att):
                atts[att] = \
                      (node.get(att, '') + ' ' + atts.get(att, '')).strip()
        for att in ('id',):             # node attribute overrides
            if node.has_key(att):
                atts[att] = node[att]
        if atts.has_key('id') and self.named_tags.has_key(tagname):
            atts['name'] = atts['id']   # for compatibility with old browsers
        attlist = atts.items()
        attlist.sort()
        parts = [tagname]
        for name, value in attlist:
            if value is None:           # boolean attribute
                # According to the HTML spec, ``<element boolean>`` is good,
                # ``<element boolean="boolean">`` is bad.
                # (But the XHTML (XML) spec says the opposite.  <sigh>)
                parts.append(name.lower())
            elif isinstance(value, ListType):
                values = [unicode(v) for v in value]
                parts.append('%s="%s"' % (name.lower(),
                                          self.attval(' '.join(values))))
            else:
                try:
                    uval = unicode(value)
                except TypeError:       # for Python 2.1 compatibility:
                    uval = unicode(str(value))
                parts.append('%s="%s"' % (name.lower(), self.attval(uval)))
        return '<%s%s>%s' % (' '.join(parts), infix, suffix)

    def emptytag(self, node, tagname, suffix='\n', **attributes):
        """Construct and return an XML-compatible empty tag."""
        return self.starttag(node, tagname, suffix, infix=' /', **attributes)

    def visit_Text(self, node):
        self.body.append(self.encode(node.astext()))

    def depart_Text(self, node):
        pass

    def visit_abbreviation(self, node):
        # @@@ implementation incomplete ("title" attribute)
        self.body.append(self.starttag(node, 'abbr', ''))

    def depart_abbreviation(self, node):
        self.body.append('</abbr>')

    def visit_acronym(self, node):
        # @@@ implementation incomplete ("title" attribute)
        self.body.append(self.starttag(node, 'acronym', ''))

    def depart_acronym(self, node):
        self.body.append('</acronym>')

    def visit_address(self, node):
        self.visit_docinfo_item(node, 'address', meta=None)
        self.body.append(self.starttag(node, 'pre', CLASS='address'))

    def depart_address(self, node):
        self.body.append('\n</pre>\n')
        self.depart_docinfo_item()

    def visit_admonition(self, node, name=''):
        self.body.append(self.starttag(node, 'div',
                                        CLASS=(name or 'admonition')))
        if name:
            self.body.append('<p class="admonition-title first">'
                             + self.language.labels[name] + '</p>\n')

    def depart_admonition(self, node=None):
        self.body.append('</div>\n')

    def visit_attention(self, node):
        self.visit_admonition(node, 'attention')

    def depart_attention(self, node):
        self.depart_admonition()

    attribution_formats = {'dash': ('&mdash;', ''),
                           'parentheses': ('(', ')'),
                           'parens': ('(', ')'),
                           'none': ('', '')}

    def visit_attribution(self, node):
        prefix, suffix = self.attribution_formats[self.settings.attribution]
        self.context.append(suffix)
        self.body.append(
            self.starttag(node, 'p', prefix, CLASS='attribution'))

    def depart_attribution(self, node):
        self.body.append(self.context.pop() + '</p>\n')

    def visit_author(self, node):
        self.visit_docinfo_item(node, 'author')

    def depart_author(self, node):
        self.depart_docinfo_item()

    def visit_authors(self, node):
        pass

    def depart_authors(self, node):
        pass

    def visit_block_quote(self, node):
        self.body.append(self.starttag(node, 'blockquote'))

    def depart_block_quote(self, node):
        self.body.append('</blockquote>\n')

    def check_simple_list(self, node):
        """Check for a simple list that can be rendered compactly."""
        visitor = SimpleListChecker(self.document)
        try:
            node.walk(visitor)
        except nodes.NodeFound:
            return None
        else:
            return 1

    def visit_bullet_list(self, node):
        atts = {}
        old_compact_simple = self.compact_simple
        self.context.append((self.compact_simple, self.compact_p))
        self.compact_p = None
        self.compact_simple = (self.settings.compact_lists and
                               (self.compact_simple
                                or self.topic_class == 'contents'
                                or self.check_simple_list(node)))
        if self.compact_simple and not old_compact_simple:
            atts['class'] = 'simple'
        self.body.append(self.starttag(node, 'ul', **atts))

    def depart_bullet_list(self, node):
        self.compact_simple, self.compact_p = self.context.pop()
        self.body.append('</ul>\n')

    def visit_caption(self, node):
        self.body.append(self.starttag(node, 'p', '', CLASS='caption'))

    def depart_caption(self, node):
        self.body.append('</p>\n')

    def visit_caution(self, node):
        self.visit_admonition(node, 'caution')

    def depart_caution(self, node):
        self.depart_admonition()

    def visit_citation(self, node):
        self.body.append(self.starttag(node, 'table', CLASS='citation',
                                       frame="void", rules="none"))
        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
                         '<tbody valign="top">\n'
                         '<tr>')
        self.footnote_backrefs(node)

    def depart_citation(self, node):
        self.body.append('</td></tr>\n'
                         '</tbody>\n</table>\n')

    def visit_citation_reference(self, node):
        href = ''
        if node.has_key('refid'):
            href = '#' + node['refid']
        elif node.has_key('refname'):
            href = '#' + self.document.nameids[node['refname']]
        self.body.append(self.starttag(node, 'a', '[', href=href,
                                       CLASS='citation-reference'))

    def depart_citation_reference(self, node):
        self.body.append(']</a>')

    def visit_classifier(self, node):
        self.body.append(' <span class="classifier-delimiter">:</span> ')
        self.body.append(self.starttag(node, 'span', '', CLASS='classifier'))

    def depart_classifier(self, node):
        self.body.append('</span>')

    def visit_colspec(self, node):
        self.colspecs.append(node)

    def depart_colspec(self, node):
        pass

    def write_colspecs(self):
        width = 0
        for node in self.colspecs:
            width += node['colwidth']
        for node in self.colspecs:
            colwidth = int(node['colwidth'] * 100.0 / width + 0.5)
            self.body.append(self.emptytag(node, 'col',
                                           width='%i%%' % colwidth))
        self.colspecs = []

    def visit_comment(self, node,
                      sub=re.compile('-(?=-)').sub):
        """Escape double-dashes in comment text."""
        self.body.append('<!-- %s -->\n' % sub('- ', node.astext()))
        # Content already processed:
        raise nodes.SkipNode

    def visit_contact(self, node):
        self.visit_docinfo_item(node, 'contact', meta=None)

    def depart_contact(self, node):
        self.depart_docinfo_item()

    def visit_copyright(self, node):
        self.visit_docinfo_item(node, 'copyright')

    def depart_copyright(self, node):
        self.depart_docinfo_item()

    def visit_danger(self, node):
        self.visit_admonition(node, 'danger')

    def depart_danger(self, node):
        self.depart_admonition()

    def visit_date(self, node):
        self.visit_docinfo_item(node, 'date')

    def depart_date(self, node):
        self.depart_docinfo_item()

    def visit_decoration(self, node):
        pass

    def depart_decoration(self, node):
        pass

    def visit_definition(self, node):
        self.body.append('</dt>\n')
        self.body.append(self.starttag(node, 'dd', ''))
        if len(node):
            node[0].set_class('first')
            node[-1].set_class('last')

    def depart_definition(self, node):
        self.body.append('</dd>\n')

    def visit_definition_list(self, node):
        self.body.append(self.starttag(node, 'dl'))

    def depart_definition_list(self, node):
        self.body.append('</dl>\n')

    def visit_definition_list_item(self, node):
        pass

    def depart_definition_list_item(self, node):
        pass

    def visit_description(self, node):
        self.body.append(self.starttag(node, 'td', ''))
        if len(node):
            node[0].set_class('first')
            node[-1].set_class('last')

    def depart_description(self, node):
        self.body.append('</td>')

    def visit_docinfo(self, node):
        self.context.append(len(self.body))
        self.body.append(self.starttag(node, 'table', CLASS='docinfo',
                                       frame="void", rules="none"))
        self.body.append('<col class="docinfo-name" />\n'
                         '<col class="docinfo-content" />\n'
                         '<tbody valign="top">\n')
        self.in_docinfo = 1

    def depart_docinfo(self, node):
        self.body.append('</tbody>\n</table>\n')
        self.in_docinfo = None
        start = self.context.pop()
        self.docinfo = self.body[start:]
        self.body = []

    def visit_docinfo_item(self, node, name, meta=1):
        if meta:
            meta_tag = '<meta name="%s" content="%s" />\n' \
                       % (name, self.attval(node.astext()))
            self.add_meta(meta_tag)
        self.body.append(self.starttag(node, 'tr', ''))
        self.body.append('<th class="docinfo-name">%s:</th>\n<td>'
                         % self.language.labels[name])
        if len(node):
            if isinstance(node[0], nodes.Element):
                node[0].set_class('first')
            if isinstance(node[-1], nodes.Element):
                node[-1].set_class('last')

    def depart_docinfo_item(self):
        self.body.append('</td></tr>\n')

    def visit_doctest_block(self, node):
        self.body.append(self.starttag(node, 'pre', CLASS='doctest-block'))

    def depart_doctest_block(self, node):
        self.body.append('\n</pre>\n')

    def visit_document(self, node):
        # empty or untitled document?
        if not len(node) or not isinstance(node[0], nodes.title):
            # for XHTML conformance, modulo IE6 appeasement:
            self.head.insert(0, '<title></title>\n')

    def depart_document(self, node):
        self.fragment.extend(self.body)
        self.body.insert(0, self.starttag(node, 'div', CLASS='document'))
        self.body.append('</div>\n')

    def visit_emphasis(self, node):
        self.body.append('<em>')

    def depart_emphasis(self, node):
        self.body.append('</em>')

    def visit_entry(self, node):
        if isinstance(node.parent.parent, nodes.thead):
            tagname = 'th'
        else:
            tagname = 'td'
        atts = {}
        if node.has_key('morerows'):
            atts['rowspan'] = node['morerows'] + 1
        if node.has_key('morecols'):
            atts['colspan'] = node['morecols'] + 1
        self.body.append(self.starttag(node, tagname, '', **atts))
        self.context.append('</%s>\n' % tagname.lower())
        if len(node) == 0:              # empty cell
            self.body.append('&nbsp;')
        else:
            node[0].set_class('first')
            node[-1].set_class('last')

    def depart_entry(self, node):
        self.body.append(self.context.pop())

    def visit_enumerated_list(self, node):
        """
        The 'start' attribute does not conform to HTML 4.01's strict.dtd, but
        CSS1 doesn't help. CSS2 isn't widely enough supported yet to be
        usable.
        """
        atts = {}
        if node.has_key('start'):
            atts['start'] = node['start']
        if node.has_key('enumtype'):
            atts['class'] = node['enumtype']
        # @@@ To do: prefix, suffix. How? Change prefix/suffix to a
        # single "format" attribute? Use CSS2?
        old_compact_simple = self.compact_simple
        self.context.append((self.compact_simple, self.compact_p))
        self.compact_p = None
        self.compact_simple = (self.settings.compact_lists and
                               (self.compact_simple
                                or self.topic_class == 'contents'
                                or self.check_simple_list(node)))
        if self.compact_simple and not old_compact_simple:
            atts['class'] = (atts.get('class', '') + ' simple').strip()
        self.body.append(self.starttag(node, 'ol', **atts))

    def depart_enumerated_list(self, node):
        self.compact_simple, self.compact_p = self.context.pop()
        self.body.append('</ol>\n')

    def visit_error(self, node):
        self.visit_admonition(node, 'error')

    def depart_error(self, node):
        self.depart_admonition()

    def visit_field(self, node):
        self.body.append(self.starttag(node, 'tr', '', CLASS='field'))

    def depart_field(self, node):
        self.body.append('</tr>\n')

    def visit_field_body(self, node):
        self.body.append(self.starttag(node, 'td', '', CLASS='field-body'))
        if len(node):
            node[0].set_class('first')
            node[-1].set_class('last')

    def depart_field_body(self, node):
        self.body.append('</td>\n')

    def visit_field_list(self, node):
        self.body.append(self.starttag(node, 'table', frame='void',
                                       rules='none', CLASS='field-list'))
        self.body.append('<col class="field-name" />\n'
                         '<col class="field-body" />\n'
                         '<tbody valign="top">\n')

    def depart_field_list(self, node):
        self.body.append('</tbody>\n</table>\n')

    def visit_field_name(self, node):
        atts = {}
        if self.in_docinfo:
            atts['class'] = 'docinfo-name'
        else:
            atts['class'] = 'field-name'
        if len(node.astext()) > 14:
            atts['colspan'] = 2
            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
        else:
            self.context.append('')
        self.body.append(self.starttag(node, 'th', '', **atts))

    def depart_field_name(self, node):
        self.body.append(':</th>')
        self.body.append(self.context.pop())

    def visit_figure(self, node):
        atts = {'class': 'figure'}
        if node.get('width'):
            atts['style'] = 'width: %spx' % node['width']
        self.body.append(self.starttag(node, 'div', **atts))

    def depart_figure(self, node):
        self.body.append('</div>\n')

    def visit_footer(self, node):
        self.context.append(len(self.body))

    def depart_footer(self, node):
        start = self.context.pop()
        footer = (['<hr class="footer" />\n',
                   self.starttag(node, 'div', CLASS='footer')]
                  + self.body[start:] + ['</div>\n'])
        self.footer.extend(footer)
        self.body_suffix[:0] = footer
        del self.body[start:]

    def visit_footnote(self, node):
        self.body.append(self.starttag(node, 'table', CLASS='footnote',
                                       frame="void", rules="none"))
        self.body.append('<colgroup><col class="label" /><col /></colgroup>\n'
                         '<tbody valign="top">\n'
                         '<tr>')
        self.footnote_backrefs(node)

    def footnote_backrefs(self, node):
        if self.settings.footnote_backlinks and node.hasattr('backrefs'):
            backrefs = node['backrefs']
            if len(backrefs) == 1:
                self.context.append('')
                self.context.append('<a class="fn-backref" href="#%s" '
                                    'name="%s">' % (backrefs[0], node['id']))
            else:
                i = 1
                backlinks = []
                for backref in backrefs:
                    backlinks.append('<a class="fn-backref" href="#%s">%s</a>'
                                     % (backref, i))
                    i += 1
                self.context.append('<em>(%s)</em> ' % ', '.join(backlinks))
                self.context.append('<a name="%s">' % node['id'])
        else:
            self.context.append('')
            self.context.append('<a name="%s">' % node['id'])

    def depart_footnote(self, node):
        self.body.append('</td></tr>\n'
                         '</tbody>\n</table>\n')

    def visit_footnote_reference(self, node):
        href = ''
        if node.has_key('refid'):
            href = '#' + node['refid']
        elif node.has_key('refname'):
            href = '#' + self.document.nameids[node['refname']]
        format = self.settings.footnote_references
        if format == 'brackets':
            suffix = '['
            self.context.append(']')
        elif format == 'superscript':
            suffix = '<sup>'
            self.context.append('</sup>')
        else:                           # shouldn't happen
            suffix = '???'
            self.content.append('???')
        self.body.append(self.starttag(node, 'a', suffix, href=href,
                                       CLASS='footnote-reference'))

    def depart_footnote_reference(self, node):
        self.body.append(self.context.pop() + '</a>')

    def visit_generated(self, node):
        pass

    def depart_generated(self, node):
        pass

    def visit_header(self, node):
        self.context.append(len(self.body))

    def depart_header(self, node):
        start = self.context.pop()
        header = [self.starttag(node, 'div', CLASS='header')]
        header.extend(self.body[start:])
        header.append('<hr />\n</div>\n')
        self.body_prefix.extend(header)
        self.header = header
        del self.body[start:]

    def visit_hint(self, node):
        self.visit_admonition(node, 'hint')

    def depart_hint(self, node):
        self.depart_admonition()

    def visit_image(self, node):
        atts = node.attributes.copy()
        if atts.has_key('class'):
            del atts['class']           # prevent duplication with node attrs
        atts['src'] = atts['uri']
        del atts['uri']
        if atts.has_key('scale'):
            if Image and not (atts.has_key('width')
                              and atts.has_key('height')):
                try:
                    im = Image.open(str(atts['src']))
                except (IOError, # Source image can't be found or opened
                        UnicodeError):  # PIL doesn't like Unicode paths.
                    pass
                else:
                    if not atts.has_key('width'):
                        atts['width'] = im.size[0]
                    if not atts.has_key('height'):
                        atts['height'] = im.size[1]
                    del im
            if atts.has_key('width'):
                atts['width'] = int(round(atts['width']
                                          * (float(atts['scale']) / 100)))
            if atts.has_key('height'):
                atts['height'] = int(round(atts['height']
                                           * (float(atts['scale']) / 100)))
            del atts['scale']
        if not atts.has_key('alt'):
            atts['alt'] = atts['src']
        if isinstance(node.parent, nodes.TextElement):
            self.context.append('')
        else:
            if atts.has_key('align'):
                self.body.append('<p align="%s">' %
                                 (self.attval(atts['align'],)))
            else:
                self.body.append('<p>')
            self.context.append('</p>\n')
        self.body.append(self.emptytag(node, 'img', '', **atts))

    def depart_image(self, node):
        self.body.append(self.context.pop())

    def visit_important(self, node):
        self.visit_admonition(node, 'important')

    def depart_important(self, node):
        self.depart_admonition()

    def visit_inline(self, node):
        self.body.append(self.starttag(node, 'span', ''))

    def depart_inline(self, node):
        self.body.append('</span>')

    def visit_label(self, node):
        self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(),
                                       CLASS='label'))

    def depart_label(self, node):
        self.body.append(']</a></td><td>%s' % self.context.pop())

    def visit_legend(self, node):
        self.body.append(self.starttag(node, 'div', CLASS='legend'))

    def depart_legend(self, node):
        self.body.append('</div>\n')

    def visit_line_block(self, node):
        self.body.append(self.starttag(node, 'pre', CLASS='line-block'))

    def depart_line_block(self, node):
        self.body.append('\n</pre>\n')

    def visit_list_item(self, node):
        self.body.append(self.starttag(node, 'li', ''))
        if len(node):
            node[0].set_class('first')

    def depart_list_item(self, node):
        self.body.append('</li>\n')

    def visit_literal(self, node):
        """Process text to prevent tokens from wrapping."""
        self.body.append(self.starttag(node, 'tt', '', CLASS='literal'))
        text = node.astext()
        for token in self.words_and_spaces.findall(text):
            if token.strip():
                # Protect text like "--an-option" from bad line wrapping:
                self.body.append('<span class="pre">%s</span>'
                                 % self.encode(token))
            elif token in ('\n', ' '):
                # Allow breaks at whitespace:
                self.body.append(token)
            else:
                # Protect runs of multiple spaces; the last space can wrap:
                self.body.append('&nbsp;' * (len(token) - 1) + ' ')
        self.body.append('</tt>')
        # Content already processed:
        raise nodes.SkipNode

    def visit_literal_block(self, node):
        self.body.append(self.starttag(node, 'pre', CLASS='literal-block'))

    def depart_literal_block(self, node):
        self.body.append('\n</pre>\n')

    def visit_meta(self, node):
        meta = self.emptytag(node, 'meta', **node.attributes)
        self.add_meta(meta)

    def depart_meta(self, node):
        pass

    def add_meta(self, tag):
        self.meta.append(tag)
        self.head.append(tag)

    def visit_note(self, node):
        self.visit_admonition(node, 'note')

    def depart_note(self, node):
        self.depart_admonition()

    def visit_option(self, node):
        if self.context[-1]:
            self.body.append(', ')

    def depart_option(self, node):
        self.context[-1] += 1

    def visit_option_argument(self, node):
        self.body.append(node.get('delimiter', ' '))
        self.body.append(self.starttag(node, 'var', ''))

    def depart_option_argument(self, node):
        self.body.append('</var>')

    def visit_option_group(self, node):
        atts = {}
        if len(node.astext()) > 14:
            atts['colspan'] = 2
            self.context.append('</tr>\n<tr><td>&nbsp;</td>')
        else:
            self.context.append('')
        self.body.append(self.starttag(node, 'td', **atts))
        self.body.append('<kbd>')
        self.context.append(0)          # count number of options

    def depart_option_group(self, node):
        self.context.pop()
        self.body.append('</kbd></td>\n')
        self.body.append(self.context.pop())

    def visit_option_list(self, node):
        self.body.append(
              self.starttag(node, 'table', CLASS='option-list',
                            frame="void", rules="none"))
        self.body.append('<col class="option" />\n'
                         '<col class="description" />\n'
                         '<tbody valign="top">\n')

    def depart_option_list(self, node):
        self.body.append('</tbody>\n</table>\n')

    def visit_option_list_item(self, node):
        self.body.append(self.starttag(node, 'tr', ''))

    def depart_option_list_item(self, node):
        self.body.append('</tr>\n')

    def visit_option_string(self, node):
        self.body.append(self.starttag(node, 'span', '', CLASS='option'))

    def depart_option_string(self, node):
        self.body.append('</span>')

    def visit_organization(self, node):
        self.visit_docinfo_item(node, 'organization')

    def depart_organization(self, node):
        self.depart_docinfo_item()

    def visit_paragraph(self, node):
        # Omit <p> tags if this is an only child and optimizable.
        if (self.compact_simple or
            self.compact_p and (len(node.parent) == 1 or
                                len(node.parent) == 2 and
                                isinstance(node.parent[0], nodes.label))):
            self.context.append('')
        else:
            self.body.append(self.starttag(node, 'p', ''))
            self.context.append('</p>\n')

    def depart_paragraph(self, node):
        self.body.append(self.context.pop())

    def visit_problematic(self, node):
        if node.hasattr('refid'):
            self.body.append('<a href="#%s" name="%s">' % (node['refid'],
                                                           node['id']))
            self.context.append('</a>')
        else:
            self.context.append('')
        self.body.append(self.starttag(node, 'span', '', CLASS='problematic'))

    def depart_problematic(self, node):
        self.body.append('</span>')
        self.body.append(self.context.pop())

    def visit_raw(self, node):
        if node.get('format') == 'html':
            self.body.append(node.astext())
        # Keep non-HTML raw text out of output:
        raise nodes.SkipNode

    def visit_reference(self, node):
        if isinstance(node.parent, nodes.TextElement):
            self.context.append('')
        else:
            self.body.append('<p>')
            self.context.append('</p>\n')
        if node.has_key('refuri'):
            href = node['refuri']
        elif node.has_key('refid'):
            href = '#' + node['refid']
        elif node.has_key('refname'):
            href = '#' + self.document.nameids[node['refname']]
        self.body.append(self.starttag(node, 'a', '', href=href,
                                       CLASS='reference'))

    def depart_reference(self, node):
        self.body.append('</a>')
        self.body.append(self.context.pop())

    def visit_revision(self, node):
        self.visit_docinfo_item(node, 'revision', meta=None)

    def depart_revision(self, node):
        self.depart_docinfo_item()

    def visit_row(self, node):
        self.body.append(self.starttag(node, 'tr', ''))

    def depart_row(self, node):
        self.body.append('</tr>\n')

    def visit_rubric(self, node):
        self.body.append(self.starttag(node, 'p', '', CLASS='rubric'))

    def depart_rubric(self, node):
        self.body.append('</p>\n')

    def visit_section(self, node):
        self.section_level += 1
        self.body.append(self.starttag(node, 'div', CLASS='section'))

    def depart_section(self, node):
        self.section_level -= 1
        self.body.append('</div>\n')

    def visit_sidebar(self, node):
        self.body.append(self.starttag(node, 'div', CLASS='sidebar'))
        self.in_sidebar = 1

    def depart_sidebar(self, node):
        self.body.append('</div>\n')
        self.in_sidebar = None

    def visit_status(self, node):
        self.visit_docinfo_item(node, 'status', meta=None)

    def depart_status(self, node):
        self.depart_docinfo_item()

    def visit_strong(self, node):
        self.body.append('<strong>')

    def depart_strong(self, node):
        self.body.append('</strong>')

    def visit_subscript(self, node):
        self.body.append(self.starttag(node, 'sub', ''))

    def depart_subscript(self, node):
        self.body.append('</sub>')

    def visit_substitution_definition(self, node):
        """Internal only."""
        raise nodes.SkipNode

    def visit_substitution_reference(self, node):
        self.unimplemented_visit(node)

    def visit_subtitle(self, node):
        if isinstance(node.parent, nodes.sidebar):
            self.body.append(self.starttag(node, 'p', '',
                                           CLASS='sidebar-subtitle'))
            self.context.append('</p>\n')
        elif isinstance(node.parent, nodes.document):
            self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle'))
            self.context.append('</h2>\n')
            self.in_document_title = len(self.body)

    def depart_subtitle(self, node):
        self.body.append(self.context.pop())
        if self.in_document_title:
            self.subtitle = self.body[self.in_document_title:-1]
            self.in_document_title = 0
            self.body_pre_docinfo.extend(self.body)
            del self.body[:]

    def visit_superscript(self, node):
        self.body.append(self.starttag(node, 'sup', ''))

    def depart_superscript(self, node):
        self.body.append('</sup>')

    def visit_system_message(self, node):
        if node['level'] < self.document.reporter['writer'].report_level:
            # Level is too low to display:
            raise nodes.SkipNode
        self.body.append(self.starttag(node, 'div', CLASS='system-message'))
        self.body.append('<p class="system-message-title">')
        attr = {}
        backref_text = ''
        if node.hasattr('id'):
            attr['name'] = node['id']
        if node.hasattr('backrefs'):
            backrefs = node['backrefs']
            if len(backrefs) == 1:
                backref_text = ('; <em><a href="#%s">backlink</a></em>'
                                % backrefs[0])
            else:
                i = 1
                backlinks = []
                for backref in backrefs:
                    backlinks.append('<a href="#%s">%s</a>' % (backref, i))
                    i += 1
                backref_text = ('; <em>backlinks: %s</em>'
                                % ', '.join(backlinks))
        if node.hasattr('line'):
            line = ', line %s' % node['line']
        else:
            line = ''
        if attr:
            a_start = self.starttag({}, 'a', '', **attr)
            a_end = '</a>'
        else:
            a_start = a_end = ''
        self.body.append('System Message: %s%s/%s%s (<tt>%s</tt>%s)%s</p>\n'
                         % (a_start, node['type'], node['level'], a_end,
                            self.encode(node['source']), line, backref_text))

    def depart_system_message(self, node):
        self.body.append('</div>\n')

    def visit_table(self, node):
        self.body.append(
              # "border=None" is a boolean attribute;
              # it means "standard border", not "no border":
              self.starttag(node, 'table', CLASS="table", border="1"))

    def depart_table(self, node):
        self.body.append('</table>\n')

    def visit_target(self, node):
        if not (node.has_key('refuri') or node.has_key('refid')
                or node.has_key('refname')):
            self.body.append(self.starttag(node, 'a', '', CLASS='target'))
            self.context.append('</a>')
        else:
            self.context.append('')

    def depart_target(self, node):
        self.body.append(self.context.pop())

    def visit_tbody(self, node):
        self.write_colspecs()
        self.body.append(self.context.pop()) # '</colgroup>\n' or ''
        self.body.append(self.starttag(node, 'tbody', valign='top'))

    def depart_tbody(self, node):
        self.body.append('</tbody>\n')

    def visit_term(self, node):
        self.body.append(self.starttag(node, 'dt', ''))

    def depart_term(self, node):
        """
        Leave the end tag to `self.visit_definition()`, in case there's a
        classifier.
        """
        pass

    def visit_tgroup(self, node):
        # Mozilla needs <colgroup>:
        self.body.append(self.starttag(node, 'colgroup'))
        # Appended by thead or tbody:
        self.context.append('</colgroup>\n')

    def depart_tgroup(self, node):
        pass

    def visit_thead(self, node):
        self.write_colspecs()
        self.body.append(self.context.pop()) # '</colgroup>\n'
        # There may or may not be a <thead>; this is for <tbody> to use:
        self.context.append('')
        self.body.append(self.starttag(node, 'thead', valign='bottom'))

    def depart_thead(self, node):
        self.body.append('</thead>\n')

    def visit_tip(self, node):
        self.visit_admonition(node, 'tip')

    def depart_tip(self, node):
        self.depart_admonition()

    def visit_title(self, node):
        """Only 6 section levels are supported by HTML."""
        check_id = 0
        close_tag = '</p>\n'
        if isinstance(node.parent, nodes.topic):
            self.body.append(
                  self.starttag(node, 'p', '', CLASS='topic-title first'))
            check_id = 1
        elif isinstance(node.parent, nodes.sidebar):
            self.body.append(
                  self.starttag(node, 'p', '', CLASS='sidebar-title first'))
            check_id = 1
        elif isinstance(node.parent, nodes.admonition):
            self.body.append(
                  self.starttag(node, 'p', '', CLASS='admonition-title first'))
            check_id = 1
        elif isinstance(node.parent, nodes.table):
            self.body.append(
                  self.starttag(node, 'caption', ''))
            check_id = 1
            close_tag = '</caption>\n'
        elif self.section_level == 0:
            # document title
            self.head.append('<title>%s</title>\n'
                             % self.encode(node.astext()))
            self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
            self.context.append('</h1>\n')
            self.in_document_title = len(self.body)
        else:
            h_level = self.section_level + self.initial_header_level - 1
            self.body.append(
                  self.starttag(node, 'h%s' % h_level, ''))
            atts = {}
            if node.parent.hasattr('id'):
                atts['name'] = node.parent['id']
            if node.hasattr('refid'):
                atts['class'] = 'toc-backref'
                atts['href'] = '#' + node['refid']
            self.body.append(self.starttag({}, 'a', '', **atts))
            self.context.append('</a></h%s>\n' % (h_level))
        if check_id:
            if node.parent.hasattr('id'):
                self.body.append(
                    self.starttag({}, 'a', '', name=node.parent['id']))
                self.context.append('</a>' + close_tag)
            else:
                self.context.append(close_tag)

    def depart_title(self, node):
        self.body.append(self.context.pop())
        if self.in_document_title:
            self.title = self.body[self.in_document_title:-1]
            self.in_document_title = 0
            self.body_pre_docinfo.extend(self.body)
            del self.body[:]

    def visit_title_reference(self, node):
        self.body.append(self.starttag(node, 'cite', ''))

    def depart_title_reference(self, node):
        self.body.append('</cite>')

    def visit_topic(self, node):
        self.body.append(self.starttag(node, 'div', CLASS='topic'))
        self.topic_class = node.get('class')

    def depart_topic(self, node):
        self.body.append('</div>\n')
        self.topic_class = ''

    def visit_transition(self, node):
        self.body.append(self.emptytag(node, 'hr'))

    def depart_transition(self, node):
        pass

    def visit_version(self, node):
        self.visit_docinfo_item(node, 'version', meta=None)

    def depart_version(self, node):
        self.depart_docinfo_item()

    def visit_warning(self, node):
        self.visit_admonition(node, 'warning')

    def depart_warning(self, node):
        self.depart_admonition()

    def unimplemented_visit(self, node):
        raise NotImplementedError('visiting unimplemented node type: %s'
                                  % node.__class__.__name__)


class SimpleListChecker(nodes.GenericNodeVisitor):

    """
    Raise `nodes.SkipNode` if non-simple list item is encountered.

    Here "simple" means a list item containing nothing other than a single
    paragraph, a simple list, or a paragraph followed by a simple list.
    """

    def default_visit(self, node):
        raise nodes.NodeFound

    def visit_bullet_list(self, node):
        pass

    def visit_enumerated_list(self, node):
        pass

    def visit_list_item(self, node):
        children = []
        for child in node.get_children():
            if not isinstance(child, nodes.Invisible):
                children.append(child)
        if (children and isinstance(children[0], nodes.paragraph)
            and (isinstance(children[-1], nodes.bullet_list)
                 or isinstance(children[-1], nodes.enumerated_list))):
            children.pop()
        if len(children) <= 1:
            return
        else:
            raise nodes.NodeFound

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

    def invisible_visit(self, node):
        """Invisible nodes should be ignored."""
        pass

    visit_comment = invisible_visit
    visit_substitution_definition = invisible_visit
    visit_target = invisible_visit
    visit_pending = invisible_visit


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/latex2e.py ===
"""
:Author: Engelbert Gruber
:Contact: grubert at users.sourceforge.net
:Revision: $Revision: 1.1.2.1 $
:Date: $Date: 2004/10/29 18:24:54 $
:Copyright: This module has been placed in the public domain.

LaTeX2e document tree Writer.
"""

__docformat__ = 'reStructuredText'

# code contributions from several people included, thanks too all.
# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others.
#
# convention deactivate code by two # e.g. ##.

import sys
import time
import re
import string
from types import ListType
from docutils import frontend, nodes, languages, writers

class Writer(writers.Writer):

    supported = ('latex','latex2e')
    """Formats this writer supports."""

    settings_spec = (
        'LaTeX-Specific Options',
        'The LaTeX "--output-encoding" default is "latin-1:strict".',
        (('Specify documentclass.  Default is "article".',
          ['--documentclass'],
          {'default': 'article', }),
         ('Specify document options.  Multiple options can be given, '
          'separated by commas.  Default is "10pt,a4paper".',
          ['--documentoptions'],
          {'default': '10pt,a4paper', }),
         ('Use LaTeX footnotes. LaTeX supports only numbered footnotes (does it?). '
          'Default: no, uses figures.',
          ['--use-latex-footnotes'],
          {'default': 0, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Format for footnote references: one of "superscript" or '
          '"brackets".  Default is "brackets".',
          ['--footnote-references'],
          {'choices': ['superscript', 'brackets'], 'default': 'brackets',
           'metavar': '<format>'}),
         ('Use LaTeX citations. '
          'Default: no, uses figures which might get mixed with images.',
          ['--use-latex-citations'],
          {'default': 0, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Format for block quote attributions: one of "dash" (em-dash '
          'prefix), "parentheses"/"parens", or "none".  Default is "dash".',
          ['--attribution'],
          {'choices': ['dash', 'parentheses', 'parens', 'none'],
           'default': 'dash', 'metavar': '<format>'}),
         ('Specify a stylesheet file. The file will be "input" by latex in '
          'the document header.  Default is no stylesheet ("").  '
          'Overridden by --stylesheet-path.',
          ['--stylesheet'],
          {'default': '', 'metavar': '<file>'}),
         ('Specify a stylesheet file, relative to the current working '
          'directory.  Overrides --stylesheet.',
          ['--stylesheet-path'],
          {'metavar': '<file>'}),
         ('Link to the stylesheet in the output LaTeX file.  This is the '
          'default.',
          ['--link-stylesheet'],
          {'dest': 'embed_stylesheet', 'action': 'store_false',
           'validator': frontend.validate_boolean}),
         ('Embed the stylesheet in the output LaTeX file.  The stylesheet '
          'file must be accessible during processing (--stylesheet-path is '
          'recommended).',
          ['--embed-stylesheet'], {'action': 'store_true'}),
         ('Table of contents by docutils (default) or latex. Latex (writer) '
          'supports only one ToC per document, but docutils does not write '
          'pagenumbers.',
          ['--use-latex-toc'],
          {'default': 0, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Let LaTeX print author and date, do not show it in docutils '
          'document info.',
          ['--use-latex-docinfo'],
          {'default': 0, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Color of any hyperlinks embedded in text '
          '(default: "blue", "0" to disable).',
          ['--hyperlink-color'], {'default': 'blue'}),
         ('Enable compound enumerators for nested enumerated lists '
          '(e.g. "1.2.a.ii").  Default: disabled.',
          ['--compound-enumerators'],
          {'default': None, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Disable compound enumerators for nested enumerated lists.  This is '
          'the default.',
          ['--no-compound-enumerators'],
          {'action': 'store_false', 'dest': 'compound_enumerators'}),
         ('Enable section ("." subsection ...) prefixes for compound '
          'enumerators.  This has no effect without --compound-enumerators.  '
          'Default: disabled.',
          ['--section-prefix-for-enumerators'],
          {'default': None, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Disable section prefixes for compound enumerators.  '
          'This is the default.',
          ['--no-section-prefix-for-enumerators'],
          {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}),
         ('Set the separator between section number and enumerator '
          'for compound enumerated lists.  Default is "-".',
          ['--section-enumerator-separator'],
          {'default': '-', 'metavar': '<char>'}),
         ('When possibile, use verbatim for literal-blocks.'
          'Default is to always use the mbox environment.',
          ['--use-verbatim-when-possible'],
          {'default': 0, 'action': 'store_true',
           'validator': frontend.validate_boolean}),
         ('Table style. "standard" with horizontal and vertical lines, '
          '"booktabs" (LaTeX booktabs style) only horizontal lines '
          'above and below the table and below the header or "nolines".'
          'default: "standard"',
          ['--table-style'],
          {'choices': ['standard', 'booktabs','nolines'], 'default': 'standard',
           'metavar': '<format>'}),
          ('LaTeX graphicx package option.'
           'Possible values are "dvips", "pdftex". "auto" includes LaTeX code '
           'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. '
           'Default is no option.',
           ['--graphicx-option'],
           {'default': ''}),
          ),)

    settings_defaults = {'output_encoding': 'latin-1'}

    config_section = 'latex2e writer'
    config_section_dependencies = ('writers',)

    output = None
    """Final translated form of `document`."""

    def translate(self):
        visitor = LaTeXTranslator(self.document)
        self.document.walkabout(visitor)
        self.output = visitor.astext()
        self.head_prefix = visitor.head_prefix
        self.head = visitor.head
        self.body_prefix = visitor.body_prefix
        self.body = visitor.body
        self.body_suffix = visitor.body_suffix

"""
Notes on LaTeX
--------------

* latex does not support multiple tocs in one document.
  (might be no limitation except for docutils documentation)

* width

  * linewidth - width of a line in the local environment
  * textwidth - the width of text on the page

  Maybe always use linewidth ?

  *Bug* inside a minipage a (e.g. Sidebar) the linewidth is
        not changed, needs fix in docutils so that tables
        are not too wide.

        So we add locallinewidth set it initially and
        on entering sidebar and reset on exit.
"""

class Babel:
    """Language specifics for LaTeX."""
    # country code by a.schlock.
    # partly manually converted from iso and babel stuff, dialects and some
    _ISO639_TO_BABEL = {
        'no': 'norsk',     #XXX added by hand ( forget about nynorsk?)
        'gd': 'scottish',  #XXX added by hand
        'hu': 'magyar',    #XXX added by hand
        'pt': 'portuguese',#XXX added by hand
        'sl': 'slovenian',
        'af': 'afrikaans',
        'bg': 'bulgarian',
        'br': 'breton',
        'ca': 'catalan',
        'cs': 'czech',
        'cy': 'welsh',
        'da': 'danish',
        'fr': 'french',
        # french, francais, canadien, acadian
        'de': 'ngerman',  #XXX rather than german
        # ngerman, naustrian, german, germanb, austrian
        'el': 'greek',
        'en': 'english',
        # english, USenglish, american, UKenglish, british, canadian
        'eo': 'esperanto',
        'es': 'spanish',
        'et': 'estonian',
        'eu': 'basque',
        'fi': 'finnish',
        'ga': 'irish',
        'gl': 'galician',
        'he': 'hebrew',
        'hr': 'croatian',
        'hu': 'hungarian',
        'is': 'icelandic',
        'it': 'italian',
        'la': 'latin',
        'nl': 'dutch',
        'pl': 'polish',
        'pt': 'portuguese',
        'ro': 'romanian',
        'ru': 'russian',
        'sk': 'slovak',
        'sr': 'serbian',
        'sv': 'swedish',
        'tr': 'turkish',
        'uk': 'ukrainian'
    }

    def __init__(self,lang):
        self.language = lang
        # pdflatex does not produce double quotes for ngerman in tt.
        self.double_quote_replacment = None
        if re.search('^de',self.language):
            # maybe use: {\glqq} {\grqq}.
            self.quotes = ("\"`", "\"'")
            self.double_quote_replacment = "{\\dq}"
        else:
            self.quotes = ("``", "''")
        self.quote_index = 0

    def next_quote(self):
        q = self.quotes[self.quote_index]
        self.quote_index = (self.quote_index+1)%2
        return q

    def quote_quotes(self,text):
        t = None
        for part in text.split('"'):
            if t == None:
                t = part
            else:
                t += self.next_quote() + part
        return t

    def double_quotes_in_tt (self,text):
        if not self.double_quote_replacment:
            return text
        return text.replace('"', self.double_quote_replacment)

    def get_language(self):
        if self._ISO639_TO_BABEL.has_key(self.language):
            return self._ISO639_TO_BABEL[self.language]
        else:
            # support dialects.
            l = self.language.split("_")[0]
            if self._ISO639_TO_BABEL.has_key(l):
                return self._ISO639_TO_BABEL[l]
        return None


latex_headings = {
        'optionlist_environment' : [
              '\\newcommand{\\optionlistlabel}[1]{\\bf #1 \\hfill}\n'
              '\\newenvironment{optionlist}[1]\n'
              '{\\begin{list}{}\n'
              '  {\\setlength{\\labelwidth}{#1}\n'
              '   \\setlength{\\rightmargin}{1cm}\n'
              '   \\setlength{\\leftmargin}{\\rightmargin}\n'
              '   \\addtolength{\\leftmargin}{\\labelwidth}\n'
              '   \\addtolength{\\leftmargin}{\\labelsep}\n'
              '   \\renewcommand{\\makelabel}{\\optionlistlabel}}\n'
              '}{\\end{list}}\n',
              ],
        'footnote_floats' : [
            '% begin: floats for footnotes tweaking.\n',
            '\\setlength{\\floatsep}{0.5em}\n',
            '\\setlength{\\textfloatsep}{\\fill}\n',
            '\\addtolength{\\textfloatsep}{3em}\n',
            '\\renewcommand{\\textfraction}{0.5}\n',
            '\\renewcommand{\\topfraction}{0.5}\n',
            '\\renewcommand{\\bottomfraction}{0.5}\n',
            '\\setcounter{totalnumber}{50}\n',
            '\\setcounter{topnumber}{50}\n',
            '\\setcounter{bottomnumber}{50}\n',
            '% end floats for footnotes\n',
            ],
         'some_commands' : [
            '% some commands, that could be overwritten in the style file.\n'
            '\\newcommand{\\rubric}[1]'
            '{\\subsection*{~\\hfill {\\it #1} \\hfill ~}}\n'
            '\\newcommand{\\titlereference}[1]{\\textsl{#1}}\n'
            '% end of "some commands"\n',
         ]
        }

class DocumentClass:
    """Details of a LaTeX document class."""

    # BUG: LaTeX has no deeper sections (actually paragrah is no
    # section either).
    # BUG: No support for unknown document classes.  Make 'article'
    # default?
    _class_sections = {
        'book': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
        'scrbook': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
        'report': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
        'scrreprt': ( 'chapter', 'section', 'subsection', 'subsubsection' ),
        'article': ( 'section', 'subsection', 'subsubsection' ),
        'scrartcl': ( 'section', 'subsection', 'subsubsection' ),
        }
    _deepest_section = 'subsubsection'

    def __init__(self, document_class):
        self.document_class = document_class

    def section(self, level):
        """ Return the section name at the given level for the specific
            document class.

            Level is 1,2,3..., as level 0 is the title."""

        sections = self._class_sections[self.document_class]
        if level <= len(sections):
            return sections[level-1]
        else:
            return self._deepest_section

class Table:
    """ Manage a table while traversing. 
        Maybe change to a mixin defining the visit/departs, but then
        class Table internal variables are in the Translator.
    """
    def __init__(self,latex_type,table_style):
        self._latex_type = latex_type
        self._table_style = table_style
        self._open = 0
        # miscellaneous attributes
        self._attrs = {}
        self._col_width = []
        self._rowspan = []

    def open(self):
        self._open = 1
        self._col_specs = []
        self.caption = None
        self._attrs = {}
        self._in_head = 0 # maybe context with search
    def close(self):
        self._open = 0
        self._col_specs = None
        self.caption = None
        self._attrs = {}
    def is_open(self):
        return self._open
    def used_packages(self):
        if self._table_style == 'booktabs':
            return '\\usepackage{booktabs}\n'
        return ''
    def is_open(self):
        return self._open
    def get_latex_type(self):
        return self._latex_type
    
    def set(self,attr,value):
        self._attrs[attr] = value
    def get(self,attr):
        if self._attrs.has_key(attr):
            return self._attrs[attr]
        return None
    def get_vertical_bar(self):
        if self._table_style == 'standard':
            return '|'
        return ''
    # horizontal lines are drawn below a row, because we.
    def get_opening(self):
        return '\\begin{%s}[c]' % self._latex_type
    def get_closing(self):
        line = ""
        if self._table_style == 'booktabs':
            line = '\\bottomrule\n'
        elif self._table_style == 'standard':
            lines = '\\hline\n'
        return '%s\\end{%s}' % (line,self._latex_type)

    def visit_colspec(self,node):
        self._col_specs.append(node)

    def get_colspecs(self):
        """
        Return column specification for longtable.

        Assumes reST line length being 80 characters.
        Table width is hairy.

        === ===
        ABC DEF
        === ===

        usually gets to narrow, therefore we add 1 (fiddlefactor).
        """
        width = 80

        total_width = 0.0
        # first see if we get too wide.
        for node in self._col_specs:
            colwidth = float(node['colwidth']+1) / width
            total_width += colwidth
        self._col_width = []
        self._rowspan = []
        # donot make it full linewidth
        factor = 0.93
        if total_width > 1.0:
            factor /= total_width
        bar = self.get_vertical_bar()
        latex_table_spec = ""
        for node in self._col_specs:
            colwidth = factor * float(node['colwidth']+1) / width
            self._col_width.append(colwidth+0.005)
            self._rowspan.append(0)
            latex_table_spec += "%sp{%.2f\\locallinewidth}" % (bar,colwidth+0.005)
        return latex_table_spec+bar

    def get_column_width(self):
        """ return columnwidth for current cell (not multicell) 
        """
        return "%.2f\\locallinewidth" % self._col_width[self._cell_in_row-1]

    def visit_thead(self):
        self._in_thead = 1
        if self._table_style == 'standard':
            return ['\\hline\n']
        elif self._table_style == 'booktabs':
            return ['\\toprule\n']
        return []
    def depart_thead(self):
        a = []
        #if self._table_style == 'standard':
        #    a.append('\\hline\n')
        if self._table_style == 'booktabs':
            a.append('\\midrule\n')
        a.append('\\endhead\n')
        # for longtable one could add firsthead, foot and lastfoot
        self._in_thead = 0
        return a
    def visit_row(self):
        self._cell_in_row = 0
    def depart_row(self):
        res = [' \\\\\n']
        self._cell_in_row = None  # remove cell counter
        for i in range(len(self._rowspan)):
            if (self._rowspan[i]>0):
                self._rowspan[i] -= 1
        
        if self._table_style == 'standard':
            rowspans = []
            for i in range(len(self._rowspan)):
                if (self._rowspan[i]<=0):
                    rowspans.append(i+1)
            if len(rowspans)==len(self._rowspan):
                res.append('\\hline\n')
            else:
                cline = ''
                rowspans.reverse()
                # TODO merge clines
                while 1:
                    try:
                        c_start = rowspans.pop()
                    except:
                        break
                    cline += '\\cline{%d-%d}\n' % (c_start,c_start)
                res.append(cline)
        return res

    def set_rowspan(self,cell,value):
        try:
            self._rowspan[cell] = value
        except:
            pass
    def get_rowspan(self,cell):
        try:
            return self._rowspan[cell]
        except:
            return 0
    def get_entry_number(self):
        return self._cell_in_row
    def visit_entry(self):
        self._cell_in_row += 1

        
class LaTeXTranslator(nodes.NodeVisitor):
    # When options are given to the documentclass, latex will pass them
    # to other packages, as done with babel.
    # Dummy settings might be taken from document settings

    latex_head = '\\documentclass[%s]{%s}\n'
    encoding = '\\usepackage[%s]{inputenc}\n'
    linking = '\\usepackage[colorlinks=%s,linkcolor=%s,urlcolor=%s]{hyperref}\n'
    stylesheet = '\\input{%s}\n'
    # add a generated on day , machine by user using docutils version.
    generator = '%% generator Docutils: http://docutils.sourceforge.net/\n'

    # use latex tableofcontents or let docutils do it.
    use_latex_toc = 0

    # TODO: use mixins for different implementations.
    # list environment for option-list. else tabularx
    use_optionlist_for_option_list = 1
    # list environment for docinfo. else tabularx
    use_optionlist_for_docinfo = 0 # NOT YET IN USE

    # Use compound enumerations (1.A.1.)
    compound_enumerators = 0

    # If using compound enumerations, include section information.
    section_prefix_for_enumerators = 0

    # This is the character that separates the section ("." subsection ...)
    # prefix from the regular list enumerator.
    section_enumerator_separator = '-'

    # default link color
    hyperlink_color = "blue"

    def __init__(self, document):
        nodes.NodeVisitor.__init__(self, document)
        self.settings = settings = document.settings
        self.use_latex_toc = settings.use_latex_toc
        self.use_latex_docinfo = settings.use_latex_docinfo
        self.use_latex_footnotes = settings.use_latex_footnotes
        self._use_latex_citations = settings.use_latex_citations
        self.hyperlink_color = settings.hyperlink_color
        self.compound_enumerators = settings.compound_enumerators
        self.fontenc = ''
        self.section_prefix_for_enumerators = (
            settings.section_prefix_for_enumerators)
        self.section_enumerator_separator = (
            settings.section_enumerator_separator.replace('_', '\\_'))
        if self.hyperlink_color == '0':
            self.hyperlink_color = 'black'
            self.colorlinks = 'false'
        else:
            self.colorlinks = 'true'

        # language: labels, bibliographic_fields, and author_separators.
        # to allow writing labes for specific languages.
        self.language = languages.get_language(settings.language_code)
        self.babel = Babel(settings.language_code)
        self.author_separator = self.language.author_separators[0]
        self.d_options = self.settings.documentoptions
        if self.babel.get_language():
            self.d_options += ',%s' % \
                    self.babel.get_language()

        self.d_class = DocumentClass(settings.documentclass)
        # object for a table while proccessing.
        self.active_table = Table('longtable',settings.table_style)

        # HACK.  Should have more sophisticated typearea handling.
        if settings.documentclass.find('scr') == -1:
            self.typearea = '\\usepackage[DIV12]{typearea}\n'
        else:
            if self.d_options.find('DIV') == -1 and self.d_options.find('BCOR') == -1:
                self.typearea = '\\typearea{12}\n'
            else:
                self.typearea = ''

        if self.fontenc == 'T1':
            fontenc = '\\usepackage[T1]{fontenc}\n'
        else:
            fontenc = ''

        if self.settings.graphicx_option == '':
            self.graphicx_package = '\\usepackage{graphicx}\n'
        elif self.settings.graphicx_option.lower() == 'auto':
            self.graphicx_package = '\n'.join(
                ('%Check if we are compiling under latex or pdflatex',
                 '\\ifx\\pdftexversion\\undefined',
                 '  \\usepackage{graphicx}',
                 '\\else',
                 '  \\usepackage[pdftex]{graphicx}',
                 '\\fi\n'))
        else:
            self.graphicx_package = (
                '\\usepackage[%s]{graphicx}\n' % self.settings.graphicx_option)

        self.head_prefix = [
              self.latex_head % (self.d_options,self.settings.documentclass),
              '\\usepackage{babel}\n',     # language is in documents settings.
              fontenc,
              '\\usepackage{shortvrb}\n',  # allows verb in footnotes.
              self.encoding % self.to_latex_encoding(settings.output_encoding),
              # * tabularx: for docinfo, automatic width of columns, always on one page.
              '\\usepackage{tabularx}\n',
              '\\usepackage{longtable}\n',
              self.active_table.used_packages(),
              # possible other packages.
              # * fancyhdr
              # * ltxtable is a combination of tabularx and longtable (pagebreaks).
              #   but ??
              #
              # extra space between text in tables and the line above them
              '\\setlength{\\extrarowheight}{2pt}\n',
              '\\usepackage{amsmath}\n',   # what fore amsmath.
              self.graphicx_package,
              '\\usepackage{color}\n',
              '\\usepackage{multirow}\n',
              '\\usepackage{ifthen}\n',   # before hyperref!
              self.linking % (self.colorlinks, self.hyperlink_color, self.hyperlink_color),
              self.typearea,
              self.generator,
              # latex lengths
              '\\newlength{\\admonitionwidth}\n',
              '\\setlength{\\admonitionwidth}{0.9\\textwidth}\n'
              # width for docinfo tablewidth
              '\\newlength{\\docinfowidth}\n',
              '\\setlength{\\docinfowidth}{0.9\\textwidth}\n'
              # linewidth of current environment, so tables are not wider
              # than the sidebar: using locallinewidth seems to defer evaluation
              # of linewidth, this is fixing it.
              '\\newlength{\\locallinewidth}\n',
              # will be set later.
              ]
        self.head_prefix.extend( latex_headings['optionlist_environment'] )
        self.head_prefix.extend( latex_headings['footnote_floats'] )
        self.head_prefix.extend( latex_headings['some_commands'] )
        ## stylesheet is last: so it might be possible to overwrite defaults.
        stylesheet = self.get_stylesheet_reference()
        if stylesheet:
            self.head_prefix.append(self.stylesheet % (stylesheet))

        if self.linking: # and maybe check for pdf
            self.pdfinfo = [ ]
            self.pdfauthor = None
            # pdftitle, pdfsubject, pdfauthor, pdfkeywords, pdfcreator, pdfproducer
        else:
            self.pdfinfo = None
        # NOTE: Latex wants a date and an author, rst puts this into
        #   docinfo, so normally we donot want latex author/date handling.
        # latex article has its own handling of date and author, deactivate.
        self.head = [ ]
        if not self.use_latex_docinfo:
            self.head.extend( [ '\\author{}\n', '\\date{}\n' ] )
        self.body_prefix = ['\\raggedbottom\n']
        # separate title, so we can appen subtitle.
        self.title = ""
        self.body = []
        self.body_suffix = ['\n']
        self.section_level = 0
        self.context = []
        self.topic_class = ''
        # column specification for tables
        self.table_caption = None
        # do we have one or more authors
        self.author_stack = None
        # Flags to encode
        # ---------------
        # verbatim: to tell encode not to encode.
        self.verbatim = 0
        # insert_newline: to tell encode to replace blanks by "~".
        self.insert_none_breaking_blanks = 0
        # insert_newline: to tell encode to add latex newline.
        self.insert_newline = 0
        # mbox_newline: to tell encode to add mbox and newline.
        self.mbox_newline = 0

        # enumeration is done by list environment.
        self._enum_cnt = 0

        # Stack of section counters so that we don't have to use_latex_toc.
        # This will grow and shrink as processing occurs.
        # Initialized for potential first-level sections.
        self._section_number = [0]

        # The current stack of enumerations so that we can expand
        # them into a compound enumeration
        self._enumeration_counters = []

        self._bibitems = []

        # docinfo.
        self.docinfo = None
        # inside literal block: no quote mangling.
        self.literal_block = 0
        self.literal_block_stack = []
        self.literal = 0
        # true when encoding in math mode
        self.mathmode = 0

    def get_stylesheet_reference(self):
        if self.settings.stylesheet_path:
            return self.settings.stylesheet_path
        else:
            return self.settings.stylesheet

    def to_latex_encoding(self,docutils_encoding):
        """
        Translate docutils encoding name into latex's.

        Default fallback method is remove "-" and "_" chars from docutils_encoding.

        """
        tr = {  "iso-8859-1": "latin1",     # west european
                "iso-8859-2": "latin2",     # east european
                "iso-8859-3": "latin3",     # esperanto, maltese
                "iso-8859-4": "latin4",     # north european,scandinavian, baltic
                "iso-8859-5": "iso88595",   # cyrillic (ISO)
                "iso-8859-9": "latin5",     # turkish
                "iso-8859-15": "latin9",    # latin9, update to latin1.
                "mac_cyrillic": "maccyr",   # cyrillic (on Mac)
                "windows-1251": "cp1251",   # cyrillic (on Windows)
                "koi8-r": "koi8-r",         # cyrillic (Russian)
                "koi8-u": "koi8-u",         # cyrillic (Ukrainian)
                "windows-1250": "cp1250",   #
                "windows-1252": "cp1252",   #
                "us-ascii": "ascii",        # ASCII (US)
                # unmatched encodings
                #"": "applemac",
                #"": "ansinew",  # windows 3.1 ansi
                #"": "ascii",    # ASCII encoding for the range 32--127.
                #"": "cp437",    # dos latine us
                #"": "cp850",    # dos latin 1
                #"": "cp852",    # dos latin 2
                #"": "decmulti",
                #"": "latin10",
                #"iso-8859-6": ""   # arabic
                #"iso-8859-7": ""   # greek
                #"iso-8859-8": ""   # hebrew
                #"iso-8859-10": ""   # latin6, more complete iso-8859-4
             }
        if tr.has_key(docutils_encoding.lower()):
            return tr[docutils_encoding.lower()]
        return docutils_encoding.translate(string.maketrans("",""),"_-").lower()

    def language_label(self, docutil_label):
        return self.language.labels[docutil_label]

    def utf8_to_latex(self,text):
        # see LaTeX codec http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124
        # Only some special chracters are translated, for documents with many utf-8
        # chars one should use the LaTeX unicode package.
        latex_equivalents = {
            u'\u00A0' : '~',
            u'\u00A9' : '{\\copyright}',
            u'\u2013' : '{--}',
            u'\u2014' : '{---}',
            u'\u2020' : '{\\dag}',
            u'\u2021' : '{\\ddag}',
            u'\u21d4' : '{$\\Leftrightarrow$}',
                }
        for uchar in latex_equivalents.keys():
            text = text.replace(uchar,latex_equivalents[uchar])
        return text

    def encode(self, text):
        """
        Encode special characters in `text` & return.
            # $ % & ~ _ ^ \ { }
        Escaping with a backslash does not help with backslashes, ~ and ^.

            < > are only available in math-mode or tt font. (really ?)
            $ starts math- mode.
        AND quotes:

        """
        if self.verbatim:
            return text
        # compile the regexps once. do it here so one can see them.
        #
        # first the braces.
        if not self.__dict__.has_key('encode_re_braces'):
            self.encode_re_braces = re.compile(r'([{}])')
        text = self.encode_re_braces.sub(r'{\\\1}',text)
        if not self.__dict__.has_key('encode_re_bslash'):
            # find backslash: except in the form '{\{}' or '{\}}'.
            self.encode_re_bslash = re.compile(r'(?<!{)(\\)(?![{}]})')
        # then the backslash: except in the form from line above:
        # either '{\{}' or '{\}}'.
        text = self.encode_re_bslash.sub(r'{\\textbackslash}', text)

        # then dollar
        text = text.replace("$", '{\\$}')
        if not ( self.literal_block or self.literal or self.mathmode ):
            # the vertical bar: in mathmode |,\vert or \mid
            #   in textmode \textbar
            text = text.replace("|", '{\\textbar}')
            text = text.replace("<", '{\\textless}')
            text = text.replace(">", '{\\textgreater}')
        # then
        text = text.replace("&", '{\\&}')
        # the ^:
        # * verb|^| does not work in mbox.
        # * mathmode has wedge. hat{~} would also work.
        # text = text.replace("^", '{\\ensuremath{^\\wedge}}')
        text = text.replace("^", '{\\textasciicircum}')
        text = text.replace("%", '{\\%}')
        text = text.replace("#", '{\\#}')
        text = text.replace("~", '{\\textasciitilde}')
        if self.literal_block or self.literal:
            # pdflatex does not produce doublequotes for ngerman.
            text = self.babel.double_quotes_in_tt(text)
            if self.fontenc == 'T1': 
                # make sure "--" does not become a "-".
                # the same for "<<" and ">>".
                text = text.replace("--","-{}-").replace("--","-{}-")
                text = text.replace(">>",">{}>").replace(">>",">{}>")
                text = text.replace("<<","<{}<").replace("<<","<{}<")
            # replace underline by underlined blank, because this has correct width.
            text = text.replace("_", '{\\underline{ }}')
        else:
            text = self.babel.quote_quotes(text)
            text = text.replace("_", '{\\_}')
        if self.insert_newline or self.literal_block:
            # Insert a blank before the newline, to avoid
            # ! LaTeX Error: There's no line here to end.
            text = text.replace("\n", '~\\\\\n')
        elif self.mbox_newline:
            if self.literal_block:
                closings = "}" * len(self.literal_block_stack)
                openings = "".join(self.literal_block_stack)
            else:
                closings = ""
                openings = ""
            text = text.replace("\n", "%s}\\\\\n\\mbox{%s" % (closings,openings))
        # lines starting with "[" give errors.
        text = text.replace('[', '{[}')
        if self.insert_none_breaking_blanks:
            text = text.replace(' ', '~')
        if self.settings.output_encoding != 'utf-8':
            text = self.utf8_to_latex(text)
        return text

    def attval(self, text,
               whitespace=re.compile('[\n\r\t\v\f]')):
        """Cleanse, encode, and return attribute value text."""
        return self.encode(whitespace.sub(' ', text))

    def astext(self):
        if self.pdfinfo:
            if self.pdfauthor:
                self.pdfinfo.append('pdfauthor={%s}' % self.pdfauthor)
            pdfinfo = '\\hypersetup{\n' + ',\n'.join(self.pdfinfo) + '\n}\n'
        else:
            pdfinfo = ''
        title = '\\title{%s}\n' % self.title
        return ''.join(self.head_prefix + [title]
                        + self.head + [pdfinfo]
                        + self.body_prefix  + self.body + self.body_suffix)

    def visit_Text(self, node):
        self.body.append(self.encode(node.astext()))

    def depart_Text(self, node):
        pass

    def visit_address(self, node):
        self.visit_docinfo_item(node, 'address')

    def depart_address(self, node):
        self.depart_docinfo_item(node)

    def visit_admonition(self, node, name=''):
        self.body.append('\\begin{center}\\begin{sffamily}\n')
        self.body.append('\\fbox{\\parbox{\\admonitionwidth}{\n')
        if name:
            self.body.append('\\textbf{\\large '+ self.language.labels[name] + '}\n');
        self.body.append('\\vspace{2mm}\n')


    def depart_admonition(self, node=None):
        self.body.append('}}\n') # end parbox fbox
        self.body.append('\\end{sffamily}\n\\end{center}\n');

    def visit_attention(self, node):
        self.visit_admonition(node, 'attention')

    def depart_attention(self, node):
        self.depart_admonition()

    def visit_author(self, node):
        self.visit_docinfo_item(node, 'author')

    def depart_author(self, node):
        self.depart_docinfo_item(node)

    def visit_authors(self, node):
        # not used: visit_author is called anyway for each author.
        if self.use_latex_docinfo:
            self.author_stack = []

    def depart_authors(self, node):
        if self.use_latex_docinfo:
            self.head.append('\\author{%s}\n' % \
                ' \\and '.join(self.author_stack) )
            self.author_stack = None

    def visit_block_quote(self, node):
        self.body.append( '\\begin{quote}\n')

    def depart_block_quote(self, node):
        self.body.append( '\\end{quote}\n')

    def visit_bullet_list(self, node):
        if self.topic_class == 'contents':
            if not self.use_latex_toc:
                self.body.append( '\\begin{list}{}{}\n' )
        else:
            self.body.append( '\\begin{itemize}\n' )

    def depart_bullet_list(self, node):
        if self.topic_class == 'contents':
            if not self.use_latex_toc:
                self.body.append( '\\end{list}\n' )
        else:
            self.body.append( '\\end{itemize}\n' )

    # Imperfect superscript/subscript handling: mathmode italicizes
    # all letters by default.
    def visit_superscript(self, node):
        self.body.append('$^{')
        self.mathmode = 1

    def depart_superscript(self, node):
        self.body.append('}$')
        self.mathmode = 0

    def visit_subscript(self, node):
        self.body.append('$_{')
        self.mathmode = 1

    def depart_subscript(self, node):
        self.body.append('}$')
        self.mathmode = 0

    def visit_caption(self, node):
        self.body.append( '\\caption{' )

    def depart_caption(self, node):
        self.body.append('}')

    def visit_caution(self, node):
        self.visit_admonition(node, 'caution')

    def depart_caution(self, node):
        self.depart_admonition()

    def visit_title_reference(self, node):
        self.body.append( '\\titlereference{' )

    def depart_title_reference(self, node):
        self.body.append( '}' )

    def visit_citation(self, node):
        # TODO maybe use cite bibitems
        if self._use_latex_citations:
            self.context.append(len(self.body))
        else:
            self.body.append('\\begin{figure}[b]')
            self.body.append('\\hypertarget{%s}' % node['id'])

    def depart_citation(self, node):
        if self._use_latex_citations:
            size = self.context.pop()
            label = self.body[size]
            text = ''.join(self.body[size+1:])
            del self.body[size:]
            self._bibitems.append([label, text])
        else:
            self.body.append('\\end{figure}\n')

    def visit_citation_reference(self, node):
        if self._use_latex_citations:
            self.body.append('\\cite{')
        else:
            href = ''
            if node.has_key('refid'):
                href = node['refid']
            elif node.has_key('refname'):
                href = self.document.nameids[node['refname']]
            self.body.append('[\\hyperlink{%s}{' % href)

    def depart_citation_reference(self, node):
        if self._use_latex_citations:
            self.body.append('}')
        else:
            self.body.append('}]')

    def visit_classifier(self, node):
        self.body.append( '(\\textbf{' )

    def depart_classifier(self, node):
        self.body.append( '})\n' )

    def visit_colspec(self, node):
        self.active_table.visit_colspec(node)

    def depart_colspec(self, node):
        pass

    def visit_comment(self, node,
                      sub=re.compile('\n').sub):
        """Escape end of line by a ne comment start in comment text."""
        self.body.append('%% %s \n' % sub('\n% ', node.astext()))
        raise nodes.SkipNode

    def visit_contact(self, node):
        self.visit_docinfo_item(node, 'contact')

    def depart_contact(self, node):
        self.depart_docinfo_item(node)

    def visit_copyright(self, node):
        self.visit_docinfo_item(node, 'copyright')

    def depart_copyright(self, node):
        self.depart_docinfo_item(node)

    def visit_danger(self, node):
        self.visit_admonition(node, 'danger')

    def depart_danger(self, node):
        self.depart_admonition()

    def visit_date(self, node):
        self.visit_docinfo_item(node, 'date')

    def depart_date(self, node):
        self.depart_docinfo_item(node)

    def visit_decoration(self, node):
        pass

    def depart_decoration(self, node):
        pass

    def visit_definition(self, node):
        self.body.append('%[visit_definition]\n')

    def depart_definition(self, node):
        self.body.append('\n')
        self.body.append('%[depart_definition]\n')

    def visit_definition_list(self, node):
        self.body.append( '\\begin{description}\n' )

    def depart_definition_list(self, node):
        self.body.append( '\\end{description}\n' )

    def visit_definition_list_item(self, node):
        self.body.append('%[visit_definition_list_item]\n')

    def depart_definition_list_item(self, node):
        self.body.append('%[depart_definition_list_item]\n')

    def visit_description(self, node):
        if self.use_optionlist_for_option_list:
            self.body.append( ' ' )
        else:
            self.body.append( ' & ' )

    def depart_description(self, node):
        pass

    def visit_docinfo(self, node):
        self.docinfo = []
        self.docinfo.append('%' + '_'*75 + '\n')
        self.docinfo.append('\\begin{center}\n')
        self.docinfo.append('\\begin{tabularx}{\\docinfowidth}{lX}\n')

    def depart_docinfo(self, node):
        self.docinfo.append('\\end{tabularx}\n')
        self.docinfo.append('\\end{center}\n')
        self.body = self.docinfo + self.body
        # clear docinfo, so field names are no longer appended.
        self.docinfo = None

    def visit_docinfo_item(self, node, name):
        if name == 'author':
            if not self.pdfinfo == None:
                if not self.pdfauthor:
                    self.pdfauthor = self.attval(node.astext())
                else:
                    self.pdfauthor += self.author_separator + self.attval(node.astext())
            if self.use_latex_docinfo:
                if self.author_stack == None:
                    self.head.append('\\author{%s}\n' % self.attval(node.astext()))
                else:
                    self.author_stack.append( self.attval(node.astext()) )
                raise nodes.SkipNode
        elif name == 'date':
            if self.use_latex_docinfo:
                self.head.append('\\date{%s}\n' % self.attval(node.astext()))
                raise nodes.SkipNode
        self.docinfo.append('\\textbf{%s}: &\n\t' % self.language_label(name))
        if name == 'address':
            self.insert_newline = 1
            self.docinfo.append('{\\raggedright\n')
            self.context.append(' } \\\\\n')
        else:
            self.context.append(' \\\\\n')
        self.context.append(self.docinfo)
        self.context.append(len(self.body))

    def depart_docinfo_item(self, node):
        size = self.context.pop()
        dest = self.context.pop()
        tail = self.context.pop()
        tail = self.body[size:] + [tail]
        del self.body[size:]
        dest.extend(tail)
        # for address we did set insert_newline
        self.insert_newline = 0

    def visit_doctest_block(self, node):
        self.body.append( '\\begin{verbatim}' )
        self.verbatim = 1

    def depart_doctest_block(self, node):
        self.body.append( '\\end{verbatim}\n' )
        self.verbatim = 0

    def visit_document(self, node):
        self.body_prefix.append('\\begin{document}\n')
        # titled document?
        if isinstance(node[0], nodes.title):
            self.body_prefix.append('\\maketitle\n\n')
            # alternative use titlepage environment.
            # \begin{titlepage}
        self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')

    def depart_document(self, node):
        # TODO insertion point of bibliography should none automatic.
        if self._use_latex_citations and len(self._bibitems)>0:
            widest_label = ""
            for bi in self._bibitems:
                if len(widest_label)<len(bi[0]):
                    widest_label = bi[0]
            self.body.append('\n\\begin{thebibliography}{%s}\n'%widest_label)
            for bi in self._bibitems:
                self.body.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], bi[0], bi[1]))
            self.body.append('\\end{thebibliography}\n')
            
        self.body_suffix.append('\\end{document}\n')

    def visit_emphasis(self, node):
        self.body.append('\\emph{')
        self.literal_block_stack.append('\\emph{')

    def depart_emphasis(self, node):
        self.body.append('}')
        self.literal_block_stack.pop()

    def visit_entry(self, node):
        self.active_table.visit_entry()
        # cell separation
        if self.active_table.get_entry_number() == 1:
            # if the firstrow is a multirow, this actually is the second row.
            # this gets hairy if rowspans follow each other.
            if self.active_table.get_rowspan(0):
                self.body.append(' & ')
                self.active_table.visit_entry() # increment cell count
        else:
            self.body.append(' & ')

        # multi{row,column}
        # IN WORK BUG TODO HACK continues here
        # multirow in LaTeX simply will enlarge the cell over several rows
        # (the following n if n is positive, the former if negative).
        if node.has_key('morerows') and node.has_key('morecols'):
            raise NotImplementedError('Cells that '
            'span multiple rows *and* columns are not supported, sorry.')
        if node.has_key('morerows'):
            count = node['morerows'] + 1
            self.active_table.set_rowspan(self.active_table.get_entry_number()-1,count)
            self.body.append('\\multirow{%d}{%s}{' % \
                    (count,self.active_table.get_column_width()))
            self.context.append('}')
            # BUG following rows must have empty cells.
        elif node.has_key('morecols'):
            # the vertical bar before column is missing if it is the first column.
            # the one after always.
            if self.active_table.get_entry_number() == 1:
                bar1 = self.active_table.get_vertical_bar()
            else:
                bar1 = ''
            count = node['morecols'] + 1
            self.body.append('\\multicolumn{%d}{%sl%s}{' % \
                    (count, bar1, self.active_table.get_vertical_bar()))
            self.context.append('}')
        else:
            self.context.append('')

        # header / not header
        if isinstance(node.parent.parent, nodes.thead):
            self.body.append('\\textbf{')
            self.context.append('}')
        else:
            self.context.append('')

    def depart_entry(self, node):
        self.body.append(self.context.pop()) # header / not header
        self.body.append(self.context.pop()) # multirow/column
        # if following row is spanned from above.
        if self.active_table.get_rowspan(self.active_table.get_entry_number()):
           self.body.append(' & ')
           self.active_table.visit_entry() # increment cell count

    def visit_row(self, node):
        self.active_table.visit_row()

    def depart_row(self, node):
        self.body.extend(self.active_table.depart_row())

    def visit_enumerated_list(self, node):
        # We create our own enumeration list environment.
        # This allows to set the style and starting value
        # and unlimited nesting.
        self._enum_cnt += 1

        enum_style = {'arabic':'arabic',
                'loweralpha':'alph',
                'upperalpha':'Alph',
                'lowerroman':'roman',
                'upperroman':'Roman' }
        enum_suffix = ""
        if node.has_key('suffix'):
            enum_suffix = node['suffix']
        enum_prefix = ""
        if node.has_key('prefix'):
            enum_prefix = node['prefix']
        if self.compound_enumerators:
            pref = ""
            if self.section_prefix_for_enumerators and self.section_level:
                for i in range(self.section_level):
                    pref += '%d.' % self._section_number[i]
                pref = pref[:-1] + self.section_enumerator_separator
                enum_prefix += pref
            for counter in self._enumeration_counters:
                enum_prefix += counter + '.'
        enum_type = "arabic"
        if node.has_key('enumtype'):
            enum_type = node['enumtype']
        if enum_style.has_key(enum_type):
            enum_type = enum_style[enum_type]
        counter_name = "listcnt%d" % self._enum_cnt;
        self._enumeration_counters.append("\\%s{%s}" % (enum_type,counter_name))
        self.body.append('\\newcounter{%s}\n' % counter_name)
        self.body.append('\\begin{list}{%s\\%s{%s}%s}\n' % \
            (enum_prefix,enum_type,counter_name,enum_suffix))
        self.body.append('{\n')
        self.body.append('\\usecounter{%s}\n' % counter_name)
        # set start after usecounter, because it initializes to zero.
        if node.has_key('start'):
            self.body.append('\\addtocounter{%s}{%d}\n' \
                    % (counter_name,node['start']-1))
        ## set rightmargin equal to leftmargin
        self.body.append('\\setlength{\\rightmargin}{\\leftmargin}\n')
        self.body.append('}\n')

    def depart_enumerated_list(self, node):
        self.body.append('\\end{list}\n')
        self._enumeration_counters.pop()

    def visit_error(self, node):
        self.visit_admonition(node, 'error')

    def depart_error(self, node):
        self.depart_admonition()

    def visit_field(self, node):
        # real output is done in siblings: _argument, _body, _name
        pass

    def depart_field(self, node):
        self.body.append('\n')
        ##self.body.append('%[depart_field]\n')

    def visit_field_argument(self, node):
        self.body.append('%[visit_field_argument]\n')

    def depart_field_argument(self, node):
        self.body.append('%[depart_field_argument]\n')

    def visit_field_body(self, node):
        # BUG by attach as text we loose references.
        if self.docinfo:
            self.docinfo.append('%s \\\\\n' % node.astext())
            raise nodes.SkipNode
        # BUG: what happens if not docinfo

    def depart_field_body(self, node):
        self.body.append( '\n' )

    def visit_field_list(self, node):
        if not self.docinfo:
            self.body.append('\\begin{quote}\n')
            self.body.append('\\begin{description}\n')

    def depart_field_list(self, node):
        if not self.docinfo:
            self.body.append('\\end{description}\n')
            self.body.append('\\end{quote}\n')

    def visit_field_name(self, node):
        # BUG this duplicates docinfo_item
        if self.docinfo:
            self.docinfo.append('\\textbf{%s}: &\n\t' % node.astext())
            raise nodes.SkipNode
        else:
            self.body.append('\\item [')

    def depart_field_name(self, node):
        if not self.docinfo:
            self.body.append(':]')

    def visit_figure(self, node):
        self.body.append( '\\begin{figure}[htbp]\\begin{center}\n' )

    def depart_figure(self, node):
        self.body.append( '\\end{center}\\end{figure}\n' )

    def visit_footer(self, node):
        self.context.append(len(self.body))

    def depart_footer(self, node):
        start = self.context.pop()
        footer = (['\n\\begin{center}\small\n']
                  + self.body[start:] + ['\n\\end{center}\n'])
        self.body_suffix[:0] = footer
        del self.body[start:]

    def visit_footnote(self, node):
        if self.use_latex_footnotes:
            num,text = node.astext().split(None,1)
            num = self.encode(num.strip())
            self.body.append('\\footnotetext['+num+']')
            self.body.append('{')
        else:
            self.body.append('\\begin{figure}[b]')
            self.body.append('\\hypertarget{%s}' % node['id'])

    def depart_footnote(self, node):
        if self.use_latex_footnotes:
            self.body.append('}\n')
        else:
            self.body.append('\\end{figure}\n')

    def visit_footnote_reference(self, node):
        if self.use_latex_footnotes:
            self.body.append("\\footnotemark["+self.encode(node.astext())+"]")
            raise nodes.SkipNode
        href = ''
        if node.has_key('refid'):
            href = node['refid']
        elif node.has_key('refname'):
            href = self.document.nameids[node['refname']]
        format = self.settings.footnote_references
        if format == 'brackets':
            suffix = '['
            self.context.append(']')
        elif format == 'superscript':
            suffix = '\\raisebox{.5em}[0em]{\\scriptsize'
            self.context.append('}')
        else:                           # shouldn't happen
            raise AssertionError('Illegal footnote reference format.')
        self.body.append('%s\\hyperlink{%s}{' % (suffix,href))

    def depart_footnote_reference(self, node):
        if self.use_latex_footnotes:
            return
        self.body.append('}%s' % self.context.pop())

    # footnote/citation label
    def visit_label(self, node):
        if isinstance(node.parent, nodes.footnote) and self.use_latex_footnotes:
            raise nodes.SkipNode
        elif isinstance(node.parent, nodes.citation) and self._use_latex_citations:
            pass
        else:
            self.body.append('[')

    def depart_label(self, node):
        if isinstance(node.parent, nodes.citation) and self._use_latex_citations:
            return
        self.body.append(']')

    # elements generated by the framework e.g. section numbers.
    def visit_generated(self, node):
        pass

    def depart_generated(self, node):
        pass

    def visit_header(self, node):
        self.context.append(len(self.body))

    def depart_header(self, node):
        start = self.context.pop()
        self.body_prefix.append('\n\\verb|begin_header|\n')
        self.body_prefix.extend(self.body[start:])
        self.body_prefix.append('\n\\verb|end_header|\n')
        del self.body[start:]

    def visit_hint(self, node):
        self.visit_admonition(node, 'hint')

    def depart_hint(self, node):
        self.depart_admonition()

    def visit_image(self, node):
        attrs = node.attributes
        pre = []                        # in reverse order
        post = ['\\includegraphics{%s}' % attrs['uri']]
        inline = isinstance(node.parent, nodes.TextElement)
        if attrs.has_key('scale'):
            # Could also be done with ``scale`` option to
            # ``\includegraphics``; doing it this way for consistency.
            pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
            post.append('}')
        if attrs.has_key('align'):
            align_prepost = {
                # By default latex aligns the top of an image.
                (1, 'top'): ('', ''),
                (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
                (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
                (0, 'center'): ('{\\hfill', '\\hfill}'),
                # These 2 don't exactly do the right thing.  The image should
                # be floated alongside the paragraph.  See
                # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
                (0, 'left'): ('{', '\\hfill}'),
                (0, 'right'): ('{\\hfill', '}'),}
            try:
                pre.append(align_prepost[inline, attrs['align']][0])
                post.append(align_prepost[inline, attrs['align']][1])
            except KeyError:
                pass                    # XXX complain here?
        if not inline:
            pre.append('\n')
            post.append('\n')
        pre.reverse()
        self.body.extend(pre + post)

    def depart_image(self, node):
        pass

    def visit_important(self, node):
        self.visit_admonition(node, 'important')

    def depart_important(self, node):
        self.depart_admonition()

    def visit_interpreted(self, node):
        # @@@ Incomplete, pending a proper implementation on the
        # Parser/Reader end.
        self.visit_literal(node)

    def depart_interpreted(self, node):
        self.depart_literal(node)

    def visit_legend(self, node):
        self.body.append('{\\small ')

    def depart_legend(self, node):
        self.body.append('}')

    def visit_line_block(self, node):
        """line-block:
        * whitespace (including linebreaks) is significant
        * inline markup is supported.
        * serif typeface

        """
        self.body.append('\\begin{flushleft}\n')
        self.insert_none_breaking_blanks = 1
        # mbox would stop LaTeX from wrapping long lines.
        # but line_blocks are allowed to wrap.
        self.line_block_without_mbox = 1
        if self.line_block_without_mbox:
            self.insert_newline = 1
        else:
            self.mbox_newline = 1
            self.body.append('\\mbox{')

    def depart_line_block(self, node):
        if self.line_block_without_mbox:
            self.insert_newline = 0
        else:
            self.body.append('}')
            self.mbox_newline = 0
        self.insert_none_breaking_blanks = 0
        self.body.append('\n\\end{flushleft}\n')

    def visit_list_item(self, node):
        # Append "{}" in case the next character is "[", which would break
        # LaTeX's list environment (no numbering and the "[" is not printed).
        self.body.append('\\item {} ')

    def depart_list_item(self, node):
        self.body.append('\n')

    def visit_literal(self, node):
        self.literal = 1
        self.body.append('\\texttt{')

    def depart_literal(self, node):
        self.body.append('}')
        self.literal = 0

    def visit_literal_block(self, node):
        """
        Render a literal-block.

        Literal blocks are used for "::"-prefixed literal-indented
        blocks of text, where the inline markup is not recognized,
        but are also the product of the parsed-literal directive,
        where the markup is respected.
        """
        # In both cases, we want to use a typewriter/monospaced typeface.
        # For "real" literal-blocks, we can use \verbatim, while for all
        # the others we must use \mbox.
        #
        # We can distinguish between the two kinds by the number of
        # siblings the compose this node: if it is composed by a
        # single element, it's surely is either a real one, otherwise
        # it's a parsed-literal that does not contain any markup.
        #
        if (self.settings.use_verbatim_when_possible and (len(node) == 1)
              # in case of a parsed-literal containing just a "**bold**" word:
              and isinstance(node[0], nodes.Text)):
            self.verbatim = 1
            self.body.append('\\begin{quote}\\begin{verbatim}\n')
        else:
            self.literal_block = 1
            self.insert_none_breaking_blanks = 1
            if self.active_table.is_open():
                self.body.append('\n{\\ttfamily \\raggedright \\noindent\n')
            else:
                # no quote inside tables, to avoid vertical sppace between
                # table border and literal block.
                # BUG: fails if normal text preceeds the literal block.
                self.body.append('\\begin{quote}')
                self.body.append('{\\ttfamily \\raggedright \\noindent\n')
            # * obey..: is from julien and never worked for me (grubert).
            #   self.body.append('{\\obeylines\\obeyspaces\\ttfamily\n')

    def depart_literal_block(self, node):
        if self.verbatim:
            self.body.append('\n\\end{verbatim}\\end{quote}\n')
            self.verbatim = 0
        else:
            if self.active_table.is_open():
                self.body.append('\n}\n')
            else:
                self.body.append('\n')
                self.body.append('}\\end{quote}\n')
            self.insert_none_breaking_blanks = 0
            self.literal_block = 0
            # obey end: self.body.append('}\n')

    def visit_meta(self, node):
        self.body.append('[visit_meta]\n')
        # BUG maybe set keywords for pdf
        ##self.head.append(self.starttag(node, 'meta', **node.attributes))

    def depart_meta(self, node):
        self.body.append('[depart_meta]\n')

    def visit_note(self, node):
        self.visit_admonition(node, 'note')

    def depart_note(self, node):
        self.depart_admonition()

    def visit_option(self, node):
        if self.context[-1]:
            # this is not the first option
            self.body.append(', ')

    def depart_option(self, node):
        # flag tha the first option is done.
        self.context[-1] += 1

    def visit_option_argument(self, node):
        """The delimiter betweeen an option and its argument."""
        self.body.append(node.get('delimiter', ' '))

    def depart_option_argument(self, node):
        pass

    def visit_option_group(self, node):
        if self.use_optionlist_for_option_list:
            self.body.append('\\item [')
        else:
            if len(node.astext()) > 14:
                self.body.append('\\multicolumn{2}{l}{')
                self.context.append('} \\\\\n  ')
            else:
                self.context.append('')
            self.body.append('\\texttt{')
        # flag for first option
        self.context.append(0)

    def depart_option_group(self, node):
        self.context.pop() # the flag
        if self.use_optionlist_for_option_list:
            self.body.append('] ')
        else:
            self.body.append('}')
            self.body.append(self.context.pop())

    def visit_option_list(self, node):
        self.body.append('% [option list]\n')
        if self.use_optionlist_for_option_list:
            self.body.append('\\begin{optionlist}{3cm}\n')
        else:
            self.body.append('\\begin{center}\n')
            # BUG: use admwidth or make it relative to textwidth ?
            self.body.append('\\begin{tabularx}{.9\\linewidth}{lX}\n')

    def depart_option_list(self, node):
        if self.use_optionlist_for_option_list:
            self.body.append('\\end{optionlist}\n')
        else:
            self.body.append('\\end{tabularx}\n')
            self.body.append('\\end{center}\n')

    def visit_option_list_item(self, node):
        pass

    def depart_option_list_item(self, node):
        if not self.use_optionlist_for_option_list:
            self.body.append('\\\\\n')

    def visit_option_string(self, node):
        ##self.body.append(self.starttag(node, 'span', '', CLASS='option'))
        pass

    def depart_option_string(self, node):
        ##self.body.append('</span>')
        pass

    def visit_organization(self, node):
        self.visit_docinfo_item(node, 'organization')

    def depart_organization(self, node):
        self.depart_docinfo_item(node)

    def visit_paragraph(self, node):
        if not self.topic_class == 'contents':
            self.body.append('\n')

    def depart_paragraph(self, node):
        self.body.append('\n')

    def visit_problematic(self, node):
        self.body.append('{\\color{red}\\bfseries{}')

    def depart_problematic(self, node):
        self.body.append('}')

    def visit_raw(self, node):
        if node.has_key('format') and node['format'].lower() == 'latex':
            self.body.append(node.astext())
        raise nodes.SkipNode

    def visit_reference(self, node):
        # BUG: hash_char "#" is trouble some in LaTeX.
        # mbox and other environment do not like the '#'.
        hash_char = '\\#'
        if node.has_key('refuri'):
            href = node['refuri'].replace('#',hash_char)
        elif node.has_key('refid'):
            href = hash_char + node['refid']
        elif node.has_key('refname'):
            href = hash_char + self.document.nameids[node['refname']]
        else:
            raise AssertionError('Unknown reference.')
        self.body.append('\\href{%s}{' % href)

    def depart_reference(self, node):
        self.body.append('}')

    def visit_revision(self, node):
        self.visit_docinfo_item(node, 'revision')

    def depart_revision(self, node):
        self.depart_docinfo_item(node)

    def visit_section(self, node):
        self.section_level += 1
        # Initialize counter for potential subsections:
        self._section_number.append(0)
        # Counter for this section's level (initialized by parent section):
        self._section_number[self.section_level - 1] += 1

    def depart_section(self, node):
        # Remove counter for potential subsections:
        self._section_number.pop()
        self.section_level -= 1

    def visit_sidebar(self, node):
        # BUG:  this is just a hack to make sidebars render something
        self.body.append('\n\\setlength{\\locallinewidth}{0.9\\admonitionwidth}\n')
        self.body.append('\\begin{center}\\begin{sffamily}\n')
        self.body.append('\\fbox{\\colorbox[gray]{0.80}{\\parbox{\\admonitionwidth}{\n')

    def depart_sidebar(self, node):
        self.body.append('}}}\n') # end parbox colorbox fbox
        self.body.append('\\end{sffamily}\n\\end{center}\n');
        self.body.append('\n\\setlength{\\locallinewidth}{\\linewidth}\n')


    attribution_formats = {'dash': ('---', ''),
                           'parentheses': ('(', ')'),
                           'parens': ('(', ')'),
                           'none': ('', '')}

    def visit_attribution(self, node):
        prefix, suffix = self.attribution_formats[self.settings.attribution]
        self.body.append('\n\\begin{flushright}\n')
        self.body.append(prefix)
        self.context.append(suffix)

    def depart_attribution(self, node):
        self.body.append(self.context.pop() + '\n')
        self.body.append('\\end{flushright}\n')

    def visit_status(self, node):
        self.visit_docinfo_item(node, 'status')

    def depart_status(self, node):
        self.depart_docinfo_item(node)

    def visit_strong(self, node):
        self.body.append('\\textbf{')
        self.literal_block_stack.append('\\textbf{')

    def depart_strong(self, node):
        self.body.append('}')
        self.literal_block_stack.pop()

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

    def visit_substitution_reference(self, node):
        self.unimplemented_visit(node)

    def visit_subtitle(self, node):
        if isinstance(node.parent, nodes.sidebar):
            self.body.append('~\\\\\n\\textbf{')
            self.context.append('}\n\\smallskip\n')
        else:
            self.title = self.title + \
                '\\\\\n\\large{%s}\n' % self.encode(node.astext())
            raise nodes.SkipNode

    def depart_subtitle(self, node):
        if isinstance(node.parent, nodes.sidebar):
            self.body.append(self.context.pop())

    def visit_system_message(self, node):
        if node['level'] < self.document.reporter['writer'].report_level:
            raise nodes.SkipNode

    def depart_system_message(self, node):
        self.body.append('\n')

    def visit_table(self, node):
        if self.active_table.is_open():
            print 'nested tables are not supported'
            raise AssertionError
        self.active_table.open()
        self.body.append('\n' + self.active_table.get_opening())

    def depart_table(self, node):
        self.body.append(self.active_table.get_closing() + '\n')
        self.active_table.close()

    def visit_target(self, node):
        # BUG: why not (refuri or refid or refname) means not footnote ?
        if not (node.has_key('refuri') or node.has_key('refid')
                or node.has_key('refname')):
            self.body.append('\\hypertarget{%s}{' % node['id'])
            self.context.append('}')
        else:
            self.context.append('')

    def depart_target(self, node):
        self.body.append(self.context.pop())

    def visit_tbody(self, node):
        # BUG write preamble if not yet done (colspecs not [])
        # for tables without heads.
        if not self.active_table.get('preamble written'):
            self.visit_thead(None)
            # self.depart_thead(None)

    def depart_tbody(self, node):
        pass

    def visit_term(self, node):
        self.body.append('\\item[')

    def depart_term(self, node):
        # definition list term.
        self.body.append(']\n')

    def visit_tgroup(self, node):
        #self.body.append(self.starttag(node, 'colgroup'))
        #self.context.append('</colgroup>\n')
        pass

    def depart_tgroup(self, node):
        pass

    def visit_thead(self, node):
        self.body.append('{%s}\n' % self.active_table.get_colspecs())
        if self.active_table.caption:
            self.body.append('\\caption{%s}\\\\\n' % self.active_table.caption)
        self.active_table.set('preamble written',1)
        # TODO longtable supports firsthead and lastfoot too.
        self.body.extend(self.active_table.visit_thead())

    def depart_thead(self, node):
        # the table header written should be on every page
        # => \endhead
        self.body.extend(self.active_table.depart_thead())
        # and the firsthead => \endfirsthead
        # BUG i want a "continued from previous page" on every not
        # firsthead, but then we need the header twice.
        #
        # there is a \endfoot and \endlastfoot too.
        # but we need the number of columns to
        # self.body.append('\\multicolumn{%d}{c}{"..."}\n' % number_of_columns)
        # self.body.append('\\hline\n\\endfoot\n')
        # self.body.append('\\hline\n')
        # self.body.append('\\endlastfoot\n')

    def visit_tip(self, node):
        self.visit_admonition(node, 'tip')

    def depart_tip(self, node):
        self.depart_admonition()

    def bookmark(self, node):
        """Append latex href and pdfbookmarks for titles.
        """
        if node.parent.hasattr('id'):
            self.body.append('\\hypertarget{%s}{}\n' % node.parent['id'])
            if not self.use_latex_toc:
                # BUG level depends on style. pdflatex allows level 0 to 3
                # ToC would be the only on level 0 so i choose to decrement the rest.
                # "Table of contents" bookmark to see the ToC. To avoid this
                # we set all zeroes to one.
                l = self.section_level
                if l>0:
                    l = l-1
                # pdftex does not like "_" subscripts in titles
                text = self.encode(node.astext())
                self.body.append('\\pdfbookmark[%d]{%s}{%s}\n' % \
                        (l,text,node.parent['id']))

    def visit_title(self, node):
        """Only 3 section levels are supported by LaTeX article (AFAIR)."""

        if isinstance(node.parent, nodes.topic):
            # section titles before the table of contents.
            self.bookmark(node)
            # BUG: latex chokes on center environment with "perhaps a missing item".
            # so we use hfill.
            self.body.append('\\subsection*{~\\hfill ')
            # the closing brace for subsection.
            self.context.append('\\hfill ~}\n')
        # TODO: for admonition titles before the first section
        # either specify every possible node or ... ?
        elif isinstance(node.parent, nodes.sidebar) \
        or isinstance(node.parent, nodes.admonition):
            self.body.append('\\textbf{\\large ')
            self.context.append('}\n\\smallskip\n')
        elif isinstance(node.parent, nodes.table):
            # caption must be written after column spec
            self.active_table.caption = node.astext()
            raise nodes.SkipNode
        elif self.section_level == 0:
            # document title
            self.title = self.encode(node.astext())
            if not self.pdfinfo == None:
                self.pdfinfo.append( 'pdftitle={%s}' % self.encode(node.astext()) )
            raise nodes.SkipNode
        else:
            self.body.append('\n\n')
            self.body.append('%' + '_' * 75)
            self.body.append('\n\n')
            self.bookmark(node)

            if self.use_latex_toc:
                section_star = ""
            else:
                section_star = "*"

            section_name = self.d_class.section(self.section_level)
            self.body.append('\\%s%s{' % (section_name, section_star))

            self.context.append('}\n')

    def depart_title(self, node):
        self.body.append(self.context.pop())

    def visit_topic(self, node):
        self.topic_class = node.get('class')
        if self.use_latex_toc:
            self.body.append('\\tableofcontents\n\n\\bigskip\n')
            self.topic_class = ''
            raise nodes.SkipNode

    def depart_topic(self, node):
        self.topic_class = ''
        self.body.append('\n')

    def visit_rubric(self, node):
        self.body.append('\\rubric{')
        self.context.append('}\n')

    def depart_rubric(self, node):
        self.body.append(self.context.pop())

    def visit_transition(self, node):
        self.body.append('\n\n')
        self.body.append('%' + '_' * 75)
        self.body.append('\n\\hspace*{\\fill}\\hrulefill\\hspace*{\\fill}')
        self.body.append('\n\n')

    def depart_transition(self, node):
        pass

    def visit_version(self, node):
        self.visit_docinfo_item(node, 'version')

    def depart_version(self, node):
        self.depart_docinfo_item(node)

    def visit_warning(self, node):
        self.visit_admonition(node, 'warning')

    def depart_warning(self, node):
        self.depart_admonition()

    def unimplemented_visit(self, node):
        raise NotImplementedError('visiting unimplemented node type: %s'
                                  % node.__class__.__name__)

#    def unknown_visit(self, node):
#    def default_visit(self, node):

# vim: set ts=4 et ai :


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/pep_html.py ===
# Author: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/10/29 18:24:54 $
# Copyright: This module has been placed in the public domain.

"""
PEP HTML Writer.
"""

__docformat__ = 'reStructuredText'


import sys
import docutils
from docutils import frontend, nodes, utils
from docutils.writers import html4css1


class Writer(html4css1.Writer):

    settings_spec = html4css1.Writer.settings_spec + (
        'PEP/HTML-Specific Options',
        """The HTML --footnote-references option's default is set to """
        '"brackets".',
        (('Specify a template file.  Default is "pep-html-template".',
          ['--template'],
          {'default': 'pep-html-template', 'metavar': '<file>'}),
         ('Python\'s home URL.  Default is ".." (parent directory).',
          ['--python-home'],
          {'default': '..', 'metavar': '<URL>'}),
         ('Home URL prefix for PEPs.  Default is "." (current directory).',
          ['--pep-home'],
          {'default': '.', 'metavar': '<URL>'}),
         # Workaround for SourceForge's broken Python
         # (``import random`` causes a segfault).
         (frontend.SUPPRESS_HELP,
          ['--no-random'],
          {'action': 'store_true', 'validator': frontend.validate_boolean}),))

    settings_default_overrides = {'footnote_references': 'brackets'}

    relative_path_settings = (html4css1.Writer.relative_path_settings
                              + ('template',))

    config_section = 'pep_html writer'
    config_section_dependencies = ('writers', 'html4css1 writer')

    def __init__(self):
        html4css1.Writer.__init__(self)
        self.translator_class = HTMLTranslator

    def translate(self):
        html4css1.Writer.translate(self)
        settings = self.document.settings
        template = open(settings.template).read()
        # Substitutions dict for template:
        subs = {}
        subs['encoding'] = settings.output_encoding
        subs['version'] = docutils.__version__
        subs['stylesheet'] = ''.join(self.stylesheet)
        pyhome = settings.python_home
        subs['pyhome'] = pyhome
        subs['pephome'] = settings.pep_home
        if pyhome == '..':
            subs['pepindex'] = '.'
        else:
            subs['pepindex'] = pyhome + '/peps/'
        index = self.document.first_child_matching_class(nodes.field_list)
        header = self.document[index]
        pepnum = header[0][1].astext()
        subs['pep'] = pepnum
        if settings.no_random:
            subs['banner'] = 0
        else:
            import random
            subs['banner'] = random.randrange(64)
        try:
            subs['pepnum'] = '%04i' % int(pepnum)
        except:
            subs['pepnum'] = pepnum
        subs['title'] = header[1][1].astext()
        subs['body'] = ''.join(
            self.body_pre_docinfo + self.docinfo + self.body)
        subs['body_suffix'] = ''.join(self.body_suffix)
        self.output = template % subs


class HTMLTranslator(html4css1.HTMLTranslator):

    def depart_field_list(self, node):
        html4css1.HTMLTranslator.depart_field_list(self, node)
        if node.get('class') == 'rfc2822':
             self.body.append('<hr />\n')


=== Added File Zope/lib/python/third_party/docutils/docutils/writers/pseudoxml.py ===
# Authors: David Goodger
# Contact: goodger at users.sourceforge.net
# Revision: $Revision: 1.1.2.1 $
# Date: $Date: 2004/10/29 18:24:54 $
# Copyright: This module has been placed in the public domain.

"""
Simple internal document tree Writer, writes indented pseudo-XML.
"""

__docformat__ = 'reStructuredText'


from docutils import writers


class Writer(writers.Writer):

    supported = ('pprint', 'pformat', 'pseudoxml')
    """Formats this writer supports."""

    config_section = 'pseudoxml writer'
    config_section_dependencies = ('writers',)

    output = None
    """Final translated form of `document`."""

    def translate(self):
        self.output = self.document.pformat()

    def supports(self, format):
        """This writer supports all format-specific elements."""
        return 1



More information about the Zope-Checkins mailing list